mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-21 13:28:12 +08:00
Implement anchor and handle point rendering with the Path Tool (#353)
* Implement Path Tool * Draw a red rectangle where the first point on the shape is * Correctly render anchors, handles, and connecting lines * Fix drain() which can panic * Refactor frontend messages to work as return values not callbacks * Reduce the number of unnecessary frontend updates * Fix stack overflow by using a loop * Group Document Render calls and put them at the end * Speed hacks for dirtification * Add performance * Bunch folder changed updates * Add triggers to redraw overlays to movement_handler * Polish the pixel-perfect rendering of vector manipulators * Restore scrollbars that were disabled * Cleanup * WIP Add shape outline rendering * Fix compiling * Add outlines to selected shapes * Fix outlines rendering over handles and anchors * Fix dirtification * Add a comment * Address code review feedback * Formatting * Small tweaks Co-authored-by: Oliver Davies <oliver@psyfer.io> Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
co-authored by
Oliver Davies
Dennis Kobert
parent
8336b3e809
commit
913b9365e2
@@ -40,7 +40,7 @@ impl Color {
|
||||
}
|
||||
|
||||
/// Return an opaque `Color` from given `f32` RGB channels.
|
||||
const fn from_unsafe(red: f32, green: f32, blue: f32) -> Color {
|
||||
pub const fn from_unsafe(red: f32, green: f32, blue: f32) -> Color {
|
||||
Color { red, green, blue, alpha: 1. }
|
||||
}
|
||||
|
||||
|
||||
+89
-13
@@ -215,7 +215,6 @@ impl Document {
|
||||
}
|
||||
|
||||
pub fn mark_as_dirty(&mut self, path: &[LayerId]) -> Result<(), DocumentError> {
|
||||
self.mark_downstream_as_dirty(path)?;
|
||||
self.mark_upstream_as_dirty(path)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -264,6 +263,10 @@ impl Document {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn generate_transform_relative_to_viewport(&self, from: &[LayerId]) -> Result<DAffine2, DocumentError> {
|
||||
self.generate_transform_across_scope(from, None)
|
||||
}
|
||||
|
||||
pub fn apply_transform_relative_to_viewport(&mut self, layer: &[LayerId], transform: DAffine2) -> Result<(), DocumentError> {
|
||||
self.transform_relative_to_scope(layer, None, transform)
|
||||
}
|
||||
@@ -297,33 +300,79 @@ impl Document {
|
||||
|
||||
let responses = match &operation {
|
||||
Operation::AddEllipse { path, insert_index, transform, style } => {
|
||||
self.set_layer(path, Layer::new(LayerDataType::Shape(Shape::ellipse(*style)), *transform), *insert_index)?;
|
||||
let layer = Layer::new(LayerDataType::Shape(Shape::ellipse(*style)), *transform);
|
||||
|
||||
self.set_layer(path, layer, *insert_index)?;
|
||||
|
||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::CreatedLayer { path: path.clone() }])
|
||||
}
|
||||
Operation::AddOverlayEllipse { path, transform, style } => {
|
||||
let mut ellipse = Shape::ellipse(*style);
|
||||
ellipse.render_index = -1;
|
||||
|
||||
let mut layer = Layer::new(LayerDataType::Shape(ellipse), *transform);
|
||||
layer.overlay = true;
|
||||
|
||||
self.set_layer(path, layer, -1)?;
|
||||
|
||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::CreatedLayer { path: path.clone() }])
|
||||
}
|
||||
Operation::AddRect { path, insert_index, transform, style } => {
|
||||
self.set_layer(path, Layer::new(LayerDataType::Shape(Shape::rectangle(*style)), *transform), *insert_index)?;
|
||||
let layer = Layer::new(LayerDataType::Shape(Shape::rectangle(*style)), *transform);
|
||||
|
||||
self.set_layer(path, layer, *insert_index)?;
|
||||
|
||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::CreatedLayer { path: path.clone() }])
|
||||
}
|
||||
Operation::AddBoundingBox { path, transform, style } => {
|
||||
Operation::AddOverlayRect { path, transform, style } => {
|
||||
let mut rect = Shape::rectangle(*style);
|
||||
rect.render_index = -1;
|
||||
|
||||
let mut layer = Layer::new(LayerDataType::Shape(rect), *transform);
|
||||
layer.overlay = true;
|
||||
|
||||
self.set_layer(path, layer, -1)?;
|
||||
|
||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::CreatedLayer { path: path.clone() }])
|
||||
}
|
||||
Operation::AddShape {
|
||||
Operation::AddLine { path, insert_index, transform, style } => {
|
||||
let layer = Layer::new(LayerDataType::Shape(Shape::line(*style)), *transform);
|
||||
|
||||
self.set_layer(path, layer, *insert_index)?;
|
||||
|
||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::CreatedLayer { path: path.clone() }])
|
||||
}
|
||||
Operation::AddOverlayLine { path, transform, style } => {
|
||||
let mut line = Shape::line(*style);
|
||||
line.render_index = -1;
|
||||
|
||||
let mut layer = Layer::new(LayerDataType::Shape(line), *transform);
|
||||
layer.overlay = true;
|
||||
|
||||
self.set_layer(path, layer, -1)?;
|
||||
|
||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::CreatedLayer { path: path.clone() }])
|
||||
}
|
||||
Operation::AddNgon {
|
||||
path,
|
||||
insert_index,
|
||||
transform,
|
||||
style,
|
||||
sides,
|
||||
} => {
|
||||
self.set_layer(path, Layer::new(LayerDataType::Shape(Shape::shape(*sides, *style)), *transform), *insert_index)?;
|
||||
self.set_layer(path, Layer::new(LayerDataType::Shape(Shape::ngon(*sides, *style)), *transform), *insert_index)?;
|
||||
|
||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::CreatedLayer { path: path.clone() }])
|
||||
}
|
||||
Operation::AddLine { path, insert_index, transform, style } => {
|
||||
self.set_layer(path, Layer::new(LayerDataType::Shape(Shape::line(*style)), *transform), *insert_index)?;
|
||||
Operation::AddOverlayShape { path, style, bez_path } => {
|
||||
let mut shape = Shape::from_bez_path(bez_path.clone(), *style, false);
|
||||
shape.render_index = -1;
|
||||
|
||||
let mut layer = Layer::new(LayerDataType::Shape(shape), DAffine2::IDENTITY.to_cols_array());
|
||||
layer.overlay = true;
|
||||
|
||||
self.set_layer(path, layer, -1)?;
|
||||
|
||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::CreatedLayer { path: path.clone() }])
|
||||
}
|
||||
Operation::AddPen {
|
||||
@@ -395,6 +444,19 @@ impl Document {
|
||||
self.mark_as_dirty(path)?;
|
||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::LayerChanged { path: path.clone() }])
|
||||
}
|
||||
Operation::SetShapePathInViewport { path, bez_path, transform } => {
|
||||
let transform = DAffine2::from_cols_array(transform);
|
||||
self.set_transform_relative_to_viewport(path, transform)?;
|
||||
self.mark_as_dirty(path)?;
|
||||
|
||||
match &mut self.layer_mut(path)?.data {
|
||||
LayerDataType::Shape(shape) => {
|
||||
shape.path = bez_path.clone();
|
||||
}
|
||||
LayerDataType::Folder(_) => (),
|
||||
}
|
||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::LayerChanged { path: path.clone() }])
|
||||
}
|
||||
Operation::TransformLayerInScope { path, transform, scope } => {
|
||||
let transform = DAffine2::from_cols_array(transform);
|
||||
let scope = DAffine2::from_cols_array(scope);
|
||||
@@ -416,11 +478,16 @@ impl Document {
|
||||
self.mark_as_dirty(path)?;
|
||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::LayerChanged { path: path.clone() }])
|
||||
}
|
||||
Operation::ToggleVisibility { path } => {
|
||||
Operation::ToggleLayerVisibility { path } => {
|
||||
self.mark_as_dirty(path)?;
|
||||
if let Ok(layer) = self.layer_mut(path) {
|
||||
layer.visible = !layer.visible;
|
||||
}
|
||||
let layer = self.layer_mut(path)?;
|
||||
layer.visible = !layer.visible;
|
||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::LayerChanged { path: path.clone() }])
|
||||
}
|
||||
Operation::SetLayerVisibility { path, visible } => {
|
||||
self.mark_as_dirty(path)?;
|
||||
let layer = self.layer_mut(path)?;
|
||||
layer.visible = *visible;
|
||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::LayerChanged { path: path.clone() }])
|
||||
}
|
||||
Operation::SetLayerBlendMode { path, blend_mode } => {
|
||||
@@ -435,7 +502,16 @@ impl Document {
|
||||
|
||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::LayerChanged { path: path.clone() }])
|
||||
}
|
||||
Operation::FillLayer { path, color } => {
|
||||
Operation::SetLayerStyle { path, style } => {
|
||||
let layer = self.layer_mut(path)?;
|
||||
match &mut layer.data {
|
||||
LayerDataType::Shape(s) => s.style = *style,
|
||||
_ => return Err(DocumentError::NotAShape),
|
||||
}
|
||||
self.mark_as_dirty(path)?;
|
||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::LayerChanged { path: path.clone() }])
|
||||
}
|
||||
Operation::SetLayerFill { path, color } => {
|
||||
let layer = self.layer_mut(path)?;
|
||||
match &mut layer.data {
|
||||
LayerDataType::Shape(s) => s.style.set_fill(layers::style::Fill::new(*color)),
|
||||
|
||||
@@ -77,7 +77,16 @@ impl Shape {
|
||||
transforms.iter().skip(start).cloned().reduce(|a, b| a * b).unwrap_or(DAffine2::IDENTITY)
|
||||
}
|
||||
|
||||
pub fn shape(sides: u8, style: PathStyle) -> Self {
|
||||
pub fn from_bez_path(bez_path: BezPath, style: PathStyle, solid: bool) -> Self {
|
||||
Self {
|
||||
path: bez_path,
|
||||
style,
|
||||
render_index: 1,
|
||||
solid: solid,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ngon(sides: u8, style: PathStyle) -> Self {
|
||||
use std::f64::consts::{FRAC_PI_2, TAU};
|
||||
fn unit_rotation(theta: f64) -> DVec2 {
|
||||
DVec2::new(theta.sin(), theta.cos())
|
||||
|
||||
@@ -20,13 +20,18 @@ pub enum Operation {
|
||||
transform: [f64; 6],
|
||||
style: style::PathStyle,
|
||||
},
|
||||
AddOverlayEllipse {
|
||||
path: Vec<LayerId>,
|
||||
transform: [f64; 6],
|
||||
style: style::PathStyle,
|
||||
},
|
||||
AddRect {
|
||||
path: Vec<LayerId>,
|
||||
insert_index: isize,
|
||||
transform: [f64; 6],
|
||||
style: style::PathStyle,
|
||||
},
|
||||
AddBoundingBox {
|
||||
AddOverlayRect {
|
||||
path: Vec<LayerId>,
|
||||
transform: [f64; 6],
|
||||
style: style::PathStyle,
|
||||
@@ -37,6 +42,11 @@ pub enum Operation {
|
||||
transform: [f64; 6],
|
||||
style: style::PathStyle,
|
||||
},
|
||||
AddOverlayLine {
|
||||
path: Vec<LayerId>,
|
||||
transform: [f64; 6],
|
||||
style: style::PathStyle,
|
||||
},
|
||||
AddPen {
|
||||
path: Vec<LayerId>,
|
||||
transform: [f64; 6],
|
||||
@@ -44,13 +54,18 @@ pub enum Operation {
|
||||
points: Vec<(f64, f64)>,
|
||||
style: style::PathStyle,
|
||||
},
|
||||
AddShape {
|
||||
AddNgon {
|
||||
path: Vec<LayerId>,
|
||||
insert_index: isize,
|
||||
transform: [f64; 6],
|
||||
sides: u8,
|
||||
style: style::PathStyle,
|
||||
},
|
||||
AddOverlayShape {
|
||||
path: Vec<LayerId>,
|
||||
bez_path: kurbo::BezPath,
|
||||
style: style::PathStyle,
|
||||
},
|
||||
DeleteLayer {
|
||||
path: Vec<LayerId>,
|
||||
},
|
||||
@@ -81,6 +96,11 @@ pub enum Operation {
|
||||
path: Vec<LayerId>,
|
||||
transform: [f64; 6],
|
||||
},
|
||||
SetShapePathInViewport {
|
||||
path: Vec<LayerId>,
|
||||
bez_path: kurbo::BezPath,
|
||||
transform: [f64; 6],
|
||||
},
|
||||
TransformLayerInScope {
|
||||
path: Vec<LayerId>,
|
||||
transform: [f64; 6],
|
||||
@@ -95,9 +115,13 @@ pub enum Operation {
|
||||
path: Vec<LayerId>,
|
||||
transform: [f64; 6],
|
||||
},
|
||||
ToggleVisibility {
|
||||
ToggleLayerVisibility {
|
||||
path: Vec<LayerId>,
|
||||
},
|
||||
SetLayerVisibility {
|
||||
path: Vec<LayerId>,
|
||||
visible: bool,
|
||||
},
|
||||
SetLayerBlendMode {
|
||||
path: Vec<LayerId>,
|
||||
blend_mode: BlendMode,
|
||||
@@ -106,7 +130,11 @@ pub enum Operation {
|
||||
path: Vec<LayerId>,
|
||||
opacity: f64,
|
||||
},
|
||||
FillLayer {
|
||||
SetLayerStyle {
|
||||
path: Vec<LayerId>,
|
||||
style: style::PathStyle,
|
||||
},
|
||||
SetLayerFill {
|
||||
path: Vec<LayerId>,
|
||||
color: Color,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user