mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-12-23 10:11:54 +00:00
Improve Ellipse Tool (#98)
This commit is contained in:
parent
76eb944233
commit
c9fea54ec5
7 changed files with 161 additions and 30 deletions
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
|
|||
37
core/document/src/layers/ellipse.rs
Normal file
37
core/document/src/layers/ellipse.rs
Normal 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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -174,6 +174,8 @@ pub enum Key {
|
|||
Key7,
|
||||
Key8,
|
||||
Key9,
|
||||
KeyShift,
|
||||
KeyAlt,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
|
|
|
|||
|
|
@ -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))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue