From c9fea54ec5bb3a93feeabf2b11ff71a502b9cc6d Mon Sep 17 00:00:00 2001 From: Paul Kupper <11900073+pkupper@users.noreply.github.com> Date: Tue, 4 May 2021 10:53:00 +0200 Subject: [PATCH] Improve Ellipse Tool (#98) --- client/web/wasm/src/wrappers.rs | 2 + core/document/src/document.rs | 30 +++++--- core/document/src/layers/ellipse.rs | 37 ++++++++++ core/document/src/layers/mod.rs | 5 ++ core/document/src/operation.rs | 10 +++ core/editor/src/dispatcher/events.rs | 2 + core/editor/src/tools/ellipse.rs | 105 ++++++++++++++++++++++----- 7 files changed, 161 insertions(+), 30 deletions(-) create mode 100644 core/document/src/layers/ellipse.rs diff --git a/client/web/wasm/src/wrappers.rs b/client/web/wasm/src/wrappers.rs index 8bcce120a..f8ddc5400 100644 --- a/client/web/wasm/src/wrappers.rs +++ b/client/web/wasm/src/wrappers.rs @@ -108,6 +108,8 @@ pub fn translate_key(name: &str) -> events::Key { "8" => K::Key8, "9" => K::Key9, "Enter" => K::KeyEnter, + "Shift" => K::KeyShift, + "Alt" => K::KeyAlt, _ => K::UnknownKey, } } diff --git a/core/document/src/document.rs b/core/document/src/document.rs index 4c2e04ab5..d44203d2c 100644 --- a/core/document/src/document.rs +++ b/core/document/src/document.rs @@ -193,7 +193,21 @@ impl Document { self.work_operations.push(operation.clone()); let responses = match operation { Operation::AddCircle { path, insert_index, cx, cy, r, style } => { - self.add_layer(&path, Layer::new(LayerDataTypes::Circle(layers::Circle::new(kurbo::Point::new(cx, cy), r, style))), insert_index)?; + self.add_layer(&path, Layer::new(LayerDataTypes::Circle(layers::Circle::new((cx, cy), r, style))), insert_index)?; + + Some(vec![DocumentResponse::DocumentChanged]) + } + Operation::AddEllipse { + path, + insert_index, + cx, + cy, + rx, + ry, + rot, + style, + } => { + self.add_layer(&path, Layer::new(LayerDataTypes::Ellipse(layers::Ellipse::new((cx, cy), (rx, ry), rot, style))), insert_index)?; Some(vec![DocumentResponse::DocumentChanged]) } @@ -206,11 +220,7 @@ impl Document { y1, style, } => { - self.add_layer( - &path, - Layer::new(LayerDataTypes::Rect(Rect::new(kurbo::Point::new(x0, y0), kurbo::Point::new(x1, y1), style))), - insert_index, - )?; + self.add_layer(&path, Layer::new(LayerDataTypes::Rect(Rect::new((x0, y0), (x1, y1), style))), insert_index)?; Some(vec![DocumentResponse::DocumentChanged]) } @@ -223,11 +233,7 @@ impl Document { y1, style, } => { - self.add_layer( - &path, - Layer::new(LayerDataTypes::Line(Line::new(kurbo::Point::new(x0, y0), kurbo::Point::new(x1, y1), style))), - insert_index, - )?; + self.add_layer(&path, Layer::new(LayerDataTypes::Line(Line::new((x0, y0), (x1, y1), style))), insert_index)?; Some(vec![DocumentResponse::DocumentChanged]) } @@ -247,7 +253,7 @@ impl Document { sides, style, } => { - let s = Shape::new(kurbo::Point::new(x0, y0), kurbo::Vec2 { x: x0 - x1, y: y0 - y1 }, sides, style); + let s = Shape::new((x0, y0), (x0 - x1, y0 - y1), sides, style); self.add_layer(&path, Layer::new(LayerDataTypes::Shape(s)), insert_index)?; Some(vec![DocumentResponse::DocumentChanged]) diff --git a/core/document/src/layers/ellipse.rs b/core/document/src/layers/ellipse.rs new file mode 100644 index 000000000..906f97b4b --- /dev/null +++ b/core/document/src/layers/ellipse.rs @@ -0,0 +1,37 @@ +use super::style; +use super::LayerData; + +use std::fmt::Write; + +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub struct Ellipse { + shape: kurbo::Ellipse, + style: style::PathStyle, +} + +impl Ellipse { + pub fn new(center: impl Into, radii: impl Into, rotation: f64, style: style::PathStyle) -> Ellipse { + Ellipse { + shape: kurbo::Ellipse::new(center, radii, rotation), + style, + } + } +} + +impl LayerData for Ellipse { + fn render(&mut self, svg: &mut String) { + let kurbo::Vec2 { x: rx, y: ry } = self.shape.radii(); + let kurbo::Point { x: cx, y: cy } = self.shape.center(); + + let _ = write!( + svg, + r#""#, + rx, + ry, + cx, + cy, + self.shape.rotation().to_degrees(), + self.style.render(), + ); + } +} diff --git a/core/document/src/layers/mod.rs b/core/document/src/layers/mod.rs index 95f6ead16..9c88f464b 100644 --- a/core/document/src/layers/mod.rs +++ b/core/document/src/layers/mod.rs @@ -3,6 +3,9 @@ pub mod style; pub mod circle; pub use circle::Circle; +pub mod ellipse; +pub use ellipse::Ellipse; + pub mod line; pub use line::Line; @@ -26,6 +29,7 @@ pub trait LayerData { pub enum LayerDataTypes { Folder(Folder), Circle(Circle), + Ellipse(Ellipse), Rect(Rect), Line(Line), PolyLine(PolyLine), @@ -37,6 +41,7 @@ impl LayerDataTypes { match self { Self::Folder(f) => f.render(svg), Self::Circle(c) => c.render(svg), + Self::Ellipse(e) => e.render(svg), Self::Rect(r) => r.render(svg), Self::Line(l) => l.render(svg), Self::PolyLine(pl) => pl.render(svg), diff --git a/core/document/src/operation.rs b/core/document/src/operation.rs index 0938808c7..01cc3479c 100644 --- a/core/document/src/operation.rs +++ b/core/document/src/operation.rs @@ -12,6 +12,16 @@ pub enum Operation { r: f64, style: style::PathStyle, }, + AddEllipse { + path: Vec, + insert_index: isize, + cx: f64, + cy: f64, + rx: f64, + ry: f64, + rot: f64, + style: style::PathStyle, + }, AddRect { path: Vec, insert_index: isize, diff --git a/core/editor/src/dispatcher/events.rs b/core/editor/src/dispatcher/events.rs index 2c4be0607..e6428e503 100644 --- a/core/editor/src/dispatcher/events.rs +++ b/core/editor/src/dispatcher/events.rs @@ -174,6 +174,8 @@ pub enum Key { Key7, Key8, Key9, + KeyShift, + KeyAlt, } bitflags! { diff --git a/core/editor/src/tools/ellipse.rs b/core/editor/src/tools/ellipse.rs index f0cabb8e3..795f06c28 100644 --- a/core/editor/src/tools/ellipse.rs +++ b/core/editor/src/tools/ellipse.rs @@ -37,6 +37,9 @@ impl Default for EllipseToolFsmState { #[derive(Clone, Debug, Default)] struct EllipseToolData { drag_start: ViewportPosition, + drag_current: ViewportPosition, + constrain_to_circle: bool, + center_around_cursor: bool, } impl Fsm for EllipseToolFsmState { @@ -46,6 +49,7 @@ impl Fsm for EllipseToolFsmState { match (self, event) { (EllipseToolFsmState::Ready, Event::LmbDown(mouse_state)) => { data.drag_start = mouse_state.position; + data.drag_current = mouse_state.position; operations.push(Operation::MountWorkingFolder { path: vec![] }); EllipseToolFsmState::LmbDown } @@ -58,37 +62,102 @@ impl Fsm for EllipseToolFsmState { } (EllipseToolFsmState::LmbDown, Event::MouseMove(mouse_state)) => { + data.drag_current = *mouse_state; + operations.push(Operation::ClearWorkingFolder); - operations.push(Operation::AddCircle { - path: vec![], - insert_index: -1, - cx: data.drag_start.x as f64, - cy: data.drag_start.y as f64, - r: data.drag_start.distance(&mouse_state), - style: style::PathStyle::new(None, Some(style::Fill::new(tool_data.primary_color))), - }); + operations.push(make_operation(data, tool_data)); EllipseToolFsmState::LmbDown } (EllipseToolFsmState::LmbDown, Event::LmbUp(mouse_state)) => { - let r = data.drag_start.distance(&mouse_state.position); - log::info!("draw ellipse with radius: {:.2}", r); + data.drag_current = mouse_state.position; + operations.push(Operation::ClearWorkingFolder); - operations.push(Operation::AddCircle { - path: vec![], - insert_index: -1, - cx: data.drag_start.x as f64, - cy: data.drag_start.y as f64, - r: data.drag_start.distance(&mouse_state.position), - style: style::PathStyle::new(None, Some(style::Fill::new(tool_data.primary_color))), - }); + operations.push(make_operation(data, tool_data)); operations.push(Operation::CommitTransaction); EllipseToolFsmState::Ready } + (state, Event::KeyDown(Key::KeyShift)) => { + data.constrain_to_circle = true; + + if state == EllipseToolFsmState::LmbDown { + operations.push(Operation::ClearWorkingFolder); + operations.push(make_operation(data, tool_data)); + } + + self + } + + (state, Event::KeyUp(Key::KeyShift)) => { + data.constrain_to_circle = false; + + if state == EllipseToolFsmState::LmbDown { + operations.push(Operation::ClearWorkingFolder); + operations.push(make_operation(data, tool_data)); + } + + self + } + + (state, Event::KeyDown(Key::KeyAlt)) => { + data.center_around_cursor = true; + + if state == EllipseToolFsmState::LmbDown { + operations.push(Operation::ClearWorkingFolder); + operations.push(make_operation(data, tool_data)); + } + + self + } + + (state, Event::KeyUp(Key::KeyAlt)) => { + data.center_around_cursor = false; + + if state == EllipseToolFsmState::LmbDown { + operations.push(Operation::ClearWorkingFolder); + operations.push(make_operation(data, tool_data)); + } + + self + } + _ => self, } } } + +fn make_operation(data: &EllipseToolData, tool_data: &DocumentToolData) -> Operation { + let x0 = data.drag_start.x as f64; + let y0 = data.drag_start.y as f64; + let x1 = data.drag_current.x as f64; + let y1 = data.drag_current.y as f64; + + let (cx, cy, r_scale) = if data.center_around_cursor { (x0, y0, 1.0) } else { ((x0 + x1) * 0.5, (y0 + y1) * 0.5, 0.5) }; + + if data.constrain_to_circle { + let r = f64::max((x1 - x0).abs(), (y1 - y0).abs()) * r_scale; + Operation::AddCircle { + path: vec![], + insert_index: -1, + cx, + cy, + r, + style: style::PathStyle::new(None, Some(style::Fill::new(tool_data.primary_color))), + } + } else { + let (rx, ry) = ((x1 - x0).abs() * r_scale, (y1 - y0).abs() * r_scale); + Operation::AddEllipse { + path: vec![], + insert_index: -1, + cx, + cy, + rx, + ry, + rot: 0.0, + style: style::PathStyle::new(None, Some(style::Fill::new(tool_data.primary_color))), + } + } +}