Improve Ellipse Tool (#98)

This commit is contained in:
Paul Kupper 2021-05-04 10:53:00 +02:00 committed by Keavon Chambers
parent 76eb944233
commit c9fea54ec5
7 changed files with 161 additions and 30 deletions

View file

@ -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,
}
}

View file

@ -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])

View file

@ -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<kurbo::Point>, radii: impl Into<kurbo::Vec2>, 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#"<ellipse cx="0" cy="0" rx="{}" ry="{}" transform="translate({} {}) rotate({})" {} />"#,
rx,
ry,
cx,
cy,
self.shape.rotation().to_degrees(),
self.style.render(),
);
}
}

View file

@ -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),

View file

@ -12,6 +12,16 @@ pub enum Operation {
r: f64,
style: style::PathStyle,
},
AddEllipse {
path: Vec<LayerId>,
insert_index: isize,
cx: f64,
cy: f64,
rx: f64,
ry: f64,
rot: f64,
style: style::PathStyle,
},
AddRect {
path: Vec<LayerId>,
insert_index: isize,

View file

@ -174,6 +174,8 @@ pub enum Key {
Key7,
Key8,
Key9,
KeyShift,
KeyAlt,
}
bitflags! {

View file

@ -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))),
}
}
}