Rust trial: port interface to Sycamore
Now we have a reactive web app written entirely in Rust. The Trunk build tool compiles it to WebAssembly and generates a little JavaScript glue.
This commit is contained in:
parent
12abef4076
commit
42bdfabd91
10 changed files with 178 additions and 220 deletions
|
@ -1,55 +0,0 @@
|
|||
mod utils;
|
||||
|
||||
extern crate js_sys;
|
||||
|
||||
use nalgebra::*;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct Circle {
|
||||
pub center_x: f64,
|
||||
pub center_y: f64,
|
||||
pub radius: f64,
|
||||
}
|
||||
|
||||
// construct the circle through
|
||||
//
|
||||
// (x1, y1), (x2, y2), (x3, y3)
|
||||
//
|
||||
// from the array
|
||||
//
|
||||
// [x1, y1, x2, y2, x3, y3]
|
||||
//
|
||||
#[wasm_bindgen]
|
||||
pub fn circThru(data_raw: js_sys::Float64Array) -> Result<Circle, JsValue> {
|
||||
// represent the given points as the columns of a matrix
|
||||
let data = Matrix2x3::from_vec(data_raw.to_vec());
|
||||
|
||||
// build the matrix that maps the circle's coefficient vector to the
|
||||
// negative of the linear part of the circle's equation, evaluated at the
|
||||
// given points
|
||||
let neg_lin_part = stack![2.0*data.transpose(), Vector3::repeat(1.0)];
|
||||
|
||||
// find the quadrdatic part of the circle's equation, evaluated at the given
|
||||
// points
|
||||
let quad_part = Vector3::from_iterator(
|
||||
data.column_iter().map(|v| v.dot(&v))
|
||||
);
|
||||
|
||||
// find the circle's coefficient vector, and from there its center and
|
||||
// radius
|
||||
match neg_lin_part.lu().solve(&quad_part) {
|
||||
None => Err(JsValue::from("Couldn't solve system")),
|
||||
Some(coeffs) => {
|
||||
let center_x = coeffs[0];
|
||||
let center_y = coeffs[1];
|
||||
Ok(Circle {
|
||||
center_x: center_x,
|
||||
center_y: center_y,
|
||||
radius: (
|
||||
coeffs[2] + center_x*center_x + center_y*center_y
|
||||
).sqrt(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
153
lang-trials/rust/src/main.rs
Normal file
153
lang-trials/rust/src/main.rs
Normal file
|
@ -0,0 +1,153 @@
|
|||
use nalgebra::*;
|
||||
use std::f64::consts::PI as PI;
|
||||
use sycamore::{prelude::*, rt::{JsCast, JsValue}};
|
||||
|
||||
// --- interface ---
|
||||
|
||||
fn main() {
|
||||
// set up a config option that forwards panic messages to `console.error`
|
||||
#[cfg(feature = "console_error_panic_hook")]
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
sycamore::render(|| {
|
||||
let data = [-1.0, 0.0, 0.0, -1.0, 1.0, 0.0].map(|n| create_signal(n));
|
||||
let display = create_node_ref();
|
||||
|
||||
on_mount(move || {
|
||||
let canvas = display
|
||||
.get::<DomNode>()
|
||||
.unchecked_into::<web_sys::HtmlCanvasElement>();
|
||||
let ctx = canvas
|
||||
.get_context("2d")
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.dyn_into::<web_sys::CanvasRenderingContext2d>()
|
||||
.unwrap();
|
||||
|
||||
create_effect(move || {
|
||||
// center and normalize the coordinate system
|
||||
let width = canvas.width() as f64;
|
||||
let height = canvas.height() as f64;
|
||||
ctx.set_transform(1.0, 0.0, 0.0, -1.0, 0.5*width, 0.5*height).unwrap();
|
||||
|
||||
// clear the previous frame
|
||||
ctx.clear_rect(-0.5*width, -0.5*width, width, height);
|
||||
|
||||
// find the resolution
|
||||
const R_DISP: f64 = 5.0;
|
||||
let res = width / (2.0*R_DISP);
|
||||
|
||||
// set colors
|
||||
let highlight_style = JsValue::from("white");
|
||||
let grid_style = JsValue::from("#404040");
|
||||
let point_fill_styles = ["#ba5d09", "#0e8a06", "#8951fb"];
|
||||
let point_stroke_styles = ["#f89142", "#58c145", "#c396fc"];
|
||||
|
||||
// draw the grid
|
||||
let r_grid = (R_DISP - 0.01).floor() as i32;
|
||||
let edge_scr = res * R_DISP;
|
||||
ctx.set_stroke_style(&grid_style);
|
||||
for t in -r_grid ..= r_grid {
|
||||
let t_scr = res * (t as f64);
|
||||
|
||||
// draw horizontal grid line
|
||||
ctx.begin_path();
|
||||
ctx.move_to(-edge_scr, t_scr);
|
||||
ctx.line_to(edge_scr, t_scr);
|
||||
ctx.stroke();
|
||||
|
||||
// draw vertical grid line
|
||||
ctx.begin_path();
|
||||
ctx.move_to(t_scr, -edge_scr);
|
||||
ctx.line_to(t_scr, edge_scr);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// find and draw the circle through the given points
|
||||
let data_vals = data.map(|sig| sig.get()).to_vec();
|
||||
let points = Matrix2x3::from_vec(data_vals);
|
||||
if let Some(circ) = circ_thru(points) {
|
||||
ctx.begin_path();
|
||||
ctx.set_stroke_style(&highlight_style);
|
||||
ctx.arc(
|
||||
res * circ.center_x,
|
||||
res * circ.center_y,
|
||||
res * circ.radius,
|
||||
0.0, 2.0*PI
|
||||
).unwrap();
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// draw the data points
|
||||
for n in 0..3 {
|
||||
ctx.begin_path();
|
||||
ctx.set_fill_style(&JsValue::from(point_fill_styles[n]));
|
||||
ctx.set_stroke_style(&JsValue::from(point_stroke_styles[n]));
|
||||
let ind_x = 2*n;
|
||||
let ind_y = ind_x + 1;
|
||||
ctx.arc(
|
||||
res * data[ind_x].get(),
|
||||
res * data[ind_y].get(),
|
||||
3.0,
|
||||
0.0, 2.0*PI
|
||||
).unwrap();
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
view! {
|
||||
canvas(ref=display, width="600", height="600")
|
||||
div(id="data-panel") {
|
||||
div { "x" }
|
||||
div { "y" }
|
||||
input(type="number", class="point-1", bind:valueAsNumber=data[0])
|
||||
input(type="number", class="point-1", bind:valueAsNumber=data[1])
|
||||
input(type="number", class="point-2", bind:valueAsNumber=data[2])
|
||||
input(type="number", class="point-2", bind:valueAsNumber=data[3])
|
||||
input(type="number", class="point-3", bind:valueAsNumber=data[4])
|
||||
input(type="number", class="point-3", bind:valueAsNumber=data[5])
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- engine ---
|
||||
|
||||
struct Circle {
|
||||
center_x: f64,
|
||||
center_y: f64,
|
||||
radius: f64,
|
||||
}
|
||||
|
||||
// construct the circle through the points given by the columns of `points`
|
||||
fn circ_thru(points: Matrix2x3<f64>) -> Option<Circle> {
|
||||
// build the matrix that maps the circle's coefficient vector to the
|
||||
// negative of the linear part of the circle's equation, evaluated at the
|
||||
// given points
|
||||
let neg_lin_part = stack![2.0*points.transpose(), Vector3::repeat(1.0)];
|
||||
|
||||
// find the quadrdatic part of the circle's equation, evaluated at the given
|
||||
// points
|
||||
let quad_part = Vector3::from_iterator(
|
||||
points.column_iter().map(|v| v.dot(&v))
|
||||
);
|
||||
|
||||
// find the circle's coefficient vector, and from there its center and
|
||||
// radius
|
||||
match neg_lin_part.lu().solve(&quad_part) {
|
||||
None => None,
|
||||
Some(coeffs) => {
|
||||
let center_x = coeffs[0];
|
||||
let center_y = coeffs[1];
|
||||
Some(Circle {
|
||||
center_x: center_x,
|
||||
center_y: center_y,
|
||||
radius: (
|
||||
coeffs[2] + center_x*center_x + center_y*center_y
|
||||
).sqrt(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
pub fn set_panic_hook() {
|
||||
// When the `console_error_panic_hook` feature is enabled, we can call the
|
||||
// `set_panic_hook` function at least once during initialization, and then
|
||||
// we will get better error messages if our code ever panics.
|
||||
//
|
||||
// For more details see
|
||||
// https://github.com/rustwasm/console_error_panic_hook#readme
|
||||
#[cfg(feature = "console_error_panic_hook")]
|
||||
console_error_panic_hook::set_once();
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue