mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-16 23:08:05 +08:00
* Dissolve Points from path * Add handling for removing the first anchor * Add function to turn handles into bez_paths * Created overlay manager, wip * WIP Refactor of VectorShape / Overlays / ShapeEditor * WIP stripping vector shape, anchor, point. * WIP Removed kurbo deps from vector shape, anchor, point * WIP Further work to make vector shapes / anchors / points more standalone. * WIP more pruning * WIP Progress on overlay_renderer * WIP more overlay_renderer work * WIP more pruning, cleared warnings * WIP decided ShapeRenderer wasn't an accurate name, ShapeAdapter now. Error squashing continues. * WIP squashed more errors, now need to decide if anchors should have unique IDs * WIP Errors squashed, now to actually make it work. * WIP Moved vector structs to graphene, beginning to remove bezpath from shape_layer * Refactoring: disentangle kurbo from apply_affine * Refactor internal shape and remove reliance on Kurbo (PR #617) - Disentangle Kurbo (#619) * Refactoring: disentangle kurbo from apply_affine * Broke boolean operations, refactor in state which compiles * "fixed" boolean operation refactor related errors * fixed apply_affine, which would not have applied any type of affine * Small Cleanup, readability * Fix issue with overlay styles no longer showing selection state. * Resolved error with point option * WIP, figuring out how to have one source of truth for VectorShape. Trying to avoid cloning. * WIP work on single source of truth vectorshapes * More steps toward single source of truth VectorShape * Continued wip on making VectorShapes mutably accessible without cloning * Wip using paths to reference vectorshapes instead, need to restructure ShapeEditor * Decided to allow temporary copies of vectorshapes. * Removed HashSet for selected shape indices * Added @TrueDoctor's id_storage.rs with some heavy modification. Added it to VectorShape. Isn't yet used for folders. * Integrated UniqueElements<T> with VectorShape to store VectorAnchors * Improved storage_id.rs perf and cleaned up it's interface * Iterator Implementations and fixes (#637) * Refactoring: disentangle kurbo from apply_affine * Broke boolean operations, refactor in state which compiles * "fixed" boolean operation refactor related errors * fixed apply_affine, which would not have applied any type of affine * implemented transforms for VectorAnchors implemented Not for VectorControlPointType * started adding Vector Shape implementations of shape prototypes * added several useful implemtations to UniqueElements * added another implemnation for UniqueElements to make working with iterators easier, fixed vector-shape errors * package-lock.json * clean up rebase, added back Layer paths * added deref implementation for VectorShape * unnecesary variable * simplify code by removing levels of indirection * fixed errors * merge cleanup * removed package-lock.json * Removed .selected from VectorShape, it isn't needed as layers are selected not shapes specifically. * Removed transform and layer_path from VectorShape * Auto-saving tentitively working. Work toward Overlay transform issues. * Overlays properly hiding and caching. Not clearing cache yet and some tool switching issues remain, but progress. * Putting layers in folders changes their unique ID. This is problematic. Assumed this was not the case. * Removed need for closed bool, changed VectorShape to a tuple struct. * WIP Switched to layer paths as opposed to VectorShapes. Next up add messages for changing VectorShapes. * Added initial messages to edit VectorShape points. * DeleteSelectedPoints messages implemented, selection isn't working currently though. * Selection messages arriving in document, but transform is wrong. * Selection, Deselection working, delete working for first point. * Working towards moving points again * Removed extra vec from UniqueElements, attempting to squash ordering bug. Still appears to occur though. * Delete more stable, clean up, renamed to HandleIn, HandleOut * Further vec_unique cleanup * Further cleanup * Removed Deref / DerefMut from VectorShape * Document version++, will likely revert before merge into master * Seleting / deleting handles tentitively working again. * Version number bump, fixed tests. * Fixed comment in VecUnique * Improved VecUnique descriptor comment * Renamed VecUnique to IdBackedVec to further clarify usage. * Resolved formatting. * WIP Fixing dragging points * Fixed an instance where an OverlayMessage could be sent to the main document incorrectly. * Deleting all of a shapes points now gracefully deletes the layer instead of crashing. * Fixed handle configurations that would panic on deletion * Single anchor dragging restored with multi-dragging next plus handles * sides.into() * Handle and Multi-point dragging working * WIP Handle symmetry working again * Handle mirroring functional again. * Cleaned up warnings * Fixed overlay outline not matching shape * Git branch fix of compatibility with new master * Fixed closed shape bug, replaced kurbo ellipse * Removed unused func, updated comments * Deleting points can undo, multiple shape selection deletes now working * Removed AddOverlay* operations * Partial fix for select drift, added helpers * Don't snap against dragging points * Properly cleanup path outline with multiple shapes * Clear all points in other selected shapes * Actually don't snap against dragging points * Fix path tool & add snap angle and break handle * Fix handle being set to NaN causing render issues * Fix cached overlays not showing line -> curve * Add operations for modifying paths * Remove kurbo from pen tool * Do not snap against handles when anchor selected * Fix overlays not being cleaned up on path tool * Fix handle position after dragging * Use `Anchor` for text & no kurbo in operations * Replace kurbo to_svg function * Ngon no longer center scales by default, still some weird behaviour when holding alt * Cleanup overlays * Fix render and bounding box doctests * Fix fun to_svg error * Fix compile error * Some code review * Remove legacy `SelectPoint` on doubleclick * Remove font from test document * Fix the pen tool selection changed * Reorder imports Co-authored-by: Dennis <dennis@kobert.dev> Co-authored-by: Caleb Dennis <caleb.dennis429@gmail.com> Co-authored-by: caleb <56044292+caleb-ad@users.noreply.github.com> Co-authored-by: Keavon Chambers <keavon@keavon.com> Co-authored-by: 0hypercube <0hypercube@gmail.com> Co-authored-by: 0HyperCube <78500760+0HyperCube@users.noreply.github.com>
177 lines
5.7 KiB
Rust
177 lines
5.7 KiB
Rust
use super::layer_info::LayerData;
|
|
use super::style::{self, PathStyle, RenderData, ViewMode};
|
|
use super::vector::vector_shape::VectorShape;
|
|
use crate::intersection::{intersect_quad_bez_path, Quad};
|
|
use crate::layers::text_layer::FontCache;
|
|
use crate::LayerId;
|
|
|
|
use glam::{DAffine2, DMat2, DVec2};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::fmt::Write;
|
|
|
|
/// A generic SVG element defined using Bezier paths.
|
|
/// Shapes are rendered as
|
|
/// [`<path>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path)
|
|
/// elements inside a
|
|
/// [`<g>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g)
|
|
/// group that the transformation matrix is applied to.
|
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
|
pub struct ShapeLayer {
|
|
/// The geometry of the layer.
|
|
pub shape: VectorShape,
|
|
/// The visual style of the shape.
|
|
pub style: style::PathStyle,
|
|
// TODO: We might be able to remove this in a future refactor
|
|
pub render_index: i32,
|
|
}
|
|
|
|
impl LayerData for ShapeLayer {
|
|
fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec<DAffine2>, render_data: RenderData) {
|
|
let mut vector_shape = self.shape.clone();
|
|
|
|
let kurbo::Rect { x0, y0, x1, y1 } = vector_shape.bounding_box();
|
|
let layer_bounds = [(x0, y0).into(), (x1, y1).into()];
|
|
|
|
let transform = self.transform(transforms, render_data.view_mode);
|
|
let inverse = transform.inverse();
|
|
if !inverse.is_finite() {
|
|
let _ = write!(svg, "<!-- SVG shape has an invalid transform -->");
|
|
return;
|
|
}
|
|
vector_shape.apply_affine(transform);
|
|
|
|
let kurbo::Rect { x0, y0, x1, y1 } = vector_shape.bounding_box();
|
|
let transformed_bounds = [(x0, y0).into(), (x1, y1).into()];
|
|
|
|
let _ = writeln!(svg, r#"<g transform="matrix("#);
|
|
inverse.to_cols_array().iter().enumerate().for_each(|(i, entry)| {
|
|
let _ = svg.write_str(&(entry.to_string() + if i == 5 { "" } else { "," }));
|
|
});
|
|
let _ = svg.write_str(r#")">"#);
|
|
let _ = write!(
|
|
svg,
|
|
r#"<path d="{}" {} />"#,
|
|
vector_shape.to_svg(),
|
|
self.style.render(render_data.view_mode, svg_defs, transform, layer_bounds, transformed_bounds)
|
|
);
|
|
let _ = svg.write_str("</g>");
|
|
}
|
|
|
|
fn bounding_box(&self, transform: glam::DAffine2, _font_cache: &FontCache) -> Option<[DVec2; 2]> {
|
|
let mut vector_shape = self.shape.clone();
|
|
if transform.matrix2 == DMat2::ZERO {
|
|
return None;
|
|
}
|
|
vector_shape.apply_affine(transform);
|
|
|
|
let kurbo::Rect { x0, y0, x1, y1 } = vector_shape.bounding_box();
|
|
Some([(x0, y0).into(), (x1, y1).into()])
|
|
}
|
|
|
|
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, _font_cache: &FontCache) {
|
|
let filled = self.style.fill().is_some() || self.shape.anchors().last().filter(|anchor| anchor.is_close()).is_some();
|
|
if intersect_quad_bez_path(quad, &(&self.shape).into(), filled) {
|
|
intersections.push(path.clone());
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ShapeLayer {
|
|
/// Construct a new [ShapeLayer] with the specified [VectorShape] and [PathStyle]
|
|
pub fn new(shape: VectorShape, style: PathStyle) -> Self {
|
|
Self { shape, style, render_index: 1 }
|
|
}
|
|
|
|
pub fn transform(&self, transforms: &[DAffine2], mode: ViewMode) -> DAffine2 {
|
|
let start = match (mode, self.render_index) {
|
|
(ViewMode::Outline, _) => 0,
|
|
(_, -1) => 0,
|
|
(_, x) => (transforms.len() as i32 - x).max(0) as usize,
|
|
};
|
|
transforms.iter().skip(start).fold(DAffine2::IDENTITY, |a, b| a * *b)
|
|
}
|
|
|
|
/// TODO The behavior of ngon changed from the previous iteration slightly, match original behavior
|
|
/// Create an N-gon.
|
|
///
|
|
/// # Panics
|
|
/// This function panics if `sides` is zero.
|
|
pub fn ngon(sides: u32, style: PathStyle) -> Self {
|
|
use std::f64::consts::{FRAC_PI_2, TAU};
|
|
|
|
fn unit_rotation(theta: f64) -> DVec2 {
|
|
DVec2::new(theta.sin(), theta.cos())
|
|
}
|
|
|
|
let mut path = kurbo::BezPath::new();
|
|
|
|
let apothem_offset_angle = TAU / (sides as f64);
|
|
// Rotate odd sided shapes by 90 degrees
|
|
let offset = ((sides + 1) % 2) as f64 * FRAC_PI_2;
|
|
|
|
let relative_points = (0..sides).map(|i| apothem_offset_angle * i as f64 + offset).map(unit_rotation);
|
|
|
|
let min = relative_points.clone().reduce(|a, b| a.min(b)).unwrap_or_default();
|
|
let transform = DAffine2::from_scale_angle_translation(DVec2::ONE / 2., 0., -min / 2.);
|
|
let point = |vec: DVec2| kurbo::Point::new(vec.x, vec.y);
|
|
|
|
let mut relative_points = relative_points.map(|p| point(transform.transform_point2(p)));
|
|
path.move_to(relative_points.next().expect("Tried to create an ngon with 0 sides"));
|
|
relative_points.for_each(|p| path.line_to(p));
|
|
|
|
path.close_path();
|
|
|
|
Self {
|
|
shape: VectorShape::new_ngon(DVec2::new(0., 0.), sides.into(), 1.),
|
|
style,
|
|
render_index: 1,
|
|
}
|
|
}
|
|
|
|
/// Create a rectangular shape.
|
|
pub fn rectangle(style: PathStyle) -> Self {
|
|
Self {
|
|
shape: VectorShape::new_rect(DVec2::new(0., 0.), DVec2::new(1., 1.)),
|
|
style,
|
|
render_index: 1,
|
|
}
|
|
}
|
|
|
|
/// Create an elliptical shape.
|
|
pub fn ellipse(style: PathStyle) -> Self {
|
|
Self {
|
|
shape: VectorShape::new_ellipse(DVec2::new(0., 0.), DVec2::new(1., 1.)),
|
|
style,
|
|
render_index: 1,
|
|
}
|
|
}
|
|
|
|
/// Create a straight line from (0, 0) to (1, 0).
|
|
pub fn line(style: PathStyle) -> Self {
|
|
Self {
|
|
shape: VectorShape::new_line(DVec2::new(0., 0.), DVec2::new(1., 0.)),
|
|
style,
|
|
render_index: 1,
|
|
}
|
|
}
|
|
|
|
/// Create a polygonal line that visits each provided point.
|
|
pub fn poly_line(points: Vec<impl Into<glam::DVec2>>, style: PathStyle) -> Self {
|
|
Self {
|
|
shape: VectorShape::new_poly_line(points),
|
|
style,
|
|
render_index: 0,
|
|
}
|
|
}
|
|
|
|
/// Creates a smooth bezier spline that passes through all given points.
|
|
/// The algorithm used in this implementation is described here: <https://www.particleincell.com/2012/bezier-splines/>
|
|
pub fn spline(points: Vec<impl Into<glam::DVec2>>, style: PathStyle) -> Self {
|
|
Self {
|
|
shape: VectorShape::new_spline(points),
|
|
style,
|
|
render_index: 0,
|
|
}
|
|
}
|
|
}
|