mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-24 16:13:44 +00:00
Initial work migrating vector layers to document graph
* Fix pen tool (except overlays) * Thumbnail of only the layer and not the composite * Fix occasional transform breakages * Constrain size of thumbnail * Insert new layers at the top * Broken layer tree * Fix crash when drawing * Reduce calls to send graph * Reduce calls to updating properties * Store cached transforms upon the document * Fix missing node UI updates * Fix fill tool and clean up imports and indentation * Error on overide existing layer * Fix pen tool (partially) * Fix some lints
This commit is contained in:
parent
fc6cee372a
commit
4cd72edb64
50 changed files with 3585 additions and 3053 deletions
|
@ -97,6 +97,13 @@ fn construct_layer<Data: Into<GraphicElementData>>(
|
|||
stack
|
||||
}
|
||||
|
||||
pub struct ToGraphicElementData {}
|
||||
|
||||
#[node_fn(ToGraphicElementData)]
|
||||
fn to_graphic_element_data<Data: Into<GraphicElementData>>(graphic_element_data: Data) -> GraphicElementData {
|
||||
graphic_element_data.into()
|
||||
}
|
||||
|
||||
pub struct ConstructArtboardNode<Location, Dimensions, Background, Clip> {
|
||||
location: Location,
|
||||
dimensions: Dimensions,
|
||||
|
|
|
@ -1,11 +1,56 @@
|
|||
use crate::raster::{Image, ImageFrame};
|
||||
use crate::{uuid::generate_uuid, vector::VectorData, Artboard, Color, GraphicElementData, GraphicGroup};
|
||||
use quad::Quad;
|
||||
use crate::uuid::{generate_uuid, ManipulatorGroupId};
|
||||
use crate::{vector::VectorData, Artboard, Color, GraphicElementData, GraphicGroup};
|
||||
use bezier_rs::Subpath;
|
||||
pub use quad::Quad;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
mod quad;
|
||||
|
||||
/// Represents a clickable target for the layer
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ClickTarget {
|
||||
pub subpath: bezier_rs::Subpath<ManipulatorGroupId>,
|
||||
pub stroke_width: f64,
|
||||
}
|
||||
|
||||
impl ClickTarget {
|
||||
/// Does the click target intersect the rectangle
|
||||
pub fn intersect_rectangle(&self, document_quad: Quad, layer_transform: DAffine2) -> bool {
|
||||
let quad = layer_transform.inverse() * document_quad;
|
||||
|
||||
// Check if outlines intersect
|
||||
if self
|
||||
.subpath
|
||||
.iter()
|
||||
.any(|path_segment| quad.bezier_lines().any(|line| !path_segment.intersections(&line, None, None).is_empty()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// Check if selection is entirely within the shape
|
||||
if self.subpath.closed() && self.subpath.contains_point(quad.center()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if shape is entirely within selection
|
||||
self.subpath
|
||||
.manipulator_groups()
|
||||
.first()
|
||||
.map(|group| group.anchor)
|
||||
.map(|shape_point| quad.contains(shape_point))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Does the click target intersect the point (accounting for stroke size)
|
||||
pub fn intersect_point(&self, point: DVec2, layer_transform: DAffine2) -> bool {
|
||||
// Allows for selecting lines
|
||||
// TODO: actual intersection of stroke
|
||||
let inflated_quad = Quad::from_box([point - DVec2::splat(self.stroke_width / 2.), point + DVec2::splat(self.stroke_width / 2.)]);
|
||||
self.intersect_rectangle(inflated_quad, layer_transform)
|
||||
}
|
||||
}
|
||||
|
||||
/// Mutable state used whilst rendering to an SVG
|
||||
pub struct SvgRender {
|
||||
pub svg: SvgSegmentList,
|
||||
|
@ -109,6 +154,7 @@ pub fn format_transform_matrix(transform: DAffine2) -> String {
|
|||
pub trait GraphicElementRendered {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams);
|
||||
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]>;
|
||||
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>);
|
||||
}
|
||||
|
||||
impl GraphicElementRendered for GraphicGroup {
|
||||
|
@ -118,6 +164,7 @@ impl GraphicElementRendered for GraphicGroup {
|
|||
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.iter().filter_map(|element| element.graphic_element_data.bounding_box(transform)).reduce(Quad::combine_bounds)
|
||||
}
|
||||
fn add_click_targets(&self, _click_targets: &mut Vec<ClickTarget>) {}
|
||||
}
|
||||
|
||||
impl GraphicElementRendered for VectorData {
|
||||
|
@ -140,6 +187,14 @@ impl GraphicElementRendered for VectorData {
|
|||
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.bounding_box_with_transform(self.transform * transform)
|
||||
}
|
||||
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
let stroke_width = self.style.stroke().as_ref().map_or(0., crate::vector::style::Stroke::weight);
|
||||
let update_closed = |mut subpath: bezier_rs::Subpath<ManipulatorGroupId>| {
|
||||
subpath.set_closed(self.style.fill().is_some());
|
||||
subpath
|
||||
};
|
||||
click_targets.extend(self.subpaths.iter().cloned().map(update_closed).map(|subpath| ClickTarget { stroke_width, subpath }))
|
||||
}
|
||||
}
|
||||
|
||||
impl GraphicElementRendered for Artboard {
|
||||
|
@ -195,7 +250,15 @@ impl GraphicElementRendered for Artboard {
|
|||
}
|
||||
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
let artboard_bounds = (transform * Quad::from_box([self.location.as_dvec2(), self.location.as_dvec2() + self.dimensions.as_dvec2()])).bounding_box();
|
||||
[self.graphic_group.bounding_box(transform), Some(artboard_bounds)].into_iter().flatten().reduce(Quad::combine_bounds)
|
||||
if self.clip {
|
||||
Some(artboard_bounds)
|
||||
} else {
|
||||
[self.graphic_group.bounding_box(transform), Some(artboard_bounds)].into_iter().flatten().reduce(Quad::combine_bounds)
|
||||
}
|
||||
}
|
||||
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
let subpath = Subpath::new_rect(self.location.as_dvec2(), self.location.as_dvec2() + self.dimensions.as_dvec2());
|
||||
click_targets.push(ClickTarget { stroke_width: 0., subpath });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -216,6 +279,10 @@ impl GraphicElementRendered for ImageFrame<Color> {
|
|||
let transform = self.transform * transform;
|
||||
(transform.matrix2 != glam::DMat2::ZERO).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box())
|
||||
}
|
||||
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
|
||||
click_targets.push(ClickTarget { subpath, stroke_width: 0. });
|
||||
}
|
||||
}
|
||||
|
||||
impl GraphicElementRendered for GraphicElementData {
|
||||
|
@ -238,6 +305,16 @@ impl GraphicElementRendered for GraphicElementData {
|
|||
GraphicElementData::Artboard(artboard) => artboard.bounding_box(transform),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
match self {
|
||||
GraphicElementData::VectorShape(vector_data) => vector_data.add_click_targets(click_targets),
|
||||
GraphicElementData::ImageFrame(image_frame) => image_frame.add_click_targets(click_targets),
|
||||
GraphicElementData::Text(_) => todo!("click target for text GraphicElementData"),
|
||||
GraphicElementData::GraphicGroup(graphic_group) => graphic_group.add_click_targets(click_targets),
|
||||
GraphicElementData::Artboard(artboard) => artboard.add_click_targets(click_targets),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A segment of an svg string to allow for embedding blob urls
|
||||
|
|
|
@ -5,6 +5,11 @@ use glam::{DAffine2, DVec2};
|
|||
pub struct Quad([DVec2; 4]);
|
||||
|
||||
impl Quad {
|
||||
/// Create a zero sized quad at the point
|
||||
pub fn from_point(point: DVec2) -> Self {
|
||||
Self([point; 4])
|
||||
}
|
||||
|
||||
/// Convert a box defined by two corner points to a quad.
|
||||
pub fn from_box(bbox: [DVec2; 2]) -> Self {
|
||||
let size = bbox[1] - bbox[0];
|
||||
|
@ -12,7 +17,7 @@ impl Quad {
|
|||
}
|
||||
|
||||
/// Get all the edges in the quad.
|
||||
pub fn lines_glam(&self) -> impl Iterator<Item = bezier_rs::Bezier> + '_ {
|
||||
pub fn bezier_lines(&self) -> impl Iterator<Item = bezier_rs::Bezier> + '_ {
|
||||
[[self.0[0], self.0[1]], [self.0[1], self.0[2]], [self.0[2], self.0[3]], [self.0[3], self.0[0]]]
|
||||
.into_iter()
|
||||
.map(|[start, end]| bezier_rs::Bezier::from_linear_dvec2(start, end))
|
||||
|
@ -40,6 +45,33 @@ impl Quad {
|
|||
pub fn combine_bounds(a: [DVec2; 2], b: [DVec2; 2]) -> [DVec2; 2] {
|
||||
[a[0].min(b[0]), a[1].max(b[1])]
|
||||
}
|
||||
|
||||
/// Expand a quad by a certain amount on all sides.
|
||||
///
|
||||
/// Not currently very optimised
|
||||
pub fn inflate(&self, offset: f64) -> Quad {
|
||||
let offset = |index_before, index, index_after| {
|
||||
let [point_before, point, point_after]: [DVec2; 3] = [self.0[index_before], self.0[index], self.0[index_after]];
|
||||
let [line_in, line_out] = [point - point_before, point_after - point];
|
||||
let angle = line_in.angle_between(-line_out);
|
||||
let offset_length = offset / (std::f64::consts::FRAC_PI_2 - angle / 2.).cos();
|
||||
point + (line_in.perp().normalize_or_zero() + line_out.perp().normalize_or_zero()).normalize_or_zero() * offset_length
|
||||
};
|
||||
Self([offset(3, 0, 1), offset(0, 1, 2), offset(1, 2, 3), offset(2, 3, 0)])
|
||||
}
|
||||
|
||||
/// Does this quad contain a point
|
||||
///
|
||||
/// Code from https://wrfranklin.org/Research/Short_Notes/pnpoly.html
|
||||
pub fn contains(&self, p: DVec2) -> bool {
|
||||
let mut inside = false;
|
||||
for (i, j) in (0..4).zip([3, 0, 1, 2]) {
|
||||
if (self.0[i].y > p.y) != (self.0[j].y > p.y) && p.x < (self.0[j].x - self.0[i].x * (p.y - self.0[i].y) / (self.0[j].y - self.0[i].y) + self.0[i].x) {
|
||||
inside = !inside;
|
||||
}
|
||||
}
|
||||
inside
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Mul<Quad> for DAffine2 {
|
||||
|
@ -49,3 +81,26 @@ impl core::ops::Mul<Quad> for DAffine2 {
|
|||
Quad(rhs.0.map(|point| self.transform_point2(point)))
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn offset_quad() {
|
||||
fn eq(a: Quad, b: Quad) -> bool {
|
||||
a.0.iter().zip(b.0).all(|(a, b)| a.abs_diff_eq(b, 0.0001))
|
||||
}
|
||||
|
||||
assert!(eq(Quad::from_box([DVec2::ZERO, DVec2::ONE]).inflate(0.5), Quad::from_box([DVec2::splat(-0.5), DVec2::splat(1.5)])));
|
||||
assert!(eq(Quad::from_box([DVec2::ONE, DVec2::ZERO]).inflate(0.5), Quad::from_box([DVec2::splat(1.5), DVec2::splat(-0.5)])));
|
||||
assert!(eq(
|
||||
(DAffine2::from_scale(DVec2::new(-1., 1.)) * Quad::from_box([DVec2::ZERO, DVec2::ONE])).inflate(0.5),
|
||||
DAffine2::from_scale(DVec2::new(-1., 1.)) * Quad::from_box([DVec2::splat(-0.5), DVec2::splat(1.5)])
|
||||
));
|
||||
}
|
||||
#[test]
|
||||
fn quad_contains() {
|
||||
assert!(Quad::from_box([DVec2::ZERO, DVec2::ONE]).contains(DVec2::splat(0.5)));
|
||||
assert!(Quad::from_box([DVec2::ONE, DVec2::ZERO]).contains(DVec2::splat(0.5)));
|
||||
assert!((DAffine2::from_scale(DVec2::new(-1., 1.)) * Quad::from_box([DVec2::ZERO, DVec2::ONE])).contains(DVec2::new(-0.5, 0.5)));
|
||||
|
||||
assert!(!Quad::from_box([DVec2::ZERO, DVec2::ONE]).contains(DVec2::new(1., 1.1)));
|
||||
assert!(!Quad::from_box([DVec2::ONE, DVec2::ZERO]).contains(DVec2::new(0.5, -0.01)));
|
||||
assert!(!(DAffine2::from_scale(DVec2::new(-1., 1.)) * Quad::from_box([DVec2::ZERO, DVec2::ONE])).contains(DVec2::splat(0.5)));
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ use glam::DVec2;
|
|||
use crate::raster::ImageFrame;
|
||||
use crate::raster::Pixel;
|
||||
use crate::vector::VectorData;
|
||||
use crate::GraphicElementData;
|
||||
use crate::Node;
|
||||
|
||||
pub trait Transform {
|
||||
|
@ -42,6 +43,52 @@ impl<P: Pixel> TransformMut for ImageFrame<P> {
|
|||
&mut self.transform
|
||||
}
|
||||
}
|
||||
impl Transform for GraphicElementData {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
match self {
|
||||
GraphicElementData::VectorShape(vector_shape) => vector_shape.transform(),
|
||||
GraphicElementData::ImageFrame(image_frame) => image_frame.transform(),
|
||||
GraphicElementData::Text(_) => todo!("Transform of text"),
|
||||
GraphicElementData::GraphicGroup(_graphic_group) => DAffine2::IDENTITY,
|
||||
GraphicElementData::Artboard(_artboard) => DAffine2::IDENTITY,
|
||||
}
|
||||
}
|
||||
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
|
||||
match self {
|
||||
GraphicElementData::VectorShape(vector_shape) => vector_shape.local_pivot(pivot),
|
||||
GraphicElementData::ImageFrame(image_frame) => image_frame.local_pivot(pivot),
|
||||
GraphicElementData::Text(_) => todo!("Transform of text"),
|
||||
GraphicElementData::GraphicGroup(_graphic_group) => pivot,
|
||||
GraphicElementData::Artboard(_artboard) => pivot,
|
||||
}
|
||||
}
|
||||
fn decompose_scale(&self) -> DVec2 {
|
||||
let standard = || {
|
||||
DVec2::new(
|
||||
self.transform().transform_vector2((1., 0.).into()).length(),
|
||||
self.transform().transform_vector2((0., 1.).into()).length(),
|
||||
)
|
||||
};
|
||||
match self {
|
||||
GraphicElementData::VectorShape(vector_shape) => vector_shape.decompose_scale(),
|
||||
GraphicElementData::ImageFrame(image_frame) => image_frame.decompose_scale(),
|
||||
GraphicElementData::Text(_) => todo!("Transform of text"),
|
||||
GraphicElementData::GraphicGroup(_graphic_group) => standard(),
|
||||
GraphicElementData::Artboard(_artboard) => standard(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TransformMut for GraphicElementData {
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
match self {
|
||||
GraphicElementData::VectorShape(vector_shape) => vector_shape.transform_mut(),
|
||||
GraphicElementData::ImageFrame(image_frame) => image_frame.transform_mut(),
|
||||
GraphicElementData::Text(_) => todo!("Transform of text"),
|
||||
GraphicElementData::GraphicGroup(_graphic_group) => todo!("Mutable transform of graphic group"),
|
||||
GraphicElementData::Artboard(_artboard) => todo!("Mutable transform of artboard"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Transform for VectorData {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
|
|
|
@ -453,9 +453,7 @@ impl NodeNetwork {
|
|||
return true;
|
||||
}
|
||||
// Get the outputs
|
||||
let Some(mut stack) = self.outputs.iter().map(|&output| self.nodes.get(&output.node_id)).collect::<Option<Vec<_>>>() else {
|
||||
return false;
|
||||
};
|
||||
let mut stack = self.outputs.iter().filter_map(|&output| self.nodes.get(&output.node_id)).collect::<Vec<_>>();
|
||||
let mut already_visited = HashSet::new();
|
||||
already_visited.extend(self.outputs.iter().map(|output| output.node_id));
|
||||
|
||||
|
|
|
@ -328,7 +328,7 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
|
|||
#[cfg(feature = "quantization")]
|
||||
buffers: vec![width_uniform.clone(), storage_buffer.clone(), quantization_uniform.clone()],
|
||||
#[cfg(not(feature = "quantization"))]
|
||||
buffers: vec![width_uniform.clone(), storage_buffer.clone()],
|
||||
buffers: vec![width_uniform, storage_buffer],
|
||||
};
|
||||
|
||||
let shader = gpu_executor::Shader {
|
||||
|
@ -343,13 +343,13 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
|
|||
shader: shader.into(),
|
||||
entry_point: "eval".to_string(),
|
||||
bind_group: bind_group.into(),
|
||||
output_buffer: output_buffer.clone(),
|
||||
output_buffer,
|
||||
};
|
||||
log::debug!("created pipeline");
|
||||
|
||||
Ok(ComputePass {
|
||||
pipeline_layout: pipeline,
|
||||
readback_buffer: Some(readback_buffer.clone()),
|
||||
readback_buffer: Some(readback_buffer),
|
||||
})
|
||||
}
|
||||
/*
|
||||
|
|
|
@ -318,7 +318,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||
)],
|
||||
register_node!(graphene_std::raster::EmptyImageNode<_, _>, input: DAffine2, params: [Color]),
|
||||
register_node!(graphene_core::memo::MonitorNode<_>, input: ImageFrame<Color>, params: []),
|
||||
register_node!(graphene_core::memo::MonitorNode<_>, input: graphene_core::GraphicGroup, params: []),
|
||||
register_node!(graphene_core::memo::MonitorNode<_>, input: graphene_core::GraphicElementData, params: []),
|
||||
async_node!(graphene_std::wasm_application_io::LoadResourceNode<_>, input: WasmEditorApi, output: Arc<[u8]>, params: [String]),
|
||||
register_node!(graphene_std::wasm_application_io::DecodeImageNode, input: Arc<[u8]>, params: []),
|
||||
async_node!(graphene_std::wasm_application_io::CreateSurfaceNode, input: WasmEditorApi, output: Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>, params: []),
|
||||
|
@ -653,10 +653,11 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||
register_node!(graphene_core::text::TextGenerator<_, _, _>, input: WasmEditorApi, params: [String, graphene_core::text::Font, f64]),
|
||||
register_node!(graphene_std::brush::VectorPointsNode, input: VectorData, params: []),
|
||||
register_node!(graphene_core::ExtractImageFrame, input: WasmEditorApi, params: []),
|
||||
register_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>, input: graphene_core::vector::VectorData, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]),
|
||||
register_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>, input: ImageFrame<Color>, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]),
|
||||
register_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>, input: graphene_core::GraphicGroup, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]),
|
||||
register_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>, input: graphene_core::Artboard, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]),
|
||||
register_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>, input: graphene_core::GraphicElementData, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]),
|
||||
register_node!(graphene_core::ToGraphicElementData, input: graphene_core::vector::VectorData, params: []),
|
||||
register_node!(graphene_core::ToGraphicElementData, input: ImageFrame<Color>, params: []),
|
||||
register_node!(graphene_core::ToGraphicElementData, input: graphene_core::GraphicGroup, params: []),
|
||||
register_node!(graphene_core::ToGraphicElementData, input: graphene_core::Artboard, params: []),
|
||||
register_node!(graphene_core::ConstructArtboardNode<_, _, _, _>, input: graphene_core::GraphicGroup, params: [glam::IVec2, glam::IVec2, Color, bool]),
|
||||
];
|
||||
let mut map: HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> = HashMap::new();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue