Improve Ellipse Tool (#98)
This commit is contained in:
parent
4ffb836673
commit
0ff9e5ff49
|
|
@ -108,6 +108,8 @@ pub fn translate_key(name: &str) -> events::Key {
|
||||||
"8" => K::Key8,
|
"8" => K::Key8,
|
||||||
"9" => K::Key9,
|
"9" => K::Key9,
|
||||||
"Enter" => K::KeyEnter,
|
"Enter" => K::KeyEnter,
|
||||||
|
"Shift" => K::KeyShift,
|
||||||
|
"Alt" => K::KeyAlt,
|
||||||
_ => K::UnknownKey,
|
_ => K::UnknownKey,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -193,7 +193,21 @@ impl Document {
|
||||||
self.work_operations.push(operation.clone());
|
self.work_operations.push(operation.clone());
|
||||||
let responses = match operation {
|
let responses = match operation {
|
||||||
Operation::AddCircle { path, insert_index, cx, cy, r, style } => {
|
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])
|
Some(vec![DocumentResponse::DocumentChanged])
|
||||||
}
|
}
|
||||||
|
|
@ -206,11 +220,7 @@ impl Document {
|
||||||
y1,
|
y1,
|
||||||
style,
|
style,
|
||||||
} => {
|
} => {
|
||||||
self.add_layer(
|
self.add_layer(&path, Layer::new(LayerDataTypes::Rect(Rect::new((x0, y0), (x1, y1), style))), insert_index)?;
|
||||||
&path,
|
|
||||||
Layer::new(LayerDataTypes::Rect(Rect::new(kurbo::Point::new(x0, y0), kurbo::Point::new(x1, y1), style))),
|
|
||||||
insert_index,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Some(vec![DocumentResponse::DocumentChanged])
|
Some(vec![DocumentResponse::DocumentChanged])
|
||||||
}
|
}
|
||||||
|
|
@ -223,11 +233,7 @@ impl Document {
|
||||||
y1,
|
y1,
|
||||||
style,
|
style,
|
||||||
} => {
|
} => {
|
||||||
self.add_layer(
|
self.add_layer(&path, Layer::new(LayerDataTypes::Line(Line::new((x0, y0), (x1, y1), style))), insert_index)?;
|
||||||
&path,
|
|
||||||
Layer::new(LayerDataTypes::Line(Line::new(kurbo::Point::new(x0, y0), kurbo::Point::new(x1, y1), style))),
|
|
||||||
insert_index,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Some(vec![DocumentResponse::DocumentChanged])
|
Some(vec![DocumentResponse::DocumentChanged])
|
||||||
}
|
}
|
||||||
|
|
@ -247,7 +253,7 @@ impl Document {
|
||||||
sides,
|
sides,
|
||||||
style,
|
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)?;
|
self.add_layer(&path, Layer::new(LayerDataTypes::Shape(s)), insert_index)?;
|
||||||
|
|
||||||
Some(vec![DocumentResponse::DocumentChanged])
|
Some(vec![DocumentResponse::DocumentChanged])
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,9 @@ pub mod style;
|
||||||
pub mod circle;
|
pub mod circle;
|
||||||
pub use circle::Circle;
|
pub use circle::Circle;
|
||||||
|
|
||||||
|
pub mod ellipse;
|
||||||
|
pub use ellipse::Ellipse;
|
||||||
|
|
||||||
pub mod line;
|
pub mod line;
|
||||||
pub use line::Line;
|
pub use line::Line;
|
||||||
|
|
||||||
|
|
@ -26,6 +29,7 @@ pub trait LayerData {
|
||||||
pub enum LayerDataTypes {
|
pub enum LayerDataTypes {
|
||||||
Folder(Folder),
|
Folder(Folder),
|
||||||
Circle(Circle),
|
Circle(Circle),
|
||||||
|
Ellipse(Ellipse),
|
||||||
Rect(Rect),
|
Rect(Rect),
|
||||||
Line(Line),
|
Line(Line),
|
||||||
PolyLine(PolyLine),
|
PolyLine(PolyLine),
|
||||||
|
|
@ -37,6 +41,7 @@ impl LayerDataTypes {
|
||||||
match self {
|
match self {
|
||||||
Self::Folder(f) => f.render(svg),
|
Self::Folder(f) => f.render(svg),
|
||||||
Self::Circle(c) => c.render(svg),
|
Self::Circle(c) => c.render(svg),
|
||||||
|
Self::Ellipse(e) => e.render(svg),
|
||||||
Self::Rect(r) => r.render(svg),
|
Self::Rect(r) => r.render(svg),
|
||||||
Self::Line(l) => l.render(svg),
|
Self::Line(l) => l.render(svg),
|
||||||
Self::PolyLine(pl) => pl.render(svg),
|
Self::PolyLine(pl) => pl.render(svg),
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,16 @@ pub enum Operation {
|
||||||
r: f64,
|
r: f64,
|
||||||
style: style::PathStyle,
|
style: style::PathStyle,
|
||||||
},
|
},
|
||||||
|
AddEllipse {
|
||||||
|
path: Vec<LayerId>,
|
||||||
|
insert_index: isize,
|
||||||
|
cx: f64,
|
||||||
|
cy: f64,
|
||||||
|
rx: f64,
|
||||||
|
ry: f64,
|
||||||
|
rot: f64,
|
||||||
|
style: style::PathStyle,
|
||||||
|
},
|
||||||
AddRect {
|
AddRect {
|
||||||
path: Vec<LayerId>,
|
path: Vec<LayerId>,
|
||||||
insert_index: isize,
|
insert_index: isize,
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,8 @@ pub enum Key {
|
||||||
Key7,
|
Key7,
|
||||||
Key8,
|
Key8,
|
||||||
Key9,
|
Key9,
|
||||||
|
KeyShift,
|
||||||
|
KeyAlt,
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,9 @@ impl Default for EllipseToolFsmState {
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
struct EllipseToolData {
|
struct EllipseToolData {
|
||||||
drag_start: ViewportPosition,
|
drag_start: ViewportPosition,
|
||||||
|
drag_current: ViewportPosition,
|
||||||
|
constrain_to_circle: bool,
|
||||||
|
center_around_cursor: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fsm for EllipseToolFsmState {
|
impl Fsm for EllipseToolFsmState {
|
||||||
|
|
@ -46,6 +49,7 @@ impl Fsm for EllipseToolFsmState {
|
||||||
match (self, event) {
|
match (self, event) {
|
||||||
(EllipseToolFsmState::Ready, Event::LmbDown(mouse_state)) => {
|
(EllipseToolFsmState::Ready, Event::LmbDown(mouse_state)) => {
|
||||||
data.drag_start = mouse_state.position;
|
data.drag_start = mouse_state.position;
|
||||||
|
data.drag_current = mouse_state.position;
|
||||||
operations.push(Operation::MountWorkingFolder { path: vec![] });
|
operations.push(Operation::MountWorkingFolder { path: vec![] });
|
||||||
EllipseToolFsmState::LmbDown
|
EllipseToolFsmState::LmbDown
|
||||||
}
|
}
|
||||||
|
|
@ -58,37 +62,102 @@ impl Fsm for EllipseToolFsmState {
|
||||||
}
|
}
|
||||||
|
|
||||||
(EllipseToolFsmState::LmbDown, Event::MouseMove(mouse_state)) => {
|
(EllipseToolFsmState::LmbDown, Event::MouseMove(mouse_state)) => {
|
||||||
|
data.drag_current = *mouse_state;
|
||||||
|
|
||||||
operations.push(Operation::ClearWorkingFolder);
|
operations.push(Operation::ClearWorkingFolder);
|
||||||
operations.push(Operation::AddCircle {
|
operations.push(make_operation(data, tool_data));
|
||||||
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))),
|
|
||||||
});
|
|
||||||
|
|
||||||
EllipseToolFsmState::LmbDown
|
EllipseToolFsmState::LmbDown
|
||||||
}
|
}
|
||||||
|
|
||||||
(EllipseToolFsmState::LmbDown, Event::LmbUp(mouse_state)) => {
|
(EllipseToolFsmState::LmbDown, Event::LmbUp(mouse_state)) => {
|
||||||
let r = data.drag_start.distance(&mouse_state.position);
|
data.drag_current = mouse_state.position;
|
||||||
log::info!("draw ellipse with radius: {:.2}", r);
|
|
||||||
operations.push(Operation::ClearWorkingFolder);
|
operations.push(Operation::ClearWorkingFolder);
|
||||||
operations.push(Operation::AddCircle {
|
operations.push(make_operation(data, tool_data));
|
||||||
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(Operation::CommitTransaction);
|
operations.push(Operation::CommitTransaction);
|
||||||
|
|
||||||
EllipseToolFsmState::Ready
|
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,
|
_ => 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))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue