lj_sketch_front/src/canvas.rs

137 lines
3.6 KiB
Rust

use yew::prelude::*;
use web_sys::{HtmlCanvasElement, CanvasRenderingContext2d,
MouseEvent, EventTarget, Element, DomRect};
use wasm_bindgen::{JsValue, JsCast};
pub type Point = (f32, f32, String);
pub type Line = Vec<Point>;
#[derive(Clone,Debug,Properties,PartialEq)]
pub struct Sketch {
pub lines: Vec<Line>
}
#[derive(Debug)]
pub struct CanvasComp {
node_ref: NodeRef,
line: Line,
}
#[derive(Debug)]
pub enum CanvasMsg {
MoveTo(Point),
LineTo(Point),
Stroke,
}
#[derive(Clone, PartialEq, Properties, Debug)]
pub struct CanvasProps {
pub width: u32,
pub height: u32,
pub color: String,
pub sketch: Sketch,
pub on_newline: Callback<Line>
}
impl Component for CanvasComp {
type Message = Option<CanvasMsg>;
type Properties = CanvasProps;
fn create(_ctx: &Context<Self>) -> Self {
Self {
node_ref: NodeRef::default(),
line: vec![],
}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let props = ctx.props();
let size = (props.width as f32 , props.height as f32);
let width : AttrValue = format!("{}", props.width).into();
let height : AttrValue = format!("{}", props.height).into();
let onmousedown = {
let color = props.color.clone();
ctx.link().callback(move |e: MouseEvent| {
let (x, y) = get_position(e, size);
Some(CanvasMsg::MoveTo((x, y, color.clone())))
})
};
let onmousemove = {
let color = props.color.clone();
ctx.link().callback(move |e: MouseEvent| {
match e.buttons() {
0 => None,
_ => {
let (x, y) = get_position(e, size);
Some(CanvasMsg::LineTo((x, y, color.clone())))
}
}
})
};
let onmouseup = ctx.link().callback(move |_| Some(CanvasMsg::Stroke));
html! {
<canvas {width} {height} {onmousedown} {onmousemove} {onmouseup}
ref={self.node_ref.clone()} />
}
}
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
let Some(msg) = msg else {
return false;
};
match msg {
CanvasMsg::MoveTo(point) => self.line = vec![ point ],
CanvasMsg::LineTo(point) => self.line.push(point),
CanvasMsg::Stroke => {
ctx.props().on_newline.emit(self.line.clone());
self.line.clear();
}
};
true
}
fn rendered(&mut self, ctx: &Context<Self>, _first_render: bool) {
let canvas = self.node_ref.cast::<HtmlCanvasElement>().unwrap();
let ctx2d: CanvasRenderingContext2d = canvas
.get_context("2d").unwrap().unwrap()
.dyn_into().unwrap();
let (width, height) = (canvas.width(), canvas.height());
ctx2d.set_line_width(2.0);
ctx2d.set_fill_style(&JsValue::from_str("#000000"));
ctx2d.fill_rect(0.0, 0.0, width.into(), height.into());
for line in &ctx.props().sketch.lines {
draw_line(&ctx2d, line);
}
if !self.line.is_empty() {
draw_line(&ctx2d, &self.line);
}
}
}
fn draw_line(ctx: &CanvasRenderingContext2d, line: &Line) {
ctx.set_stroke_style(&JsValue::from_str(&line[0].2));
ctx.begin_path();
ctx.move_to(line[0].0.into(), line[0].1.into());
for (x, y, _) in &line[1..] {
ctx.line_to((*x).into(), (*y).into());
}
ctx.stroke();
}
fn get_position(e: MouseEvent, (w, h) : (f32, f32)) -> (f32, f32) {
let target = e.target().and_then(|event_target: EventTarget| {
event_target.dyn_into::<Element>().ok()
}).unwrap();
let rect: DomRect = target.get_bounding_client_rect();
let (width, height) = (rect.width(), rect.height());
let mouse_x = w * e.offset_x() as f32 / width as f32;
let mouse_y = h * e.offset_y() as f32 / height as f32;
(mouse_x, mouse_y)
}