Conner Bondurant: 2 Implement framework for trait-object tools Appendectomy of code no longer needed 8 files changed, 204 insertions(+), 76 deletions(-)
Copy & paste the following snippet into your terminal to import this patchset into git:
curl -s https://lists.sr.ht/~cbondurant/lipuma-devel/patches/36427/mbox | git am -3Learn more about email & git
--- I hope these are properly formatted! Test patch email
Seemed to work!
src/draw_tools/fractal_line_tool.rs | 152 ++++++++++++++++++++++++++++ src/draw_tools/mod.rs | 2 + src/draw_tools/tool.rs | 17 ++++ src/graphics_scene_widget.rs | 69 ++++--------- src/main.rs | 1 + src/renderobject.rs | 8 ++ 6 files changed, 199 insertions(+), 50 deletions(-) create mode 100644 src/draw_tools/fractal_line_tool.rs create mode 100644 src/draw_tools/mod.rs create mode 100644 src/draw_tools/tool.rs diff --git a/src/draw_tools/fractal_line_tool.rs b/src/draw_tools/fractal_line_tool.rs new file mode 100644 index 0000000..944ea0a --- /dev/null +++ b/src/draw_tools/fractal_line_tool.rs @@ -0,0 +1,152 @@ +use druid::{Data, Point}; +use noise::OpenSimplex; +use rand::random; +use std::rc::Rc; + +use super::tool::Tool; +use crate::{fractal_line::FractalLine, renderobject::RenderObject}; + +#[derive(Data, Clone, PartialEq, Eq)] +enum ToolState { + Drawing, + Standby, +}
For things like this I usually like to derive Debug, too.
+ +#[derive(Data, Clone)] +pub struct FractalLineTool { + preview: FractalLine, + state: ToolState, +} + +impl FractalLineTool { + pub fn new() -> Self { + Self { + preview: FractalLine { + start: Point::ZERO, + end: Point::ZERO, + noise: Rc::new(OpenSimplex::new(0)), + width: 10.0, + density: 0.05, + samples: 1000, + }, + state: ToolState::Standby, + } + } +} + +impl Tool for FractalLineTool { + fn enable(&mut self, _data: &mut crate::graphics_scene_widget::GraphicsData) { + self.state = ToolState::Standby; + } + + fn disable(&mut self, data: &mut crate::graphics_scene_widget::GraphicsData) { + match self.state { + ToolState::Drawing => { + // get_preview always returns some when drawing + data.objects.insert(self.get_preview().unwrap()); + } + ToolState::Standby => (), + } + } + + fn on_mouse_move( + &mut self, + event: &druid::MouseEvent, + ctx: &mut druid::EventCtx, + data: &mut crate::graphics_scene_widget::GraphicsData, + ) { + match self.state { + ToolState::Drawing => { + ctx.set_handled(); + self.preview.end = event.pos; + } + ToolState::Standby => (), + } + } + + fn on_mouse_down( + &mut self, + event: &druid::MouseEvent, + ctx: &mut druid::EventCtx, + data: &mut crate::graphics_scene_widget::GraphicsData, + ) { + self.state = ToolState::Drawing; + self.preview = FractalLine { + start: event.pos, + end: event.pos, + noise: Rc::new(OpenSimplex::new(random())), + width: 10.0, + density: 0.05, + samples: 1000, + }; + ctx.set_handled(); + } + + fn on_mouse_up( + &mut self, + event: &druid::MouseEvent, + ctx: &mut druid::EventCtx, + data: &mut crate::graphics_scene_widget::GraphicsData, + ) { + match self.state { + ToolState::Drawing => { + self.preview.end = event.pos; + let mut obj = self.get_preview().unwrap(); + obj.z = match data.objects.get_max() { + Some(obj) => obj.z + 1, + None => 0, + }; + self.state = ToolState::Standby; + data.objects.insert(obj); + ctx.is_handled(); + } + ToolState::Standby => (), + } + } + + fn on_mouse_wheel( + &mut self, + event: &druid::MouseEvent, + ctx: &mut druid::EventCtx, + data: &mut crate::graphics_scene_widget::GraphicsData, + ) { + () + } + + fn on_key_down( + &mut self, + _event: &druid::KeyEvent, + _ctx: &mut druid::EventCtx, + _data: &mut crate::graphics_scene_widget::GraphicsData, + ) { + () + } + + fn on_key_up( + &mut self, + _event: &druid::KeyEvent, + _ctx: &mut druid::EventCtx, + _data: &mut crate::graphics_scene_widget::GraphicsData, + ) { + () + } + + fn on_paste( + &mut self, + _event: &druid::Clipboard, + _ctx: &mut druid::EventCtx, + _data: &mut crate::graphics_scene_widget::GraphicsData, + ) { + () + } + + fn get_preview(&self) -> Option<crate::renderobject::RenderObject> { + match self.state { + ToolState::Drawing => Some(RenderObject::new( + u32::MAX, + Rc::new(Box::new(self.preview.clone())), + )), + ToolState::Standby => None, + } + } +} diff --git a/src/draw_tools/mod.rs b/src/draw_tools/mod.rs new file mode 100644 index 0000000..033ef91 --- /dev/null +++ b/src/draw_tools/mod.rs @@ -0,0 +1,2 @@ +pub mod fractal_line_tool; +pub mod tool; diff --git a/src/draw_tools/tool.rs b/src/draw_tools/tool.rs new file mode 100644 index 0000000..9aec10a --- /dev/null +++ b/src/draw_tools/tool.rs @@ -0,0 +1,17 @@ +use crate::{renderobject::RenderObject, GraphicsData}; +use druid::{Clipboard, EventCtx, KeyEvent, MouseEvent}; + +pub trait Tool { + fn enable(&mut self, data: &mut GraphicsData); + fn disable(&mut self, data: &mut GraphicsData); + + fn on_mouse_move(&mut self, event: &MouseEvent, ctx: &mut EventCtx, data: &mut GraphicsData); + fn on_mouse_down(&mut self, event: &MouseEvent, ctx: &mut EventCtx, data: &mut GraphicsData); + fn on_mouse_up(&mut self, event: &MouseEvent, ctx: &mut EventCtx, data: &mut GraphicsData); + + fn on_mouse_wheel(&mut self, event: &MouseEvent, ctx: &mut EventCtx, data: &mut GraphicsData); + fn on_key_down(&mut self, event: &KeyEvent, ctx: &mut EventCtx, data: &mut GraphicsData); + fn on_key_up(&mut self, event: &KeyEvent, ctx: &mut EventCtx, data: &mut GraphicsData); + fn on_paste(&mut self, event: &Clipboard, ctx: &mut EventCtx, data: &mut GraphicsData);
Right now you have to implement a bunch of methods on each struct that is Tool, even if it won't use them. If you add another method to the trait, it'll have to also be added on all the Tools. Did you consider instead creating one handler method, something like `on_event`, and an `Event` enum that wraps around the other events? Then Tool impls could switch on Event and choose which which events to match on, and ignore the other ones. That could make some of the Tool implementations simpler if they're not going to all use all the events.
+ fn get_preview(&self) -> Option<RenderObject>; +} diff --git a/src/graphics_scene_widget.rs b/src/graphics_scene_widget.rs index 7433c92..1fc6b26 100644 --- a/src/graphics_scene_widget.rs +++ b/src/graphics_scene_widget.rs @@ -10,8 +10,9 @@ use druid::{Data, Widget}; use noise::OpenSimplex; +use crate::draw_tools::fractal_line_tool::FractalLineTool; +use crate::draw_tools::tool::Tool; use crate::drawable::Drawable; -use crate::fractal_line::FractalLine; use crate::renderobject::RenderObject; #[derive(Data, Clone, Debug)] @@ -20,7 +21,7 @@ pub struct Line(Point, Point, Rc<OpenSimplex>); #[derive(Data, Clone)] pub struct GraphicsData { pub objects: OrdSet<RenderObject>, - pub preview: Option<FractalLine>, + pub preview: Option<RenderObject>, } pub enum GraphicsEngineState { @@ -32,6 +33,7 @@ pub struct GraphicsWidget { pub state: GraphicsEngineState, change_list: OrdSet<RenderObject>, remove_list: Vector<RenderObject>, + current_tool: Rc<Box<dyn Tool>>, } impl GraphicsWidget { @@ -40,6 +42,7 @@ impl GraphicsWidget { state: GraphicsEngineState::Default, change_list: OrdSet::new(), remove_list: Vector::new(), + current_tool: Rc::new(Box::new(FractalLineTool::new())), } } @@ -66,49 +69,22 @@ impl Widget<GraphicsData> for GraphicsWidget { ) { match event { druid::Event::WindowConnected => {} - druid::Event::MouseDown(event) => match self.state { - GraphicsEngineState::Default => { - self.enter_state(GraphicsEngineState::Drawing); - data.preview = Some(FractalLine { - start: event.pos, - end: event.pos, - noise: Rc::new(OpenSimplex::new(rand::random())), - width: 10.0, - density: 0.05, - samples: 1000, - }); - } - GraphicsEngineState::Drawing => (), - }, - druid::Event::MouseUp(_) => match self.state { - GraphicsEngineState::Default => (), - GraphicsEngineState::Drawing => { - data.objects.insert(RenderObject { - transform: Affine::scale(1.0), - drawable: Rc::new(Box::new(data.preview.take().unwrap())), - z: match data.objects.get_max() { - Some(v) => v.z + 1, - None => 0, - }, - }); - - data.preview = None; - self.enter_state(GraphicsEngineState::Default); - } - }, - druid::Event::MouseMove(event) => { - if let GraphicsEngineState::Drawing = self.state { - if let Some(preview) = &mut data.preview { - preview.end = event.pos; - } - } - } + druid::Event::MouseDown(event) => Rc::get_mut(&mut self.current_tool) + .unwrap() + .on_mouse_down(event, ctx, data), + druid::Event::MouseUp(event) => Rc::get_mut(&mut self.current_tool) + .unwrap() + .on_mouse_up(event, ctx, data), + druid::Event::MouseMove(event) => Rc::get_mut(&mut self.current_tool) + .unwrap() + .on_mouse_move(event, ctx, data), druid::Event::WindowSize(_) => { // Need to request full repaint to ensure everything draws correctly ctx.request_paint(); } _ => (), } + data.preview = self.current_tool.get_preview(); } fn lifecycle( @@ -162,18 +138,11 @@ impl Widget<GraphicsData> for GraphicsWidget { if let (Some(old), Some(new)) = (&old_data.preview, &data.preview) { if !old.same(new) { - self.change_list.insert(RenderObject { - transform: Affine::scale(1.0), - drawable: Rc::new(Box::new(new.clone())), - z: match data.objects.get_max() { - Some(v) => v.z + 1, - None => 0, - }, - }); + self.change_list.insert(new.clone()); } - ctx.request_paint_rect(old.AABB()); - ctx.request_paint_rect(new.AABB()); + ctx.request_paint_rect(old.get_drawable().AABB()); + ctx.request_paint_rect(new.get_drawable().AABB()); } } @@ -215,7 +184,7 @@ impl Widget<GraphicsData> for GraphicsWidget { robj.paint(ctx, env); } if let Some(line) = &data.preview { - line.paint(ctx, env, &Affine::rotate(0.0)); + line.paint(ctx, env); } ctx.restore().unwrap(); self.change_list.clear(); diff --git a/src/main.rs b/src/main.rs index 1418d97..9a77fd3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ use druid::im::ordset; use druid::{theme, AppLauncher, Color, PlatformError, Widget, WindowDesc}; mod bound; +mod draw_tools; mod drawable; mod fractal_line; mod graphics_scene_widget; diff --git a/src/renderobject.rs b/src/renderobject.rs index b2847d1..f3ba3d2 100644 --- a/src/renderobject.rs +++ b/src/renderobject.rs @@ -41,6 +41,14 @@ impl RenderObject { }); } + pub fn new(z: u32, drawable: Rc<Box<dyn Drawable>>) -> Self { + Self { + z, + transform: Affine::new([1.0, 0.0, 0.0, 1.0, 0.0, 0.0]), + drawable, + } + } + pub fn get_drawable(&self) -> Rc<Box<dyn Drawable>> { Rc::clone(&self.drawable) } -- 2.34.1
This looks pretty good to me. I like the pattern of dispatching to the tool object, which will then know how to handle the events. I wonder if all of the events are necessary—are things like mousewheel in there for future expansion? I left one suggestion that I think could make it simpler, but this way is good (and of course, your project not mine!).
--- src/draw_tools/fractal_line_tool.rs | 10 +++++----- src/graphics_scene_widget.rs | 21 --------------------- 2 files changed, 5 insertions(+), 26 deletions(-) diff --git a/src/draw_tools/fractal_line_tool.rs b/src/draw_tools/fractal_line_tool.rs index 944ea0a..aa4b371 100644 --- a/src/draw_tools/fractal_line_tool.rs +++ b/src/draw_tools/fractal_line_tool.rs @@ -53,7 +53,7 @@ impl Tool for FractalLineTool { &mut self, event: &druid::MouseEvent, ctx: &mut druid::EventCtx, - data: &mut crate::graphics_scene_widget::GraphicsData, + _data: &mut crate::graphics_scene_widget::GraphicsData, ) { match self.state { ToolState::Drawing => { @@ -68,7 +68,7 @@ impl Tool for FractalLineTool { &mut self, event: &druid::MouseEvent, ctx: &mut druid::EventCtx, - data: &mut crate::graphics_scene_widget::GraphicsData, + _data: &mut crate::graphics_scene_widget::GraphicsData, ) { self.state = ToolState::Drawing; self.preview = FractalLine { @@ -106,9 +106,9 @@ impl Tool for FractalLineTool { fn on_mouse_wheel( &mut self, - event: &druid::MouseEvent, - ctx: &mut druid::EventCtx, - data: &mut crate::graphics_scene_widget::GraphicsData, + _event: &druid::MouseEvent, + _ctx: &mut druid::EventCtx, + _data: &mut crate::graphics_scene_widget::GraphicsData, ) { () } diff --git a/src/graphics_scene_widget.rs b/src/graphics_scene_widget.rs index 1fc6b26..433d10f 100644 --- a/src/graphics_scene_widget.rs +++ b/src/graphics_scene_widget.rs @@ -2,7 +2,6 @@ use std::rc::Rc; use druid::im::OrdSet; use druid::im::Vector; -use druid::Affine; use druid::Color; use druid::Point; use druid::RenderContext; @@ -12,7 +11,6 @@ use noise::OpenSimplex; use crate::draw_tools::fractal_line_tool::FractalLineTool; use crate::draw_tools::tool::Tool; -use crate::drawable::Drawable; use crate::renderobject::RenderObject; #[derive(Data, Clone, Debug)] @@ -24,13 +22,7 @@ pub struct GraphicsData { pub preview: Option<RenderObject>, } -pub enum GraphicsEngineState { - Default, - Drawing, -} - pub struct GraphicsWidget { - pub state: GraphicsEngineState, change_list: OrdSet<RenderObject>, remove_list: Vector<RenderObject>, current_tool: Rc<Box<dyn Tool>>, @@ -39,24 +31,11 @@ pub struct GraphicsWidget { impl GraphicsWidget { pub fn new() -> Self { Self { - state: GraphicsEngineState::Default, change_list: OrdSet::new(), remove_list: Vector::new(), current_tool: Rc::new(Box::new(FractalLineTool::new())), } } - - fn enter_state(&mut self, new_state: GraphicsEngineState) { - self.exit_state(); - self.state = new_state; - } - - fn exit_state(&self) { - match self.state { - GraphicsEngineState::Default => (), - GraphicsEngineState::Drawing => (), - } - } } impl Widget<GraphicsData> for GraphicsWidget { -- 2.34.1