initial commit
This commit is contained in:
commit
bf2272e70b
9 changed files with 419 additions and 0 deletions
136
src/canvas.rs
Normal file
136
src/canvas.rs
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
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)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue