From 9a7d7de8faea58e4674475f904896c293d87c770 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Tue, 19 Dec 2023 04:36:19 -0800 Subject: [PATCH] Remove most of document-legacy (#1519) * Remove boolean ops and unused doc-legacy Operations * Remove Shape legacy layers * Remove legacy layer Properties panel code * Remove additional unused doc-legacy Operations * Removed unused rendering-related legacy-layer code * Upgrade dep so CI builds * Remove various additional unused functions and messages * Remove the LayerData trait * Remove RenderData struct and usages * Banish the Operations system * Further removals --- Cargo.lock | 8 +- document-legacy/src/boolean_ops.rs | 811 ----------- document-legacy/src/consts.rs | 5 - document-legacy/src/document.rs | 868 +----------- document-legacy/src/document_metadata.rs | 24 +- document-legacy/src/error.rs | 13 - document-legacy/src/intersection.rs | 1216 ----------------- document-legacy/src/layers/base64_serde.rs | 23 - document-legacy/src/layers/folder_layer.rs | 233 +--- document-legacy/src/layers/layer_info.rs | 504 +------ document-legacy/src/layers/layer_layer.rs | 167 +-- document-legacy/src/layers/mod.rs | 13 - document-legacy/src/layers/render_data.rs | 22 - document-legacy/src/layers/shape_layer.rs | 176 --- document-legacy/src/lib.rs | 20 +- document-legacy/src/operation.rs | 176 --- document-legacy/src/response.rs | 39 - editor/src/dispatcher.rs | 8 +- editor/src/messages/frontend/utility_types.rs | 2 +- .../utility_types/input_keyboard.rs | 2 - .../messages/layout/layout_message_handler.rs | 2 +- .../utility_types/widgets/input_widgets.rs | 2 +- .../portfolio/document/document_message.rs | 50 +- .../document/document_message_handler.rs | 209 +-- .../navigation/navigation_message_handler.rs | 2 - .../node_graph/graph_operation_message.rs | 2 +- .../graph_operation_message_handler.rs | 14 +- .../document/node_graph/node_graph_message.rs | 6 +- .../node_graph/node_graph_message_handler.rs | 24 +- .../document_node_types.rs | 2 +- .../node_properties.rs | 50 +- .../overlays/overlays_message_handler.rs | 14 +- .../document/properties_panel/mod.rs | 1 - .../properties_panel_message.rs | 20 +- .../properties_panel_message_handler.rs | 165 +-- .../properties_panel/utility_functions.rs | 793 ----------- .../properties_panel/utility_types.rs | 15 +- .../document/utility_types/layer_panel.rs | 46 +- .../portfolio/document/utility_types/misc.rs | 2 +- .../messages/portfolio/portfolio_message.rs | 10 +- .../portfolio/portfolio_message_handler.rs | 38 +- .../graph_modification_utils.rs | 41 +- .../tool/common_functionality/resize.rs | 9 +- .../tool/common_functionality/snapping.rs | 72 +- .../src/messages/tool/tool_message_handler.rs | 9 +- .../tool/tool_messages/artboard_tool.rs | 27 +- .../tool/tool_messages/ellipse_tool.rs | 10 +- .../messages/tool/tool_messages/fill_tool.rs | 6 +- .../tool/tool_messages/gradient_tool.rs | 16 +- .../tool/tool_messages/imaginate_tool.rs | 24 +- .../messages/tool/tool_messages/line_tool.rs | 8 +- .../messages/tool/tool_messages/path_tool.rs | 2 +- .../messages/tool/tool_messages/pen_tool.rs | 5 +- .../tool/tool_messages/polygon_tool.rs | 10 +- .../tool/tool_messages/rectangle_tool.rs | 10 +- .../tool/tool_messages/select_tool.rs | 12 +- .../tool/tool_messages/spline_tool.rs | 10 +- .../messages/tool/tool_messages/text_tool.rs | 48 +- editor/src/messages/tool/utility_types.rs | 8 +- editor/src/node_graph_executor.rs | 17 +- frontend/src/state-providers/portfolio.ts | 2 +- frontend/src/wasm-communication/editor.ts | 8 +- frontend/wasm/src/editor_api.rs | 45 +- node-graph/graphene-cli/src/main.rs | 2 +- 64 files changed, 330 insertions(+), 5868 deletions(-) delete mode 100644 document-legacy/src/boolean_ops.rs delete mode 100644 document-legacy/src/consts.rs delete mode 100644 document-legacy/src/error.rs delete mode 100644 document-legacy/src/intersection.rs delete mode 100644 document-legacy/src/layers/base64_serde.rs delete mode 100644 document-legacy/src/layers/render_data.rs delete mode 100644 document-legacy/src/layers/shape_layer.rs delete mode 100644 document-legacy/src/operation.rs delete mode 100644 document-legacy/src/response.rs delete mode 100644 editor/src/messages/portfolio/document/properties_panel/utility_functions.rs diff --git a/Cargo.lock b/Cargo.lock index 77e4499e4c..8ec5580f77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7892,18 +7892,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.28" +version = "0.7.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d6f15f7ade05d2a4935e34a457b936c23dc70a05cc1d97133dc99e7a3fe0f0e" +checksum = "1c4061bedbb353041c12f413700357bec76df2c7e2ca8e4df8bac24c6bf68e3d" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.28" +version = "0.7.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbbad221e3f78500350ecbd7dfa4e63ef945c05f4c61cb7f4d3f84cd0bba649b" +checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a" dependencies = [ "proc-macro2", "quote", diff --git a/document-legacy/src/boolean_ops.rs b/document-legacy/src/boolean_ops.rs deleted file mode 100644 index 87adee716b..0000000000 --- a/document-legacy/src/boolean_ops.rs +++ /dev/null @@ -1,811 +0,0 @@ -use crate::consts::F64PRECISE; -use crate::intersection::{intersections, line_curve_intersections, valid_t, Intersect, Origin}; -use crate::layers::shape_layer::ShapeLegacyLayer; -use crate::layers::style::PathStyle; - -use kurbo::{BezPath, CubicBez, Line, ParamCurve, ParamCurveArclen, ParamCurveArea, ParamCurveExtrema, PathEl, PathSeg, Point, QuadBez, Rect}; -use serde::{Deserialize, Serialize}; -use std::cell::RefCell; -use std::fmt::{self, Debug, Formatter}; -use std::mem::swap; - -#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, specta::Type)] -pub enum BooleanOperation { - Union, - Difference, - Intersection, - SubtractFront, - SubtractBack, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] -pub enum BooleanOperationError { - InvalidSelection, - InvalidIntersections, - NoIntersections, - NothingDone, // Not necessarily an error - DirectionUndefined, - NoResult, - Unexpected, // For debugging, when complete nothing should be unexpected -} - -struct Edge { - pub from: Origin, - pub destination: usize, - pub curve: BezPath, -} - -impl Debug for Edge { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_str(format!("\n To: {}, Type: {:?}", self.destination, self.from).as_str())?; - f.write_str(format!(" {:?}", self.curve).as_str()) - } -} - -struct Vertex { - pub intersect: Intersect, - pub edges: Vec, -} - -impl Debug for Vertex { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.write_str( - format!( - "\n Intersect Point: {:?} Segment index of A: {:?}, Segment index of B: {:?} t value of A: {:?} t value of B: {:?}", - self.intersect.point, - self.intersect.segment_index(Origin::Alpha), - self.intersect.segment_index(Origin::Beta), - self.intersect.t_value(Origin::Alpha), - self.intersect.t_value(Origin::Beta), - ) - .as_str(), - )?; - f.debug_list().entries(self.edges.iter()).finish() - } -} - -#[derive(PartialEq, Eq, Clone, Copy, Debug)] -enum Direction { - Ccw, - Cw, -} - -/// Behavior: Intersection and Union cases are distinguished between by cycle area magnitude. -/// This only affects shapes whose intersection is a single shape, and the intersection is similarly sized to the union. -/// Can be solved by first computing at low accuracy, and if the values are close recomputing. -#[derive(Clone)] -struct Cycle { - vertices: Vec<(usize, Origin)>, - direction: Option, - area: f64, -} - -impl Cycle { - pub fn new(start_vertex_index: usize, edge_origin: Origin) -> Self { - Cycle { - vertices: vec![(start_vertex_index, edge_origin)], - direction: None, - area: 0.0, - } - } - - /// Returns true when the cycle is complete, a cycle is complete when it revisits its first vertex where edge is the edge traversed in order to get to vertex. - /// For purposes of computing direction this function assumes vertices are traversed in order - fn extend(&mut self, vertex: usize, edge_origin: Origin, edge_curve: &BezPath) -> bool { - self.vertices.push((vertex, edge_origin)); - self.area += path_area(edge_curve); - vertex == self.vertices[0].0 - } - - /// Returns number of vertices == number of edges in cycle. - fn len(&self) -> usize { - self.vertices.len() - 1 - } - - pub fn prev_edge_origin(&self) -> Origin { - self.vertices.last().unwrap().1 - } - - pub fn prev_vertex(&self) -> usize { - self.vertices.last().unwrap().0 - } - - pub fn vertices(&self) -> &Vec<(usize, Origin)> { - &self.vertices - } - - pub fn area(&self) -> f64 { - self.area - } - - pub fn direction(&mut self) -> Result { - match self.direction { - Some(direction) => Ok(direction), - None => { - if self.area > 0.0 { - self.direction = Some(Direction::Ccw); - Ok(Direction::Ccw) - } else if self.area < 0.0 { - self.direction = Some(Direction::Cw); - Ok(Direction::Cw) - } else { - Err(BooleanOperationError::DirectionUndefined) - } - } - } - } - - /// If the path is empty (has no segments), the function `Err`s. - /// If the path crosses itself, the computed direction may (or probably will) be wrong, on account of it not really being defined. - pub fn direction_for_path(path: &BezPath) -> Result { - let mut area = 0.0; - path.segments().for_each(|path_segment| area += path_segment.signed_area()); - if area > 0.0 { - Ok(Direction::Ccw) - } else if area < 0.0 { - Ok(Direction::Cw) - } else { - Err(BooleanOperationError::DirectionUndefined) - } - } -} - -/// Optimization: store computed segment bounding boxes, or even edge bounding boxes to prevent recomputation. -#[derive(Debug)] -struct PathGraph { - vertices: Vec, -} - -/// # Boolean Operation Algorithm -/// `PathGraph` represents a directional graph with edges "colored" by `Origin`. -/// Each edge also represents a portion of a visible shape. -/// Has somewhat (totally?) undefined behavior when shapes have self intersections. -impl PathGraph { - pub fn from_paths(alpha: &BezPath, beta: &BezPath) -> Result { - let mut new = PathGraph { - vertices: intersections(alpha, beta).into_iter().map(|i| Vertex { intersect: i, edges: Vec::new() }).collect(), - }; - // We only consider graphs with even numbers of intersections. - // An odd number of intersections occurs when either: - // 1. There exists a tangential intersection (which shouldn't affect boolean ops) - // 2. The algorithm has found an extra intersection or missed an intersection - if new.size() == 0 { - return Err(BooleanOperationError::NoIntersections); - } - if new.size() % 2 != 0 { - return Err(BooleanOperationError::InvalidIntersections); - } - new.add_edges_from_path(alpha, Origin::Alpha); - new.add_edges_from_path(beta, Origin::Beta); - Ok(new) - } - - // TODO: NOTE: about intersection time_val order - /// Expects `path` (and all subpaths in `path`) to be closed. - /// # Panics - /// This function panics when `path` is empty. - fn add_edges_from_path(&mut self, path: &BezPath, origin: Origin) { - struct AlgorithmState { - //current_start holds the index of the vertex the current edge is starting from - current_start: Option, - current: Vec, - // in order to iterate through once, store information for incomplete first edge - beginning: Vec, - start_index: Option, - // seg index != el_index - seg_index: i32, - } - - impl AlgorithmState { - fn new() -> Self { - AlgorithmState { - current_start: None, - current: Vec::new(), - beginning: Vec::new(), - start_index: None, - seg_index: 0, - } - } - - fn reset(&mut self) { - self.current_start = None; - self.current = Vec::new(); - self.beginning = Vec::new(); - self.start_index = None; - } - - fn advance_by_seg(&mut self, graph: &mut PathGraph, seg: PathSeg, origin: Origin) { - let (vertex_ids, mut t_values) = graph.intersects_in_seg(self.seg_index, origin); - if !vertex_ids.is_empty() { - let subdivided = subdivide_path_seg(&seg, &mut t_values); - for (vertex_id, sub_seg) in vertex_ids.into_iter().zip(subdivided.iter()) { - match self.current_start { - Some(index) => { - sub_seg.map(|end_of_edge| self.current.push(end_of_edge)); - graph.add_edge(origin, index, vertex_id, self.current.clone()); - self.current_start = Some(vertex_id); - self.current = Vec::new(); - } - None => { - self.current_start = Some(vertex_id); - self.start_index = Some(vertex_id); - sub_seg.map(|end_of_beginning| self.beginning.push(end_of_beginning)); - } - } - } - subdivided.last().unwrap().map(|start_of_edge| self.current.push(start_of_edge)); - } else { - match self.current_start { - Some(_) => self.current.push(seg), - None => self.beginning.push(seg), - } - } - self.seg_index += 1; - } - - fn advance_by_closepath(&mut self, graph: &mut PathGraph, initial_point: &mut Point, origin: Origin) { - // When a curve ends in a closepath and its start point does not equal its endpoint they should be connected with a line - let last_line = match self.current.last() { - Some(start_of_final_edge) => Line { - p0: start_of_final_edge.end(), - p1: *initial_point, - }, - None => { - // When None occurs the current edge has been connected to a vertex. - // Either self.beginning is Some or None, if self.beginning is Some there may be a dangling edge to connect - // if self.beginning is None, the end of the current edge may not have closed the path - match self.beginning.last() { - Some(end_of_first_edge) => Line { - p0: end_of_first_edge.end(), - p1: *initial_point, - }, - None => Line { - // should never panic, either a intersection has been encountered, so self.current_start is Some. - // or no vertex has been encountered so self.beginning.last() is Some - p0: graph.vertex(self.current_start.unwrap()).intersect.point, - p1: *initial_point, - }, - } - } - }; - if last_line.length() > F64PRECISE { - // A closepath implicitly defines a line which closes the path and the closepath line may contain intersections - self.advance_by_seg(graph, PathSeg::Line(last_line), origin); - } - } - - fn finalize_sub_path(&mut self, graph: &mut PathGraph, origin: Origin) { - if let (Some(current_start_), Some(start_index_)) = (self.current_start, self.start_index) { - // Complete the current path - self.current.append(&mut self.beginning); - graph.add_edge(origin, current_start_, start_index_, self.current.clone()); - } else { - // Path has a subpath with no intersects. - // Create a dummy vertex with single edge which will be identified as cycle. - let dumb_id = graph.add_vertex(Intersect::new(self.beginning[0].start(), 0.0, 0.0, -1, -1)); - graph.add_edge(origin, dumb_id, dumb_id, self.beginning.clone()); - } - } - } - - let mut algorithm_state = AlgorithmState::new(); - - // All valid SVG paths start with a moveto, so this will always be initialized - let mut initial_point = Point::new(0.0, 0.0); - - for (el_index, el) in path.iter().enumerate() { - match el { - PathEl::MoveTo(p) => initial_point = p, - PathEl::ClosePath => { - algorithm_state.advance_by_closepath(self, &mut initial_point, origin); - - algorithm_state.finalize_sub_path(self, origin); - - algorithm_state.reset(); - } - _ => { - algorithm_state.advance_by_seg(self, path.get_seg(el_index).unwrap(), origin); - } - } - } - } - - fn add_vertex(&mut self, intersect: Intersect) -> usize { - self.vertices.push(Vertex { intersect, edges: Vec::new() }); - self.vertices.len() - 1 - } - - fn add_edge(&mut self, origin: Origin, vertex: usize, destination: usize, curve: Vec) { - let new_edge = Edge { - from: origin, - destination, - curve: BezPath::from_path_segments(curve.into_iter()), - }; - self.vertices[vertex].edges.push(new_edge); - } - - /// Returns the `Vertex` index and intersect `t_value` for all intersects in the segment identified by `seg_index` from `origin`. - /// Sorts both lists for ascending `t_value`. - fn intersects_in_seg(&self, seg_index: i32, origin: Origin) -> (Vec, Vec) { - let mut vertex_index = Vec::new(); - let mut t_values = Vec::new(); - for (v_index, vertex) in self.vertices.iter().enumerate() { - if vertex.intersect.segment_index(origin) == seg_index { - let next_t = vertex.intersect.t_value(origin); - let insert_index = match t_values.binary_search_by(|val: &f64| (*val).partial_cmp(&next_t).unwrap_or(std::cmp::Ordering::Less)) { - Ok(val) | Err(val) => val, - }; - t_values.insert(insert_index, next_t); - vertex_index.insert(insert_index, v_index) - } - } - (vertex_index, t_values) - } - - /// Returns the number of vertices in the graph. This is equivalent to the number of intersections. - pub fn size(&self) -> usize { - self.vertices.len() - } - - pub fn vertex(&self, index: usize) -> &Vertex { - &self.vertices[index] - } - - /// A properly constructed `PathGraph` has no duplicate edges of the same `Origin`. - pub fn edge(&self, from: usize, to: usize, origin: Origin) -> Option<&Edge> { - // With a data structure restructure, or a hashmap, the `find()` here could be avoided, but it probably has a minimal performance impact - self.vertex(from).edges.iter().find(|edge| edge.destination == to && edge.from == origin) - } - - /// Where a valid cycle alternates edge `Origin`. - /// Single edge/single vertex "dummy" cycles are also valid. - fn get_cycle(&self, cycle: &mut Cycle, marker_map: &mut Vec) { - if cycle.prev_edge_origin() == Origin::Alpha { - marker_map[cycle.prev_vertex()] |= 1; - } else { - marker_map[cycle.prev_vertex()] |= 2; - } - if let Some(next_edge) = self.vertex(cycle.prev_vertex()).edges.iter().find(|edge| edge.from != cycle.prev_edge_origin()) { - if !cycle.extend(next_edge.destination, next_edge.from, &next_edge.curve) { - self.get_cycle(cycle, marker_map) - } - } - } - - pub fn get_cycles(&self) -> Vec { - let mut cycles = Vec::new(); - let mut markers = vec![0; self.size()]; - - self.vertices.iter().enumerate().for_each(|(vertex_index, _vertex)| { - if (markers[vertex_index] & 1) == 0 { - let mut temp = Cycle::new(vertex_index, Origin::Alpha); - self.get_cycle(&mut temp, &mut markers); - if temp.len() > 0 { - cycles.push(temp); - } - } - if (markers[vertex_index] & 2) == 0 { - let mut temp = Cycle::new(vertex_index, Origin::Beta); - self.get_cycle(&mut temp, &mut markers); - if temp.len() > 0 { - cycles.push(temp); - } - } - }); - cycles - } - - pub fn get_shape(&self, cycle: &Cycle, style: &PathStyle) -> ShapeLegacyLayer { - let mut curve = Vec::new(); - let vertices = cycle.vertices(); - for index in 1..vertices.len() { - // We expect the cycle to be valid so this should not panic - concat_paths(&mut curve, &self.edge(vertices[index - 1].0, vertices[index].0, vertices[index].1).unwrap().curve); - } - curve.push(PathEl::ClosePath); - ShapeLegacyLayer::new(BezPath::from_vec(curve).iter().into(), style.clone()) - } -} - -/// If `t` is on `(0, 1)`, returns the split curve. -/// If `t` is outside `[0, 1]`, returns `(None, None)` -/// If `t` is 0 returns `(None, p)`. -/// If `t` is 1 returns `(p, None)`. -pub fn split_path_seg(p: &PathSeg, t: f64) -> (Option, Option) { - if t <= -F64PRECISE || t >= 1.0 + F64PRECISE { - return (None, None); - } - if t <= F64PRECISE { - return (None, Some(*p)); - } - if t >= 1.0 - F64PRECISE { - return (Some(*p), None); - } - match p { - PathSeg::Cubic(cubic) => { - let a1 = Line::new(cubic.p0, cubic.p1).eval(t); - let a2 = Line::new(cubic.p1, cubic.p2).eval(t); - let a3 = Line::new(cubic.p2, cubic.p3).eval(t); - let b1 = Line::new(a1, a2).eval(t); - let b2 = Line::new(a2, a3).eval(t); - let c1 = Line::new(b1, b2).eval(t); - ( - Some(PathSeg::Cubic(CubicBez { p0: cubic.p0, p1: a1, p2: b1, p3: c1 })), - Some(PathSeg::Cubic(CubicBez { p0: c1, p1: b2, p2: a3, p3: cubic.p3 })), - ) - } - PathSeg::Quad(quad) => { - let b1 = Line::new(quad.p0, quad.p1).eval(t); - let b2 = Line::new(quad.p1, quad.p2).eval(t); - let c1 = Line::new(b1, b2).eval(t); - ( - Some(PathSeg::Quad(QuadBez { p0: quad.p0, p1: b1, p2: c1 })), - Some(PathSeg::Quad(QuadBez { p0: c1, p1: b2, p2: quad.p2 })), - ) - } - PathSeg::Line(line) => { - let split = line.eval(t); - (Some(PathSeg::Line(Line { p0: line.p0, p1: split })), Some(PathSeg::Line(Line { p0: split, p1: line.p1 }))) - } - } -} - -/// Splits `p` at each of `t_values`. -/// `t_values` should be sorted in ascending order. -/// The length of the returned `Vec` is always equal to `1 + t_values.len()`. -pub fn subdivide_path_seg(p: &PathSeg, t_values: &mut [f64]) -> Vec> { - let mut sub_segments = Vec::new(); - let mut to_split = Some(*p); - let mut prev_split = 0.0; - for split in t_values { - if let Some(to_split_next) = to_split { - let (sub_seg, _to_split) = split_path_seg(&to_split_next, (*split - prev_split) / (1.0 - prev_split)); - to_split = _to_split; - sub_segments.push(sub_seg); - prev_split = *split; - } else { - sub_segments.push(None); - } - } - sub_segments.push(to_split); - sub_segments -} - -pub fn composite_boolean_operation(mut select: BooleanOperation, shapes: &mut Vec>) -> Result, BooleanOperationError> { - if select == BooleanOperation::SubtractFront { - select = BooleanOperation::SubtractBack; - let temp_len = shapes.len(); - shapes.swap(0, temp_len - 1); - } - match select { - BooleanOperation::Union | BooleanOperation::Intersection => { - // We must attempt to union each shape with every other shape - let mut subject_idx = 0; - while subject_idx < shapes.len() { - let mut shape_idx = 0; - while shape_idx < shapes.len() && subject_idx < shapes.len() { - if shape_idx == subject_idx { - shape_idx += 1; - continue; - } - let partial_union = boolean_operation(select, &mut shapes[subject_idx].borrow_mut(), &mut shapes[shape_idx].borrow_mut()); - match partial_union { - Ok(temp_union) => { - // The result of a successful union will be exactly one shape - if let Some(result) = temp_union.into_iter().next() { - shapes.push(RefCell::new(result)); - shapes.swap_remove(subject_idx); - shapes.swap_remove(shape_idx); - } else { - return Err(BooleanOperationError::NoResult); - } - } - Err(BooleanOperationError::NothingDone) => shape_idx += 1, - Err(err) => return Err(err), - } - } - subject_idx += 1; - } - Ok(shapes.iter().map(|ref_shape_layer| ref_shape_layer.borrow().clone()).collect()) - } - BooleanOperation::SubtractBack => { - let mut result = vec![shapes[0].borrow().clone()]; - for shape_idx in shapes.iter().skip(1) { - let mut temp = Vec::new(); - for mut partial in result { - match boolean_operation(select, &mut partial, &mut shape_idx.borrow_mut()) { - Ok(mut partial_result) => temp.append(&mut partial_result), - Err(BooleanOperationError::NothingDone) => temp.push(partial), - Err(err) => return Err(err), - } - } - result = temp; // This move should be done without copying - } - Ok(result) - } - BooleanOperation::Difference => { - let mut difference = Vec::new(); - for shape_idx in 0..shapes.len() { - shapes.swap(0, shape_idx); - difference.append(&mut composite_boolean_operation(BooleanOperation::SubtractBack, shapes)?); - } - Ok(difference) - } - BooleanOperation::SubtractFront => unreachable!("composite boolean operation: unreachable subtract from back"), - } -} - -// TODO: check if shapes are filled -// TODO: Bug: shape with at least two subpaths and comprised of many unions sometimes has erroneous movetos embedded in edges -pub fn boolean_operation(mut select: BooleanOperation, alpha: &mut ShapeLegacyLayer, beta: &mut ShapeLegacyLayer) -> Result, BooleanOperationError> { - if alpha.shape.manipulator_groups().is_empty() || beta.shape.manipulator_groups().is_empty() { - return Err(BooleanOperationError::InvalidSelection); - } - if select == BooleanOperation::SubtractFront { - select = BooleanOperation::SubtractBack; - swap(alpha, beta); - } - let mut alpha_shape = close_path(&(&alpha.shape).into()); - let beta_shape = close_path(&(&beta.shape).into()); - let beta_reverse = close_path(&reverse_path(&beta_shape)); - let alpha_dir = Cycle::direction_for_path(&alpha_shape)?; - let beta_dir = Cycle::direction_for_path(&beta_shape)?; - match select { - BooleanOperation::Union => { - match if beta_dir == alpha_dir { - PathGraph::from_paths(&alpha_shape, &beta_shape) - } else { - PathGraph::from_paths(&alpha_shape, &beta_reverse) - } { - Ok(graph) => { - let mut cycles = graph.get_cycles(); - // "extra calls to ParamCurveArea::area here" - let mut boolean_union = graph.get_shape( - cycles.iter().reduce(|max, cycle| if cycle.area().abs() >= max.area().abs() { cycle } else { max }).unwrap(), - &alpha.style, - ); - for interior in collect_shapes(&graph, &mut cycles, |dir| dir != alpha_dir, |_| &alpha.style)? { - //TODO: this is not very efficient or nice to read - let mut a_path: BezPath = (&boolean_union.shape).into(); - let b_path: BezPath = (&interior.shape).into(); - add_subpath(&mut a_path, b_path); - boolean_union.shape = a_path.iter().into(); - } - Ok(vec![boolean_union]) - } - Err(BooleanOperationError::NoIntersections) => { - // If shape is inside the other the Union is just the larger - // Check could also be done with area and single ray cast - if cast_horizontal_ray(point_on_curve(&beta_shape), &alpha_shape) % 2 != 0 { - Ok(vec![alpha.clone()]) - } else if cast_horizontal_ray(point_on_curve(&alpha_shape), &beta_shape) % 2 != 0 { - beta.style = alpha.style.clone(); - Ok(vec![beta.clone()]) - } else { - Err(BooleanOperationError::NothingDone) - } - } - Err(err) => Err(err), - } - } - BooleanOperation::Difference => { - let graph = if beta_dir != alpha_dir { - PathGraph::from_paths(&alpha_shape, &beta_shape)? - } else { - PathGraph::from_paths(&alpha_shape, &beta_reverse)? - }; - collect_shapes(&graph, &mut graph.get_cycles(), |_| true, |dir| if dir == alpha_dir { &alpha.style } else { &beta.style }) - } - BooleanOperation::Intersection => { - match if beta_dir == alpha_dir { - PathGraph::from_paths(&alpha_shape, &beta_shape) - } else { - PathGraph::from_paths(&alpha_shape, &beta_reverse) - } { - Ok(graph) => { - let mut cycles = graph.get_cycles(); - // "extra calls to ParamCurveArea::area here" - cycles.remove( - cycles - .iter() - .enumerate() - .reduce(|(max_index, max), (index, cycle)| if cycle.area().abs() >= max.area().abs() { (index, cycle) } else { (max_index, max) }) - .unwrap() - .0, - ); - collect_shapes(&graph, &mut cycles, |dir| dir == alpha_dir, |_| &alpha.style) - } - Err(BooleanOperationError::NoIntersections) => { - // Check could also be done with area and single ray cast - if cast_horizontal_ray(point_on_curve(&beta_shape), &alpha_shape) % 2 != 0 { - beta.style = alpha.style.clone(); - Ok(vec![beta.clone()]) - } else if cast_horizontal_ray(point_on_curve(&alpha_shape), &beta_shape) % 2 != 0 { - Ok(vec![alpha.clone()]) - } else { - Err(BooleanOperationError::NothingDone) - } - } - Err(err) => Err(err), - } - } - BooleanOperation::SubtractFront => { - unreachable!("Boolean operation: unreachable subtract from back"); - } - BooleanOperation::SubtractBack => { - match if beta_dir != alpha_dir { - PathGraph::from_paths(&alpha_shape, &beta_shape) - } else { - PathGraph::from_paths(&alpha_shape, &beta_reverse) - } { - Ok(graph) => collect_shapes(&graph, &mut graph.get_cycles(), |dir| dir == alpha_dir, |_| &alpha.style), - Err(BooleanOperationError::NoIntersections) => { - if cast_horizontal_ray(point_on_curve(&beta_shape), &alpha_shape) % 2 != 0 { - add_subpath(&mut alpha_shape, if beta_dir == alpha_dir { reverse_path(&beta_shape) } else { beta_shape }); - Ok(vec![alpha.clone()]) - } else { - Err(BooleanOperationError::NothingDone) - } - } - Err(err) => Err(err), - } - } - } -} - -// TODO check bounding boxes more rigorously -pub fn cast_horizontal_ray(from: Point, into: &BezPath) -> usize { - let mut ray = PathSeg::Line(Line { - p0: from, - p1: Point { x: from.x + 1.0, y: from.y }, - }); - let mut intersects = Vec::new(); - for ref mut seg in into.segments() { - if kurbo::ParamCurveExtrema::bounding_box(seg).x1 > from.x { - line_curve_intersections((&mut ray, seg), |_, b| valid_t(b), &mut intersects); - } - } - intersects.len() -} - -/// Uses curve start point as point on the curve. -/// # Panics -/// This function panics if the `curve` is empty. -pub fn point_on_curve(curve: &BezPath) -> Point { - curve.segments().next().unwrap().start() -} - -/// # Panics -/// This function panics if the curve has no `PathSeg`s. -pub fn bounding_box(curve: &BezPath) -> Rect { - curve - .segments() - .map(|seg| ::bounding_box(&seg)) - .reduce(|bounds, rect| bounds.union(rect)) - .unwrap() -} - -fn collect_shapes<'a, F, G>(graph: &PathGraph, cycles: &mut Vec, predicate: F, style: G) -> Result, BooleanOperationError> -where - F: Fn(Direction) -> bool, - G: Fn(Direction) -> &'a PathStyle, -{ - let mut shapes = Vec::new(); - - if cycles.is_empty() { - return Err(BooleanOperationError::Unexpected); - } - - for cycle in cycles { - match cycle.direction() { - Ok(dir) => { - if predicate(dir) { - shapes.push(graph.get_shape(cycle, style(dir))); - } - } - // Exclude cycles with 0.0 area - Err(_err) => (), - } - } - Ok(shapes) -} - -pub fn reverse_path_segment(seg: &mut PathSeg) { - match seg { - PathSeg::Line(line) => std::mem::swap(&mut line.p0, &mut line.p1), - PathSeg::Quad(quad) => std::mem::swap(&mut quad.p0, &mut quad.p2), - PathSeg::Cubic(cubic) => { - std::mem::swap(&mut cubic.p0, &mut cubic.p3); - std::mem::swap(&mut cubic.p1, &mut cubic.p2); - } - } -} - -/// Reverses `path` by reversing each `PathSeg`, and reversing the order of `PathSegs` within each subpath. -/// Note: a closed path might no longer be closed after applying this function. -pub fn reverse_path(path: &BezPath) -> BezPath { - let mut curve = Vec::new(); - let mut temp = Vec::new(); - let mut path_segments = path.segments(); - - for element in path.iter() { - match element { - PathEl::MoveTo(_) => { - curve.append(&mut temp.into_iter().rev().collect()); - temp = Vec::new(); - } - _ => { - if let Some(mut seg) = path_segments.next() { - reverse_path_segment(&mut seg); - temp.push(seg); - } - } - } - } - curve.append(&mut temp.into_iter().rev().collect()); - BezPath::from_path_segments(curve.into_iter()) -} - -/// Close off all sub-paths in curve by inserting a `ClosePath` whenever a `MoveTo` is not preceded by one. -pub fn close_path(curve: &BezPath) -> BezPath { - let mut new = BezPath::new(); - let mut path_closed_flag = true; - for el in curve.iter() { - match el { - PathEl::MoveTo(p) => { - if !path_closed_flag { - new.push(PathEl::ClosePath); - } - new.push(PathEl::MoveTo(p)); - path_closed_flag = false; - } - PathEl::ClosePath => { - path_closed_flag = true; - new.push(PathEl::ClosePath); - } - element => { - new.push(element); - } - } - } - if !path_closed_flag { - new.push(PathEl::ClosePath); - } - new -} - -/// Concatenate `b` to `a`, where `b` is not a new subpath but a continuation of `a`. -pub fn concat_paths(a: &mut Vec, b: &BezPath) { - if a.is_empty() { - a.append(&mut b.elements().to_vec()); - return; - } - // Remove closepath - if let Some(PathEl::ClosePath) = a.last() { - a.remove(a.len() - 1); - } - // Skip initial `MoveTo`, which should be guaranteed to exist - b.iter().skip(1).for_each(|element| a.push(element)); -} - -/// Concatenate `b` to `a`, where `b` is a new subpath. -pub fn add_subpath(a: &mut BezPath, b: BezPath) { - b.into_iter().for_each(|el| a.push(el)); -} - -pub fn path_length(a: &BezPath, accuracy: Option) -> f64 { - let mut sum = 0.0; - // Computing arc length with `F64PRECISE` accuracy is probably ridiculous - match accuracy { - Some(val) => a.segments().for_each(|seg| sum += seg.arclen(val)), - None => a.segments().for_each(|seg| sum += seg.arclen(F64PRECISE)), - } - sum -} - -pub fn path_area(a: &BezPath) -> f64 { - a.segments().fold(0.0, |mut area, seg| { - area += seg.signed_area(); - area - }) -} diff --git a/document-legacy/src/consts.rs b/document-legacy/src/consts.rs deleted file mode 100644 index df9c3752d1..0000000000 --- a/document-legacy/src/consts.rs +++ /dev/null @@ -1,5 +0,0 @@ -// BOOLEAN OPERATIONS - -// Bezier curve intersection algorithm -pub const F64PRECISE: f64 = f64::EPSILON * ((1 << 7) as f64); // ~= 2^(-45) - For f64 comparisons to allow for rounding error; note that f64::EPSILON ~= 2^(-52) -pub const F64LOOSE: f64 = f64::EPSILON * ((1 << 20) as f64); // ~= 2^(-32) - For comparisons between values that are a result of complex computations where error accumulates diff --git a/document-legacy/src/document.rs b/document-legacy/src/document.rs index 5cdfadc780..9a90f290fe 100644 --- a/document-legacy/src/document.rs +++ b/document-legacy/src/document.rs @@ -1,11 +1,7 @@ use crate::document_metadata::{is_artboard, DocumentMetadata, LayerNodeIdentifier}; -use crate::intersection::Quad; use crate::layers::folder_layer::FolderLegacyLayer; -use crate::layers::layer_info::{LayerData, LayerDataTypeDiscriminant, LegacyLayer, LegacyLayerType}; -use crate::layers::layer_layer::{CachedOutputData, LayerLegacyLayer}; -use crate::layers::shape_layer::ShapeLegacyLayer; -use crate::layers::style::RenderData; -use crate::{DocumentError, DocumentResponse, Operation}; +use crate::layers::layer_info::{LegacyLayer, LegacyLayerType}; +use crate::DocumentError; use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeNetwork, NodeOutput}; use graphene_core::renderer::ClickTarget; @@ -13,12 +9,10 @@ use graphene_core::transform::Footprint; use graphene_core::{concrete, generic, ProtoNodeIdentifier}; use graphene_std::wasm_application_io::WasmEditorApi; -use glam::{DAffine2, DVec2}; +use glam::DVec2; use serde::{Deserialize, Serialize}; -use std::cmp::max; use std::collections::hash_map::DefaultHasher; -use std::collections::HashMap; -use std::hash::{Hash, Hasher}; +use std::hash::Hasher; use std::vec; /// A number that identifies a layer. @@ -49,7 +43,11 @@ impl PartialEq for Document { impl Default for Document { fn default() -> Self { Self { - root: LegacyLayer::new(LegacyLayerType::Folder(FolderLegacyLayer::default()), DAffine2::IDENTITY.to_cols_array()), + root: LegacyLayer { + name: None, + visible: true, + data: LegacyLayerType::Folder(FolderLegacyLayer::default()), + }, state_identifier: DefaultHasher::new(), document_network: { use graph_craft::document::{value::TaggedValue, NodeInput}; @@ -155,78 +153,10 @@ impl Document { .reduce(graphene_core::renderer::Quad::combine_bounds) } - /// Wrapper around render, that returns the whole document as a Response. - pub fn render_root(&mut self, render_data: &RenderData) -> String { - // Render and append to the defs section - let mut svg_defs = String::from(""); - self.root.render(&mut vec![], &mut svg_defs, render_data); - svg_defs.push_str(""); - - // Append the cached rendered SVG - svg_defs.push_str(&self.root.cache); - - svg_defs - } - - /// Renders everything below the given layer contained within its parent folder. - pub fn render_layers_below(&mut self, below_layer_path: &[LayerId], render_data: &RenderData) -> Option { - // Split the path into the layer ID and its parent folder - let (layer_id_to_render_below, parent_folder_path) = below_layer_path.split_last()?; - - // Note: it is bad practice to directly clone and modify the document structure, this is a temporary hack until this whole system is replaced by the node graph - let mut temp_subset_folder = self.layer_mut(parent_folder_path).ok()?.clone(); - if let LegacyLayerType::Folder(ref mut folder) = temp_subset_folder.data { - // Remove the upper layers to leave behind the lower subset for rendering - let count_of_layers_below = folder.layer_ids.iter().position(|id| id == layer_id_to_render_below).unwrap(); - folder.layer_ids.truncate(count_of_layers_below); - folder.layers.truncate(count_of_layers_below); - - // Render and append to the defs section - let mut svg_defs = String::from(""); - temp_subset_folder.render(&mut vec![], &mut svg_defs, render_data); - svg_defs.push_str(""); - - // Append the cached rendered SVG - svg_defs.push_str(&temp_subset_folder.cache); - - Some(svg_defs) - } else { - None - } - } - - /// Renders a layer and its children - pub fn render_layer(&mut self, layer_path: &[LayerId], render_data: &RenderData) -> Option { - // Note: it is bad practice to directly clone and modify the document structure, this is a temporary hack until this whole system is replaced by the node graph - let mut temp_clone = self.layer_mut(layer_path).ok()?.clone(); - - // Render and append to the defs section - let mut svg_defs = String::from(""); - temp_clone.render(&mut vec![], &mut svg_defs, render_data); - svg_defs.push_str(""); - - // Append the cached rendered SVG - svg_defs.push_str(&temp_clone.cache); - - Some(svg_defs) - } - pub fn current_state_identifier(&self) -> u64 { self.state_identifier.finish() } - /// Checks whether each layer under `path` intersects with the provided `quad` and adds all intersection layers as paths to `intersections`. - pub fn intersects_quad(&self, quad: Quad, path: &mut Vec, intersections: &mut Vec>, render_data: &RenderData) { - self.layer(path).unwrap().intersects_quad(quad, path, intersections, render_data); - } - - /// Checks whether each layer under the root path intersects with the provided `quad` and returns the paths to all intersecting layers. - pub fn intersects_quad_root(&self, quad: Quad, render_data: &RenderData) -> Vec> { - let mut intersections = Vec::new(); - self.intersects_quad(quad, &mut vec![], &mut intersections, render_data); - intersections - } - /// Returns a reference to the requested folder. Fails if the path does not exist, /// or if the requested layer is not of type folder. pub fn folder(&self, path: impl AsRef<[LayerId]>) -> Result<&FolderLegacyLayer, DocumentError> { @@ -239,7 +169,6 @@ impl Document { /// Returns a mutable reference to the requested folder. Fails if the path does not exist, /// or if the requested layer is not of type folder. - /// If you manually edit the folder you have to set the cache_dirty flag yourself. fn folder_mut(&mut self, path: &[LayerId]) -> Result<&mut FolderLegacyLayer, DocumentError> { let mut root = &mut self.root; for id in path { @@ -270,15 +199,6 @@ impl Document { layers.reduce(|a, b| &a[..a.iter().zip(b.iter()).take_while(|&(a, b)| a == b).count()]).unwrap_or_default() } - /// Filters out the non folders from an iterator of paths. - /// Takes and Iterator over &[LayerId] or &Vec. - pub fn folders<'a, T>(&'a self, layers: impl Iterator + 'a) -> impl Iterator + 'a - where - T: AsRef<[LayerId]> + std::cmp::Ord + 'a, - { - layers.filter(|layer| self.is_folder(layer.as_ref())) - } - /// Returns the shallowest folder given the selection, even if the selection doesn't contain any folders pub fn shallowest_common_folder<'a>(&self, layers: impl Iterator) -> Result<&'a [LayerId], DocumentError> { let common_prefix_of_path = self.common_layer_path_prefix(layers); @@ -289,15 +209,6 @@ impl Document { }) } - /// Returns all folders that are not contained in any other of the given folders - /// Takes and Iterator over &[LayerId] or &Vec. - pub fn shallowest_folders<'a, T>(&'a self, layers: impl Iterator) -> Vec - where - T: AsRef<[LayerId]> + std::cmp::Ord + 'a, - { - Self::shallowest_unique_layers(self.folders(layers)) - } - /// Returns all layers that are not contained in any other of the given folders /// Takes and Iterator over &[LayerId] or &Vec. pub fn shallowest_unique_layers<'a, T>(layers: impl Iterator) -> Vec @@ -310,71 +221,6 @@ impl Document { sorted_layers.dedup_by(|a, b| a.as_ref().starts_with(b.as_ref())); sorted_layers } - /// Deepest to shallowest (longest to shortest path length) - /// Takes and Iterator over &[LayerId] or &Vec. - pub fn sorted_folders_by_depth<'a, T>(&'a self, layers: impl Iterator) -> Vec - where - T: AsRef<[LayerId]> + std::cmp::Ord + 'a, - { - let mut folders: Vec<_> = self.folders(layers).collect(); - folders.sort_by_key(|a| std::cmp::Reverse(a.as_ref().len())); - folders - } - - pub fn folder_children_paths(&self, path: &[LayerId]) -> Vec> { - if let Ok(folder) = self.folder(path) { - folder.list_layers().iter().map(|f| [path, &[*f]].concat()).collect() - } else { - vec![] - } - } - - pub fn is_folder(&self, path: impl AsRef<[LayerId]>) -> bool { - return self.folder(path.as_ref()).is_ok(); - } - - // Determines which layer is closer to the root, if path_a return true, if path_b return false - // Answers the question: Is A closer to the root than B? - pub fn layer_closer_to_root(&self, path_a: &[u64], path_b: &[u64]) -> bool { - // Convert UUIDs to indices - let indices_for_path_a = self.indices_for_path(path_a).unwrap(); - let indices_for_path_b = self.indices_for_path(path_b).unwrap(); - - let longest = max(indices_for_path_a.len(), indices_for_path_b.len()); - for i in 0..longest { - // usize::MAX becomes negative one here, sneaky. So folders are compared as [X, -1]. This is intentional. - let index_a = *indices_for_path_a.get(i).unwrap_or(&usize::MAX) as i32; - let index_b = *indices_for_path_b.get(i).unwrap_or(&usize::MAX) as i32; - - // At the point at which the two paths first differ, compare to see which is closer to the root - if index_a != index_b { - // If index_a is smaller, index_a is closer to the root - return index_a < index_b; - } - } - - false - } - - // Is the target layer between a <-> b layers, inclusive - pub fn layer_is_between(&self, target: &[u64], path_a: &[u64], path_b: &[u64]) -> bool { - // If the target is the root, it isn't between - if target.is_empty() { - return false; - } - - // This function is inclusive, so we consider path_a, path_b to be between themselves - if target == path_a || target == path_b { - return true; - }; - - // These can't both be true and be between two values - let layer_vs_a = self.layer_closer_to_root(target, path_a); - let layer_vs_b = self.layer_closer_to_root(target, path_b); - - // To be in-between you need to be above A and below B or vice versa - layer_vs_a != layer_vs_b - } /// Given a path to a layer, returns a vector of the indices in the layer tree /// These indices can be used to order a list of layers @@ -387,703 +233,23 @@ impl Document { for id in path { let pos = root.layer_ids.iter().position(|x| *x == *id).ok_or_else(|| DocumentError::LayerNotFound(path.into()))?; indices.push(pos); - root = root.folder(*id).ok_or_else(|| DocumentError::LayerNotFound(path.into()))?; + root = match root.layer(*id) { + Some(LegacyLayer { + data: LegacyLayerType::Folder(folder), + .. + }) => Some(folder), + _ => None, + } + .ok_or_else(|| DocumentError::LayerNotFound(path.into()))?; } indices.push(root.layer_ids.iter().position(|x| *x == layer_id).ok_or_else(|| DocumentError::LayerNotFound(path.into()))?); Ok(indices) } - - /// Replaces the layer at the specified `path` with `layer`. - pub fn set_layer(&mut self, path: &[LayerId], layer: LegacyLayer, insert_index: isize) -> Result<(), DocumentError> { - let mut folder = self.root.as_folder_mut()?; - let mut layer_id = None; - if let Ok((path, id)) = split_path(path) { - layer_id = Some(id); - self.mark_as_dirty(path)?; - folder = self.folder_mut(path)?; - if let Some(folder_layer) = folder.layer_mut(id) { - *folder_layer = layer; - return Ok(()); - } - } - folder.add_layer(layer, layer_id, insert_index).ok_or(DocumentError::IndexOutOfBounds)?; - Ok(()) - } - - /// Visit each layer recursively, marks all children as dirty - pub fn mark_children_as_dirty(layer: &mut LegacyLayer) -> bool { - match layer.data { - LegacyLayerType::Folder(ref mut folder) => { - for sub_layer in folder.layers_mut() { - if Document::mark_children_as_dirty(sub_layer) { - layer.cache_dirty = true; - } - } - } - _ => layer.cache_dirty = true, - } - layer.cache_dirty - } - - /// Adds a new layer to the folder specified by `path`. - /// Passing a negative `insert_index` indexes relative to the end. - /// -1 is equivalent to adding the layer to the top. - pub fn add_layer(&mut self, path: &[LayerId], layer: LegacyLayer, insert_index: isize) -> Result { - let folder = self.folder_mut(path)?; - folder.add_layer(layer, None, insert_index).ok_or(DocumentError::IndexOutOfBounds) - } - - /// Deletes the layer specified by `path`. - pub fn delete(&mut self, path: &[LayerId]) -> Result<(), DocumentError> { - let (path, id) = split_path(path)?; - self.mark_as_dirty(path)?; - self.folder_mut(path)?.remove_layer(id) - } - - pub fn visible_layers(&self, path: &mut Vec, paths: &mut Vec>) -> Result<(), DocumentError> { - if !self.layer(path)?.visible { - return Ok(()); - } - if let Ok(folder) = self.folder(&path) { - for layer in folder.layer_ids.iter() { - path.push(*layer); - self.visible_layers(path, paths)?; - path.pop(); - } - } else { - paths.push(path.clone()); - } - Ok(()) - } - - pub fn viewport_bounding_box(&self, path: &[LayerId], render_data: &RenderData) -> Result, DocumentError> { - let layer = self.layer(path)?; - let transform = self.multiply_transforms(path)?; - Ok(layer.data.bounding_box(transform, render_data)) - } - - pub fn bounding_box_and_transform(&self, path: &[LayerId], render_data: &RenderData) -> Result, DocumentError> { - let layer = self.layer(path)?; - let transform = self.multiply_transforms(&path[..path.len() - 1])?; - Ok(layer.data.bounding_box(layer.transform, render_data).map(|bounds| (bounds, transform))) - } - - /// Compute the center of transformation multiplied with `Document::multiply_transforms`. - pub fn pivot(&self, path: &[LayerId], render_data: &RenderData) -> Option { - let layer = self.layer(path).ok()?; - Some(self.multiply_transforms(path).unwrap_or_default().transform_point2(layer.layerspace_pivot(render_data))) - } - - pub fn visible_layers_bounding_box(&self, render_data: &RenderData) -> Option<[DVec2; 2]> { - let mut paths = vec![]; - self.visible_layers(&mut vec![], &mut paths).ok()?; - self.combined_viewport_bounding_box(paths.iter().map(|x| x.as_slice()), render_data) - } - - pub fn combined_viewport_bounding_box<'a>(&self, paths: impl Iterator, render_data: &RenderData) -> Option<[DVec2; 2]> { - let boxes = paths.filter_map(|path| self.viewport_bounding_box(path, render_data).ok()?); - boxes.reduce(|a, b| [a[0].min(b[0]), a[1].max(b[1])]) - } - - /// Mark the layer at the provided path, as well as all the folders containing it, as dirty. - pub fn mark_upstream_as_dirty(&mut self, path: &[LayerId]) -> Result<(), DocumentError> { - let mut root = &mut self.root; - root.cache_dirty = true; - for id in path { - root = root.as_folder_mut()?.layer_mut(*id).ok_or_else(|| DocumentError::LayerNotFound(path.into()))?; - root.cache_dirty = true; - } - Ok(()) - } - - pub fn mark_downstream_as_dirty(&mut self, path: &[LayerId]) -> Result<(), DocumentError> { - let layer = self.layer_mut(path)?; - layer.cache_dirty = true; - - let mut path = path.to_vec(); - let len = path.len(); - path.push(0); - - if let Some(ids) = layer.as_folder().ok().map(|f| f.layer_ids.clone()) { - for id in ids { - path[len] = id; - self.mark_downstream_as_dirty(&path)? - } - } - Ok(()) - } - - /// For the purposes of rendering, this invalidates the render cache for the layer so it must be re-rendered next time. - pub fn mark_as_dirty(&mut self, path: &[LayerId]) -> Result<(), DocumentError> { - self.mark_upstream_as_dirty(path)?; - Ok(()) - } - - pub fn transforms(&self, path: &[LayerId]) -> Result, DocumentError> { - let mut root = &self.root; - let mut transforms = vec![self.root.transform]; - for id in path { - if let Ok(folder) = root.as_folder() { - root = folder.layer(*id).ok_or_else(|| DocumentError::LayerNotFound(path.into()))?; - } - transforms.push(root.transform); - } - Ok(transforms) - } - - pub fn multiply_transforms(&self, path: &[LayerId]) -> Result { - let mut root = &self.root; - let mut trans = self.root.transform; - for id in path { - if let Ok(folder) = root.as_folder() { - root = folder.layer(*id).ok_or_else(|| DocumentError::LayerNotFound(path.into()))?; - } - trans = trans * root.transform; - } - Ok(trans) - } - - pub fn generate_transform_across_scope(&self, from: &[LayerId], to: Option) -> Result { - let from_rev = self.multiply_transforms(from)?; - let scope = to.unwrap_or(DAffine2::IDENTITY); - Ok(scope * from_rev) - } - - pub fn transform_relative_to_scope(&mut self, layer: &[LayerId], scope: Option, transform: DAffine2) -> Result<(), DocumentError> { - let to = self.generate_transform_across_scope(&layer[..layer.len() - 1], scope)?; - let layer = self.layer_mut(layer)?; - layer.transform = to.inverse() * transform * to * layer.transform; - Ok(()) - } - - pub fn set_transform_relative_to_scope(&mut self, layer: &[LayerId], scope: Option, transform: DAffine2) -> Result<(), DocumentError> { - let to = self.generate_transform_across_scope(&layer[..layer.len() - 1], scope)?; - let layer = self.layer_mut(layer)?; - layer.transform = to.inverse() * transform; - Ok(()) - } - - pub fn generate_transform_relative_to_viewport(&self, from: &[LayerId]) -> Result { - 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) - } - - pub fn set_transform_relative_to_viewport(&mut self, layer: &[LayerId], transform: DAffine2) -> Result<(), DocumentError> { - self.set_transform_relative_to_scope(layer, None, transform) - } - - /// Mutate the document by applying the `operation` to it. If the operation necessitates a - /// reaction from the frontend, responses may be returned. - pub fn handle_operation(&mut self, operation: Operation) -> Result>, DocumentError> { - use DocumentResponse::*; - - operation.pseudo_hash().hash(&mut self.state_identifier); - - let responses = match operation { - Operation::AddEllipse { path, insert_index, transform, style } => { - let layer = LegacyLayer::new(LegacyLayerType::Shape(ShapeLegacyLayer::ellipse(style)), transform); - - self.set_layer(&path, layer, insert_index)?; - - let mut responses = vec![ - DocumentChanged, - CreatedLayer { - path: path.clone(), - is_selected: true, - }, - ]; - responses.extend(update_thumbnails_upstream(&path)); - - Some(responses) - } - Operation::AddRect { path, insert_index, transform, style } => { - let layer = LegacyLayer::new(LegacyLayerType::Shape(ShapeLegacyLayer::rectangle(style)), transform); - - self.set_layer(&path, layer, insert_index)?; - - let mut responses = vec![ - DocumentChanged, - CreatedLayer { - path: path.clone(), - is_selected: true, - }, - ]; - responses.extend(update_thumbnails_upstream(&path)); - - Some(responses) - } - Operation::AddLine { path, insert_index, transform, style } => { - let layer = LegacyLayer::new(LegacyLayerType::Shape(ShapeLegacyLayer::line(style)), transform); - - self.set_layer(&path, layer, insert_index)?; - - let mut responses = vec![ - DocumentChanged, - CreatedLayer { - path: path.clone(), - is_selected: true, - }, - ]; - responses.extend(update_thumbnails_upstream(&path)); - - Some(responses) - } - // TODO: Remove - Operation::AddFrame { - path, - insert_index, - transform, - network, - } => { - let layer = LegacyLayer::new(LegacyLayerType::Layer(LayerLegacyLayer { network, ..Default::default() }), transform); - - self.set_layer(&path, layer, insert_index)?; - - let mut responses = vec![ - DocumentChanged, - CreatedLayer { - path: path.clone(), - is_selected: true, - }, - ]; - responses.extend(update_thumbnails_upstream(&path)); - - Some(responses) - } - Operation::SetLayerPreserveAspect { layer_path, preserve_aspect } => { - if let Ok(layer) = self.layer_mut(&layer_path) { - layer.preserve_aspect = preserve_aspect; - } - Some(vec![LayerChanged { path: layer_path.clone() }]) - } - Operation::AddShape { - path, - transform, - insert_index, - style, - subpath, - } => { - let shape = ShapeLegacyLayer::new(subpath, style); - self.set_layer(&path, LegacyLayer::new(LegacyLayerType::Shape(shape), transform), insert_index)?; - Some(vec![DocumentChanged, CreatedLayer { path, is_selected: true }]) - } - Operation::AddPolyline { - path, - insert_index, - points, - transform, - style, - } => { - let points: Vec = points.iter().map(|&it| it.into()).collect(); - self.set_layer(&path, LegacyLayer::new(LegacyLayerType::Shape(ShapeLegacyLayer::poly_line(points, style)), transform), insert_index)?; - - let mut responses = vec![ - DocumentChanged, - CreatedLayer { - path: path.clone(), - is_selected: true, - }, - ]; - responses.extend(update_thumbnails_upstream(&path)); - - Some(responses) - } - Operation::DeleteLayer { path } => { - fn aggregate_deletions(folder: &FolderLegacyLayer, path: &mut Vec, responses: &mut Vec) { - for (id, layer) in folder.layer_ids.iter().zip(folder.layers()) { - path.push(*id); - responses.push(DocumentResponse::DeletedLayer { path: path.clone() }); - if let LegacyLayerType::Folder(f) = &layer.data { - aggregate_deletions(f, path, responses); - } - path.pop(); - } - } - let mut responses = Vec::new(); - if let Ok(folder) = self.folder(&path) { - aggregate_deletions(folder, &mut path.clone(), &mut responses) - }; - self.delete(&path)?; - - let (folder, _) = split_path(path.as_slice()).unwrap_or((&[], 0)); - responses.extend([DocumentChanged, DeletedLayer { path: path.clone() }, FolderChanged { path: folder.to_vec() }]); - responses.extend(update_thumbnails_upstream(folder)); - Some(responses) - } - Operation::InsertLayer { - destination_path, - layer, - insert_index, - duplicating, - } => { - let (folder_path, layer_id) = split_path(&destination_path)?; - let mut responses = vec![DocumentChanged]; - - // If we are duplicating, use the parent layer path as the folder we insert to - let (created_layer_path, folder_changed_path) = if duplicating { - let folder = self.folder_mut(&destination_path)?; - let new_layer_id = folder.add_layer(*layer, None, insert_index).ok_or(DocumentError::IndexOutOfBounds)?; - - ([destination_path.as_slice(), &[new_layer_id]].concat(), destination_path.clone()) - } else { - let folder = self.folder_mut(folder_path)?; - folder.add_layer(*layer, Some(layer_id), insert_index).ok_or(DocumentError::IndexOutOfBounds)?; - - (destination_path.clone(), folder_path.to_vec()) - }; - - responses.push(CreatedLayer { - path: created_layer_path, - is_selected: !duplicating, - }); - responses.push(FolderChanged { path: folder_changed_path.to_vec() }); - responses.extend(update_thumbnails_upstream(&destination_path)); - - self.mark_as_dirty(&destination_path)?; - - // Recursively iterate through each layer in a folder and add it to the responses vector - fn aggregate_insertions(folder: &FolderLegacyLayer, path: &mut Vec, responses: &mut Vec, duplicating: bool) { - for (id, layer) in folder.layer_ids.iter().zip(folder.layers()) { - path.push(*id); - - responses.push(DocumentResponse::CreatedLayer { - path: path.clone(), - is_selected: !duplicating, - }); - if let LegacyLayerType::Folder(f) = &layer.data { - aggregate_insertions(f, path, responses, duplicating); - } - - path.pop(); - } - } - - if let Ok(folder) = self.folder(&destination_path) { - aggregate_insertions(folder, &mut destination_path.as_slice().to_vec(), &mut responses, duplicating); - }; - - Some(responses) - } - Operation::DuplicateLayer { path } => { - // Notes for review: I wasn't sure on how to apply unwrap_or() to lines of code that use the function self.layer() - - let layer = self.layer(&path)?.clone(); - let layer_is_folder = layer.as_folder().is_ok(); - let (folder_path, _) = split_path(path.as_slice()).unwrap_or((&[], 0)); - - // Recursively collect each of the nested folders and shapes if the layer is a folder - fn recursive_collect(document: &mut Document, layer_path: &[u64]) -> Vec> { - let mut duplicated_layers_so_far = Vec::new(); - - let children = document.folder_children_paths(layer_path); - for child in children { - if document.is_folder(&child) { - duplicated_layers_so_far.push(child.to_vec()); - duplicated_layers_so_far.append(&mut recursive_collect(document, &child)); - } else { - duplicated_layers_so_far.push(child); - } - } - - duplicated_layers_so_far - } - let mut duplicated_layers = if layer_is_folder { recursive_collect(self, &path) } else { Vec::new() }; - - // Iterate through each layer path and collect the corresponding Layer objects into one vector - let duplicated_layers_objects = duplicated_layers - .iter() - .map(|layer_path| self.layer(layer_path.as_slice()).cloned().ok()) - .collect::>>() - .ok_or(DocumentError::InvalidPath)?; - - // Sort both vectors by the layer path depth, from shallowest (fewest) to deepest (most) - let mut indices: Vec = (0..duplicated_layers.len()).collect(); - indices.sort_by_key(|&i| duplicated_layers[i].len()); - duplicated_layers.sort_by_key(|a| a.len()); - let duplicate_layer_objects_sorted: Vec<&LegacyLayer> = indices.iter().map(|&i| &duplicated_layers_objects[i]).collect(); - - let folder = self.folder_mut(folder_path)?; - - let selected_id = path.last().copied().unwrap_or_default(); - let insert_index = folder.layer_ids.iter().position(|&id| id == selected_id).unwrap_or(0) as isize + 1; - - if let Some(new_layer_id) = folder.add_layer(layer, None, insert_index) { - let new_path = [folder_path, &[new_layer_id]].concat(); - - let mut responses = vec![ - DocumentChanged, - CreatedLayer { - path: new_path.clone(), - is_selected: true, - }, - FolderChanged { path: folder_path.to_vec() }, - ]; - responses.extend(update_thumbnails_upstream(path.as_slice())); - - if layer_is_folder { - let new_folder = self.folder_mut(&new_path)?; - // Clear the new folders layer_ids/layers because they contain the layer_ids/layers of the layers that were duplicated - new_folder.layer_ids = vec![]; - new_folder.layers = vec![]; - // Generate a new next assignment ID to avoid collision - new_folder.generate_new_folder_ids(); - - let mut old_to_new_layer_id: HashMap = HashMap::new(); - - for (i, duplicate_layer) in duplicated_layers.into_iter().enumerate() { - let Some(old_layer_id) = duplicate_layer.last().cloned() else { - continue; - }; - - // Iterate through each ID of the current duplicate layer - // If the dictionary contains the ID, we know the duplicate folder has been created already. Use the existing layer ID instead of creating a new one - let sub_layer = &duplicate_layer[new_path.len()..]; - let new_sub_path: Vec = sub_layer.iter().filter_map(|id| old_to_new_layer_id.get(id).cloned()).collect(); - - // Combine the new path with the IDs of the duplicate layer path to create the path where we insert the duplicate layer - let mut updated_layer = duplicate_layer_objects_sorted.get(i).unwrap().to_owned().clone(); - let updated_layer_path_parent = [new_path.clone(), new_sub_path].concat(); - - // Clear the new folder's layer_ids and layers because they contain the layer_ids/layers of the layer were duplicated - if self.is_folder(duplicate_layer) { - let updated_layer_as_folder: &mut FolderLegacyLayer = updated_layer.as_folder_mut()?; - updated_layer_as_folder.layer_ids = vec![]; - updated_layer_as_folder.layers = vec![]; - updated_layer_as_folder.generate_new_folder_ids() - } - - let result = self - .handle_operation(Operation::InsertLayer { - layer: Box::new(updated_layer), - destination_path: updated_layer_path_parent, - insert_index: -1, - duplicating: true, - }) - .ok() - .flatten() - .unwrap_or_default(); - - // Collect the new ID of the duplicated layer from the InsertLayer Operation - // Map the layer ID of the layer we're duplicating to the new ID - if let DocumentResponse::CreatedLayer { path, .. } = result.get(1).unwrap() { - if let Some(new_layer_id) = path.last() { - old_to_new_layer_id.entry(old_layer_id).or_insert(*new_layer_id); - } - } - - responses.extend(result); - } - } - - self.mark_as_dirty(folder_path)?; - - Some(responses) - } else { - return Err(DocumentError::IndexOutOfBounds); - } - } - Operation::RenameLayer { layer_path: path, new_name: name } => { - self.layer_mut(&path)?.name = Some(name); - Some(vec![LayerChanged { path }]) - } - Operation::CreateFolder { path, insert_index } => { - self.set_layer( - &path, - LegacyLayer::new(LegacyLayerType::Folder(FolderLegacyLayer::default()), DAffine2::IDENTITY.to_cols_array()), - insert_index, - )?; - self.mark_as_dirty(&path)?; - - let mut responses = vec![ - DocumentChanged, - CreatedLayer { - path: path.clone(), - is_selected: true, - }, - ]; - responses.extend(update_thumbnails_upstream(&path)); - - Some(responses) - } - Operation::TransformLayer { path, transform } => { - let layer = self.layer_mut(&path).unwrap(); - let transform = DAffine2::from_cols_array(&transform) * layer.transform; - layer.transform = transform; - self.mark_as_dirty(&path)?; - Some([vec![DocumentChanged], update_thumbnails_upstream(&path)].concat()) - } - Operation::TransformLayerInViewport { path, transform } => { - let transform = DAffine2::from_cols_array(&transform); - self.apply_transform_relative_to_viewport(&path, transform)?; - self.mark_as_dirty(&path)?; - Some([vec![DocumentChanged], update_thumbnails_upstream(&path)].concat()) - } - Operation::SetLayerBlobUrl { layer_path, blob_url, resolution: _ } => { - let layer = self.layer_mut(&layer_path).unwrap_or_else(|_| panic!("Blob URL for invalid layer with path '{layer_path:?}'")); - - let LegacyLayerType::Layer(layer) = &mut layer.data else { - panic!("Incorrectly trying to set the image blob URL for a layer that is not a 'Layer' layer type"); - }; - - layer.cached_output_data = CachedOutputData::BlobURL(blob_url); - - self.mark_as_dirty(&layer_path)?; - Some([vec![DocumentChanged, LayerChanged { path: layer_path.clone() }], update_thumbnails_upstream(&layer_path)].concat()) - } - Operation::ClearBlobURL { path } => { - let layer = self.layer_mut(&path).expect("Clearing node graph image for invalid layer"); - match &mut layer.data { - LegacyLayerType::Layer(layer) => { - if matches!(layer.cached_output_data, CachedOutputData::BlobURL(_)) { - layer.cached_output_data = CachedOutputData::None; - } - } - e => panic!("Incorrectly trying to clear the blob URL for layer of type {}", LayerDataTypeDiscriminant::from(&*e)), - } - self.mark_as_dirty(&path)?; - Some([vec![DocumentChanged, LayerChanged { path: path.clone() }], update_thumbnails_upstream(&path)].concat()) - } - Operation::SetPivot { layer_path, pivot } => { - let layer = self.layer_mut(&layer_path).expect("Setting pivot for invalid layer"); - layer.pivot = pivot.into(); - self.mark_as_dirty(&layer_path)?; - Some([vec![DocumentChanged, LayerChanged { path: layer_path.clone() }], update_thumbnails_upstream(&layer_path)].concat()) - } - Operation::SetLayerTransformInViewport { path, transform } => { - let transform = DAffine2::from_cols_array(&transform); - self.set_transform_relative_to_viewport(&path, transform)?; - self.mark_as_dirty(&path)?; - Some([vec![DocumentChanged], update_thumbnails_upstream(&path)].concat()) - } - Operation::SetShapePath { path, subpath } => { - self.mark_as_dirty(&path)?; - - if let LegacyLayerType::Shape(shape) = &mut self.layer_mut(&path)?.data { - shape.shape = subpath; - } - Some(vec![DocumentChanged, LayerChanged { path }]) - } - Operation::SetVectorData { path, vector_data } => { - if let LegacyLayerType::Layer(layer) = &mut self.layer_mut(&path)?.data { - layer.cached_output_data = CachedOutputData::VectorPath(Box::new(vector_data)); - } - Some(Vec::new()) - } - Operation::SetSurface { path, surface_id } => { - if let LegacyLayerType::Layer(layer) = &mut self.layer_mut(&path)?.data { - layer.cached_output_data = CachedOutputData::SurfaceId(surface_id); - } - Some(Vec::new()) - } - Operation::SetSvg { path, svg } => { - if let LegacyLayerType::Layer(layer) = &mut self.layer_mut(&path)?.data { - layer.cached_output_data = CachedOutputData::Svg(svg); - } - Some(Vec::new()) - } - Operation::TransformLayerInScope { path, transform, scope } => { - let transform = DAffine2::from_cols_array(&transform); - let scope = DAffine2::from_cols_array(&scope); - self.transform_relative_to_scope(&path, Some(scope), transform)?; - self.mark_as_dirty(&path)?; - Some([vec![DocumentChanged], update_thumbnails_upstream(&path)].concat()) - } - Operation::SetLayerTransformInScope { path, transform, scope } => { - let transform = DAffine2::from_cols_array(&transform); - let scope = DAffine2::from_cols_array(&scope); - self.set_transform_relative_to_scope(&path, Some(scope), transform)?; - self.mark_as_dirty(&path)?; - Some([vec![DocumentChanged], update_thumbnails_upstream(&path)].concat()) - } - Operation::SetLayerScaleAroundPivot { path, new_scale } => { - let layer = self.layer_mut(&path)?; - - let matrix = layer.transform.to_cols_array(); - let old_scale = (matrix[0], matrix[3]); - - let scale_factor = DVec2::from(new_scale) / DVec2::from(old_scale); - - let offset = DAffine2::from_translation(-layer.pivot); - let scale = DAffine2::from_scale(scale_factor); - let offset_back = DAffine2::from_translation(layer.pivot); - layer.transform = layer.transform * offset_back * scale * offset; - - self.mark_as_dirty(&path)?; - Some([vec![DocumentChanged], update_thumbnails_upstream(&path)].concat()) - } - Operation::SetLayerTransform { path, transform } => { - let transform = DAffine2::from_cols_array(&transform); - let layer = self.layer_mut(&path)?; - layer.transform = transform; - self.mark_as_dirty(&path)?; - Some([vec![DocumentChanged], update_thumbnails_upstream(&path)].concat()) - } - Operation::SetLayerVisibility { path, visible } => { - self.mark_as_dirty(&path)?; - let layer = self.layer_mut(&path)?; - layer.visible = visible; - Some([vec![DocumentChanged], update_thumbnails_upstream(&path)].concat()) - } - Operation::SetLayerBlendMode { path, blend_mode } => { - self.mark_as_dirty(&path)?; - self.layer_mut(&path)?.blend_mode = blend_mode; - - Some([vec![DocumentChanged], update_thumbnails_upstream(&path)].concat()) - } - Operation::SetLayerOpacity { path, opacity } => { - self.mark_as_dirty(&path)?; - self.layer_mut(&path)?.opacity = opacity; - - Some([vec![DocumentChanged], update_thumbnails_upstream(&path)].concat()) - } - Operation::SetLayerStyle { path, style } => { - let layer = self.layer_mut(&path)?; - match &mut layer.data { - LegacyLayerType::Shape(s) => s.style = style, - _ => return Err(DocumentError::NotShape), - } - self.mark_as_dirty(&path)?; - Some([vec![DocumentChanged, LayerChanged { path: path.clone() }], update_thumbnails_upstream(&path)].concat()) - } - Operation::SetLayerStroke { path, stroke } => { - let layer = self.layer_mut(&path)?; - layer.style_mut()?.set_stroke(stroke); - self.mark_as_dirty(&path)?; - Some([vec![DocumentChanged], update_thumbnails_upstream(&path)].concat()) - } - Operation::SetLayerFill { path, fill } => { - let layer = self.layer_mut(&path)?; - layer.style_mut()?.set_fill(fill); - self.mark_as_dirty(&path)?; - Some([vec![DocumentChanged], update_thumbnails_upstream(&path)].concat()) - } - }; - Ok(responses) - } } fn split_path(path: &[LayerId]) -> Result<(&[LayerId], LayerId), DocumentError> { let (id, path) = path.split_last().ok_or(DocumentError::InvalidPath)?; Ok((path, *id)) } - -fn update_thumbnails_upstream(path: &[LayerId]) -> Vec { - let length = path.len(); - let mut responses = Vec::with_capacity(length); - for i in 0..length { - responses.push(DocumentResponse::LayerChanged { path: path[0..(length - i)].to_vec() }); - } - responses -} - -pub fn pick_layer_safe_imaginate_resolution(layer: &LegacyLayer, render_data: &RenderData) -> (u64, u64) { - let layer_bounds = layer.bounding_transform(render_data); - let layer_bounds_size = (layer_bounds.transform_vector2((1., 0.).into()).length(), layer_bounds.transform_vector2((0., 1.).into()).length()); - - graphene_std::imaginate::pick_safe_imaginate_resolution(layer_bounds_size) -} diff --git a/document-legacy/src/document_metadata.rs b/document-legacy/src/document_metadata.rs index ca538303e0..32041af6f4 100644 --- a/document-legacy/src/document_metadata.rs +++ b/document-legacy/src/document_metadata.rs @@ -338,7 +338,7 @@ impl DocumentMetadata { .reduce(Quad::combine_bounds) } - pub fn layer_outline<'a>(&'a self, layer: LayerNodeIdentifier) -> impl Iterator> { + pub fn layer_outline(&self, layer: LayerNodeIdentifier) -> impl Iterator> { static EMPTY: Vec = Vec::new(); let click_targets = self.click_targets.get(&layer).unwrap_or(&EMPTY); click_targets.iter().map(|click_target| &click_target.subpath) @@ -361,12 +361,6 @@ impl core::fmt::Debug for LayerNodeIdentifier { } } -impl core::fmt::Display for LayerNodeIdentifier { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("Layer(node_id={})", self.to_node())) - } -} - impl LayerNodeIdentifier { pub const ROOT: Self = LayerNodeIdentifier::new_unchecked(0); @@ -387,10 +381,6 @@ impl LayerNodeIdentifier { Self::new_unchecked(node_id) } - pub fn from_path(path: &[u64], network: &NodeNetwork) -> Self { - Self::new(*path.last().unwrap(), network) - } - /// Access the node id of this layer pub fn to_node(self) -> NodeId { u64::from(self.0) - 1 @@ -572,18 +562,6 @@ impl LayerNodeIdentifier { } } -impl From for LayerNodeIdentifier { - fn from(node_id: NodeId) -> Self { - Self::new_unchecked(node_id) - } -} - -impl From for NodeId { - fn from(identifier: LayerNodeIdentifier) -> Self { - identifier.to_node() - } -} - /// Iterator over specified axis. #[derive(Clone)] pub struct AxisIter<'a> { diff --git a/document-legacy/src/error.rs b/document-legacy/src/error.rs deleted file mode 100644 index f574ba89ab..0000000000 --- a/document-legacy/src/error.rs +++ /dev/null @@ -1,13 +0,0 @@ -use super::LayerId; - -/// A set of different errors that can occur when using this crate. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum DocumentError { - LayerNotFound(Vec), - InvalidPath, - IndexOutOfBounds, - NotFolder, - NotShape, - NotLayer, - InvalidFile(String), -} diff --git a/document-legacy/src/intersection.rs b/document-legacy/src/intersection.rs deleted file mode 100644 index 1ed3aa88a9..0000000000 --- a/document-legacy/src/intersection.rs +++ /dev/null @@ -1,1216 +0,0 @@ -use crate::boolean_ops::{split_path_seg, subdivide_path_seg}; -use crate::consts::{F64LOOSE, F64PRECISE}; - -use graphene_core::uuid::ManipulatorGroupId; -use graphene_std::vector::subpath::Subpath; - -use glam::{DAffine2, DMat2, DVec2}; -use kurbo::{BezPath, CubicBez, Line, ParamCurve, ParamCurveDeriv, ParamCurveExtrema, PathSeg, Point, QuadBez, Rect, Shape, Vec2}; -use std::collections::VecDeque; -use std::ops::Mul; - -#[derive(Debug, Clone, Default, Copy)] -/// A quad defined by four vertices. -pub struct Quad([DVec2; 4]); - -impl Quad { - /// 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]; - Self([bbox[0], bbox[0] + size * DVec2::X, bbox[1], bbox[0] + size * DVec2::Y]) - } - - /// Get all the edges in the quad. - pub fn lines_glam(&self) -> impl Iterator + '_ { - [[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)) - } - - /// Get all the edges in the quad. - pub fn lines(&self) -> [Line; 4] { - [ - Line::new(to_point(self.0[0]), to_point(self.0[1])), - Line::new(to_point(self.0[1]), to_point(self.0[2])), - Line::new(to_point(self.0[2]), to_point(self.0[3])), - Line::new(to_point(self.0[3]), to_point(self.0[0])), - ] - } - - /// Generate a [BezPath] of the quad - pub fn path(&self) -> BezPath { - let mut path = kurbo::BezPath::new(); - path.move_to(to_point(self.0[0])); - path.line_to(to_point(self.0[1])); - path.line_to(to_point(self.0[2])); - path.line_to(to_point(self.0[3])); - path.close_path(); - path - } - - /// Generates a [subpath] of the quad - pub fn subpath(&self) -> Subpath { - Subpath::from_points(self.0.into_iter(), true) - } - - /// Generates the axis aligned bounding box of the quad - pub fn bounding_box(&self) -> [DVec2; 2] { - [ - self.0.into_iter().reduce(|a, b| a.min(b)).unwrap_or_default(), - self.0.into_iter().reduce(|a, b| a.max(b)).unwrap_or_default(), - ] - } - - /// Gets the center of a quad - pub fn center(&self) -> DVec2 { - self.0.iter().sum::() / 4. - } -} - -impl Mul for DAffine2 { - type Output = Quad; - - fn mul(self, rhs: Quad) -> Self::Output { - let mut output = Quad::default(); - for (i, point) in rhs.0.iter().enumerate() { - output.0[i] = self.transform_point2(*point); - } - output - } -} - -fn to_point(vec: DVec2) -> Point { - Point::new(vec.x, vec.y) -} - -/// Return `true` if `quad` intersects `shape`. -/// This is the case if any of the following conditions are true: -/// - the edges of `quad` and `shape` intersect -/// - `shape` is entirely contained within `quad` -/// - `filled` is `true` and `quad` is entirely contained within `shape`. -pub fn intersect_quad_bez_path(quad: Quad, shape: &BezPath, filled: bool) -> bool { - let mut shape = shape.clone(); - - // For filled shapes act like shape was closed even if it isn't - if filled && shape.elements().last() != Some(&kurbo::PathEl::ClosePath) { - shape.close_path(); - } - - // Check if outlines intersect - if shape.segments().any(|path_segment| quad.lines().iter().any(|line| !path_segment.intersect_line(*line).is_empty())) { - return true; - } - // Check if selection is entirely within the shape - if filled && shape.contains(to_point(quad.center())) { - return true; - } - - // Check if shape is entirely within selection - get_arbitrary_point_on_path(&shape).map(|shape_point| quad.path().contains(shape_point)).unwrap_or_default() -} - -pub fn intersect_quad_subpath(quad: Quad, subpath: &bezier_rs::Subpath, close_subpath: bool) -> bool { - let mut subpath = subpath.clone(); - - // For close_subpath shapes act like shape was closed even if it isn't - if close_subpath && !subpath.closed() { - subpath.set_closed(true); - } - - // Check if outlines intersect - if subpath - .iter() - .any(|path_segment| quad.lines_glam().any(|line| !path_segment.intersections(&line, None, None).is_empty())) - { - return true; - } - // Check if selection is entirely within the shape - if close_subpath && subpath.contains_point(quad.center()) { - return true; - } - - // Check if shape is entirely within selection - subpath - .manipulator_groups() - .first() - .map(|group| group.anchor) - .map(|shape_point| quad.path().contains(to_point(shape_point))) - .unwrap_or_default() -} - -/// Returns a point on `path`. -/// This function will usually return the first point from the path's first segment, but callers should not rely on this behavior. -pub fn get_arbitrary_point_on_path(path: &BezPath) -> Option { - path.segments().next().map(|seg| match seg { - PathSeg::Line(line) => line.p0, - PathSeg::Quad(quad) => quad.p0, - PathSeg::Cubic(cubic) => cubic.p0, - }) -} - -// -// Bezier Curve Intersection algorithm -// - -/// Each intersection has two curves. This enum helps distinguished between the two. -// TODO: refactor so actual curve data and `Origin` aren't separate -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum Origin { - Alpha, - Beta, -} - -impl std::ops::Not for Origin { - type Output = Self; - fn not(self) -> Self { - match self { - Origin::Alpha => Origin::Beta, - Origin::Beta => Origin::Alpha, - } - } -} - -#[derive(Debug, PartialEq)] -pub struct Intersect { - pub point: Point, - pub t_a: f64, - pub t_b: f64, - pub a_seg_index: i32, - pub b_seg_index: i32, - pub quality: f64, -} - -impl Intersect { - pub fn new(point: Point, t_a: f64, t_b: f64, a_seg_index: i32, b_seg_index: i32) -> Self { - Intersect { - point, - t_a, - t_b, - a_seg_index, - b_seg_index, - quality: -1.0, - } - } - - pub fn add_index(&mut self, a_index: i32, b_index: i32) { - self.a_seg_index = a_index; - self.b_seg_index = b_index; - } - - pub fn segment_index(&self, o: Origin) -> i32 { - match o { - Origin::Alpha => self.a_seg_index, - Origin::Beta => self.b_seg_index, - } - } - - pub fn t_value(&self, o: Origin) -> f64 { - match o { - Origin::Alpha => self.t_a, - Origin::Beta => self.t_b, - } - } -} - -impl From<(Point, f64, f64)> for Intersect { - fn from(place_time: (Point, f64, f64)) -> Self { - Intersect { - point: place_time.0, - t_a: place_time.1, - t_b: place_time.2, - a_seg_index: 0, - b_seg_index: 0, - quality: 0.0, - } - } -} - -#[derive(Clone, Copy)] -struct SubCurve<'a> { - pub curve: &'a PathSeg, - pub start_t: f64, - pub end_t: f64, - /// Local endpoints - local: [Point; 2], - pub extrema: &'a Vec<(Point, f64)>, -} - -impl<'a> SubCurve<'a> { - /// Extrema given by [SubCurve::subcurve_extrema], they are stored externally so they don't have to recalculated - pub fn new(parent: &'a PathSeg, extrema: &'a Vec<(Point, f64)>) -> Self { - SubCurve { - curve: parent, - start_t: 0.0, - end_t: 1.0, - local: [parent.eval(0.0), parent.eval(1.0)], - extrema, - } - } - - pub fn subcurve_extrema(parent: &PathSeg) -> Vec<(Point, f64)> { - // Extrema at endpoints should not be included here as they must be calculated for each subcurve - // Note: below filtering may filter out extrema near the endpoints - parent - .extrema() - .iter() - .filter_map(|t| if *t > F64PRECISE && *t < 1.0 - F64PRECISE { Some((parent.eval(*t), *t)) } else { None }) - .collect() - } - - fn bounding_box(&self) -> Rect { - let mut bound = Rect { - x0: self.start().x, - y0: self.start().y, - x1: self.end().x, - y1: self.end().y, - }; - self.local - .iter() - .chain( - self.extrema - .iter() - // Filter out "internal extrema which are not contained within this subcurve" - .filter_map(|place_time| if place_time.1 > self.start_t && place_time.1 < self.end_t { Some(&place_time.0) } else { None }), - ) - .for_each(|p| { - if p.x < bound.x0 { - bound.x0 = p.x; - } - if p.x > bound.x1 { - bound.x1 = p.x; - } - if p.y < bound.y0 { - bound.y0 = p.y; - } - if p.y > bound.y1 { - bound.y1 = p.y; - } - }); - bound - } - - fn available_precision(&self) -> f64 { - (self.start_t - self.end_t).abs() - } - - /// Split subcurve at `t`, as though the subcurve is a bezier curve, where `t` is a value between `0.0` and `1.0`. - fn split<'sub_life>(self: &'sub_life SubCurve<'a>, t: f64) -> (SubCurve<'a>, SubCurve<'a>) { - let split_t = self.start_t + t * (self.end_t - self.start_t); - ( - SubCurve { - curve: self.curve, - start_t: self.start_t, - end_t: split_t, - local: [self.start(), self.curve.eval(split_t)], - extrema: self.extrema, - }, - SubCurve { - curve: self.curve, - start_t: split_t, - end_t: self.end_t, - local: [self.curve.eval(split_t), self.end()], - extrema: self.extrema, - }, - ) - } - - fn start(&self) -> Point { - self.local[0] - } - - fn end(&self) -> Point { - self.local[1] - } -} - -// TODO: use the cool algorithm described in: https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.99.9678&rep=rep1&type=pdf -/// Bezier Curve Intersection Algorithm -fn path_intersections(a: &SubCurve, b: &SubCurve, intersections: &mut Vec) { - // At recursion depth 10: - // - maximum recursive execution paths = 4^10 = 1048576 - // - minimum recursive execution paths = 1 - // - up to 9 cubic Bezier intersections - // - `SubCurve` is 1/2^10 of the original curve - // - conservatively: there should never be more than 9 * 100 = 1000 total recursive execution paths - // TODO: Can probably be much less, should find a better capacity - const MAX_CALL_NUM: usize = 1000; - let mut call_buffer: VecDeque<(SubCurve, SubCurve)> = VecDeque::with_capacity(MAX_CALL_NUM); - let mut recursion = 1.0; - - fn helper<'a, 'b: 'a>(a: &'a SubCurve<'b>, b: &'a SubCurve<'b>, recursion: f64, intersections: &mut Vec, call_buffer: &'a mut VecDeque<(SubCurve<'b>, SubCurve<'b>)>) { - if let (PathSeg::Line(_), _) | (_, PathSeg::Line(_)) = (a.curve, b.curve) { - line_curve_intersections((&mut a.curve.clone(), &mut b.curve.clone()), |a, b| valid_t(a) && valid_t(b), intersections); - return; - } - // We are close enough to try linear approximation - if recursion < (1 << 10) as f64 { - // If the number of sub-curves being checked could exceed the threshold, check for overlap - if call_buffer.len() >= MAX_CALL_NUM - 4 { - overlapping_curve_intersections(a.curve, b.curve) - .into_iter() - .flatten() - .for_each(|intersect| intersections.push(intersect)); - // Regardless of whether intersections were found, need to return to prevent crashing the editor - // If no intersections are found above the curves are very close to overlapping but not quite - return; - } - if let Some(mut cross) = line_intersection(&Line { p0: a.start(), p1: a.end() }, &Line { p0: b.start(), p1: b.end() }) { - // Intersection `t_value` equals the recursive `t_value` + interpolated intersection value - cross.t_b = b.start_t + cross.t_b * recursion; - cross.t_a = a.start_t + cross.t_a * recursion; - cross.quality = guess_quality(a.curve, b.curve, &cross); - - if cross.quality <= F64LOOSE { - // Invalid intersections should still be rejected - // Rejects "valid" intersections on the non-inclusive end of a `PathSeg` - if valid_t(cross.t_a) && valid_t(cross.t_b) { - intersections.push(cross); - } - return; - } - - // Eventually the points in the curve become too close together to split the curve meaningfully - // Return the best estimate of intersection regardless of quality - // Also provides a base case and prevents infinite recursion - if a.available_precision() <= F64PRECISE || b.available_precision() <= F64PRECISE { - trace!("Precision reached"); - intersections.push(cross); - return; - } - } - // Alternate base case - // Note: may occur for the less forgiving side of a `PathSeg` endpoint intersect - if a.available_precision() <= F64PRECISE || b.available_precision() <= F64PRECISE { - trace!("Precision reached without finding intersect"); - return; - } - } - let (a1, a2) = a.split(0.5); - let (b1, b2) = b.split(0.5); - - if overlap(&a1.bounding_box(), &b1.bounding_box()) { - call_buffer.push_back((a1, b1)); - } - if overlap(&a1.bounding_box(), &b2.bounding_box()) { - call_buffer.push_back((a1, b2)); - } - if overlap(&a2.bounding_box(), &b1.bounding_box()) { - call_buffer.push_back((a2, b1)); - } - if overlap(&a2.bounding_box(), &b2.bounding_box()) { - call_buffer.push_back((a2, b2)); - } - } - - call_buffer.push_back((*a, *b)); - while !call_buffer.is_empty() { - let mut current_level = call_buffer.len(); - while current_level > 0 { - let (a, b) = call_buffer.pop_front().unwrap(); - helper(&a, &b, recursion, intersections, &mut call_buffer); - recursion /= 2.0; - current_level -= 1; - } - } -} - -/// Does nothing when neither PathSeg in `line_curve` is a line. -/// Closure `t_validate` takes the two t_values of an Intersect as arguments. -/// The order of the t_values corresponds with the order of the PathSegs in `line_curve`, -/// `t_validate` should return true for allowable intersection t_values, valid intersections will be added to `intersections`. -pub fn line_curve_intersections(line_curve: (&mut PathSeg, &mut PathSeg), t_validate: F, intersections: &mut Vec) -where - F: Fn(f64, f64) -> bool, -{ - extend_curve(line_curve.0, F64PRECISE); - extend_curve(line_curve.1, F64PRECISE); - - if let (PathSeg::Line(line), PathSeg::Line(line2)) = line_curve { - if let Some(cross) = line_intersection(line, line2) { - if t_validate(cross.t_a, cross.t_b) { - intersections.push(cross); - } - } else { - //the lines may be overlapping - overlapping_curve_intersections(line_curve.0, line_curve.1) - .into_iter() - .flatten() - .for_each(|intersect| intersections.push(intersect)) - } - } else { - let is_line_a; - let (line, curve) = match line_curve { - (PathSeg::Line(line), curve) => { - is_line_a = true; - (line, curve) - } - (curve, PathSeg::Line(line)) => { - is_line_a = false; - (line, curve) - } - _ => return, - }; - let roots = match curve { - PathSeg::Quad(quad) => Vec::from(quad_line_intersect(line, quad)), - PathSeg::Cubic(cubic) => Vec::from(cubic_line_intersect(line, cubic)), - _ => vec![], // Should never occur - }; - intersections.extend( - roots - .iter() - .filter_map(|time_option| { - if let Some(time) = time_option { - let point = match curve { - PathSeg::Cubic(cubic) => cubic.eval(*time), - PathSeg::Quad(quad) => quad.eval(*time), - _ => Point::new(0.0, 0.0), // Should never occur - }; - // The intersection point should be on the line, unless floating point math error produces bad results - let line_time = projection_on_line(line, &point); - if !t_validate(line_time, *time) { - return None; - } - if is_line_a { - Some(Intersect::from((point, line_time, *time))) - } else { - Some(Intersect::from((point, *time, line_time))) - } - } else { - None - } - }) - .collect::>(), - ); - } -} - -/// Extend the starting point of `curve` backwards along its derivative. -/// Used to make finding intersections near endpoints reliable. -pub fn extend_curve(curve: &mut PathSeg, distance: f64) { - fn extended_start(c: &mut C, d: f64) -> Point { - let mut c_prime = c.deriv().eval(0.0); - c_prime.x *= d / c_prime.distance(Point::ORIGIN); - c_prime.y *= d / c_prime.distance(Point::ORIGIN); - - // Don't apply a subtraction if c_prime is not finite (to prevent NaNs) - let es_vec = if c_prime.is_finite() { c.eval(0.0) - c_prime } else { c.eval(0.0).to_vec2() }; - - Point { x: es_vec.x, y: es_vec.y } - } - match curve { - PathSeg::Line(line) => line.p0 = extended_start(line, distance), - PathSeg::Quad(quad) => quad.p0 = extended_start(quad, distance), - PathSeg::Cubic(cubic) => cubic.p0 = extended_start(cubic, distance), - }; -} - -/// For quality Q in the worst case, the point on curve `a` corresponding to `guess` is distance Q from the point on curve `b`. -// TODO: Optimization: inline? maybe.. -fn guess_quality(a: &PathSeg, b: &PathSeg, guess: &Intersect) -> f64 { - let at_a = b.eval(guess.t_b); - let at_b = a.eval(guess.t_a); - at_a.distance(guess.point) + at_b.distance(guess.point) -} - -/// If curves overlap, returns intersections corresponding to the endpoints of the overlapping section -/// -/// # Panics -/// May panic if either curve is very short, or has endpoints which are close together -// TODO: test the case where a and b are identical -// TODO: test this, especially the overlapping curve cases which are more complex -pub fn overlapping_curve_intersections(a: &PathSeg, b: &PathSeg) -> [Option; 2] { - // To check if two curves overlap we find if the endpoints of either curve are on the other curve. - // Then, the curves are split at these points, if the resulting control polygons match the curves are the same - let b_on_a: Vec> = [point_t_value(a, &b.start()), point_t_value(a, &b.end())].into_iter().collect(); - let a_on_b: Vec> = [point_t_value(b, &a.start()), point_t_value(b, &a.end())].into_iter().collect(); - // I think, but have not mathematically shown, that if a and b are parts of the same curve then b_on_a and a_on_b should together have no more than three non-None elements. Which occurs when a or b is a cubic bezier which crosses itself - let b_on_a_not_none = b_on_a.iter().filter_map(|o| *o).count(); - let a_on_b_not_none = a_on_b.iter().filter_map(|o| *o).count(); - match b_on_a_not_none + a_on_b_not_none { - 2 | 3 => { - let (t1a, t1b, t2a, t2b): (f64, f64, f64, f64); - let to_compare = if b_on_a_not_none == 2 { - t1a = b_on_a[0].unwrap(); - t1b = 0.0; - t2a = b_on_a[1].unwrap(); - t2b = 1.0; - let mut split_at = if t1a > t2a { [t2a, t1a] } else { [t1a, t2a] }; - (*b, subdivide_path_seg(a, &mut split_at)[1].unwrap()) - } else if a_on_b_not_none == 2 { - t1a = 0.0; - t1b = a_on_b[0].unwrap(); - t2a = 1.0; - t2b = a_on_b[1].unwrap(); - let mut split_at = if t1b > t2b { [t2b, t1b] } else { [t1b, t2b] }; - (*a, subdivide_path_seg(b, &mut split_at)[1].unwrap()) - } else { - match (b_on_a[0], b_on_a[1], a_on_b[0], a_on_b[1]) { - (None, Some(a_val), None, Some(b_val)) => { - t1b = b_val; - t2b = 1.0; - t1a = 1.0; - t2a = a_val; - (split_path_seg(b, b_val).1.unwrap(), split_path_seg(a, a_val).1.unwrap()) - } - (None, Some(a_val), Some(b_val), None) => { - t1b = b_val; - t2b = 1.0; - t1a = 0.0; - t2a = a_val; - (split_path_seg(b, b_val).1.unwrap(), split_path_seg(a, a_val).0.unwrap()) - } - (Some(a_val), None, None, Some(b_val)) => { - t1b = 0.0; - t2b = b_val; - t1a = a_val; - t2a = 1.0; - (split_path_seg(b, b_val).0.unwrap(), split_path_seg(a, a_val).1.unwrap()) - } - (Some(a_val), None, Some(b_val), None) => { - t1b = 0.0; - t2b = b_val; - t1a = a_val; - t2a = 0.0; - (split_path_seg(b, b_val).0.unwrap(), split_path_seg(a, a_val).0.unwrap()) - } - _ => unreachable!("Overlapping curve intersections: too many intersections for match arm"), - } - }; - let mut to_return = [None, None]; - if match_control_polygon(&to_compare.0, &to_compare.1) { - if valid_t(t1a) && valid_t(t1b) { - to_return[0] = Some(Intersect::from((to_compare.0.start(), t1a, t1b))); - } - if valid_t(t2a) && valid_t(t2b) { - to_return[1] = Some(Intersect::from((to_compare.0.end(), t2a, t2b))); - } - } - to_return - } - _ => [None, None], - } -} - -/// Returns true if the Bezier curves described by `a` and `b` have the same control polygon. -/// The order of the polygon does not effect the result, -pub fn match_control_polygon(a: &PathSeg, b: &PathSeg) -> bool { - let mut a_polygon = get_control_polygon(a); - let mut b_polygon = get_control_polygon(b); - // Allow matching of polygons whose points are reverse ordered - if a_polygon.first().unwrap() == b_polygon.last().unwrap() && a_polygon.last().unwrap() == b_polygon.first().unwrap() { - b_polygon.reverse() - } - if a_polygon.len() == b_polygon.len() { - a_polygon.iter().eq(b_polygon.iter()) - } else { - // A sneaky higher degree Bezier curve may pose as a lower degree one - let (a_ref, b_ref) = if a_polygon.len() < b_polygon.len() { - (&mut b_polygon, &mut a_polygon) - } else { - (&mut a_polygon, &mut b_polygon) - }; - - let mut a_iter = a_ref.iter(); - for b_point in b_ref.iter() { - let a_point = a_iter.next().unwrap(); - if *a_point != *b_point { - loop { - if let Some(a_line) = a_iter.next() { - if !colinear(&[a_point, b_point, a_line]) { - return false; - } - if *a_line == *b_point { - break; - } - } else { - return false; - } - } - } - } - true - } -} - -pub fn colinear(points: &[&Point]) -> bool { - let ray = Line { p0: *points[0], p1: *points[1] }; - for p in points.iter().skip(2) { - if point_t_value(&PathSeg::Line(ray), p).is_none() { - return false; - } - } - true -} - -pub fn get_control_polygon(a: &PathSeg) -> Vec { - match a { - PathSeg::Line(Line { p0, p1 }) => vec![*p0, *p1], - PathSeg::Quad(QuadBez { p0, p1, p2 }) => vec![*p0, *p1, *p2], - PathSeg::Cubic(CubicBez { p0, p1, p2, p3 }) => vec![*p0, *p1, *p2, *p3], - } -} - -/// If `p` in on `PathSeg` `a`, returns `Some(t_value)` for `p` in the edge case where the path crosses itself, -/// and `p` is at the cross, the first t_value found (but not necessarily the smallest `t_value`) is returned. -// TODO: create a trait or something for roots to remove duplicate code -pub fn point_t_value(a: &PathSeg, p: &Point) -> Option { - match a { - PathSeg::Line(line) => { - let [mut p0, p1] = linear_bezier_coefficients(line); - p0 -= p.to_vec2(); - let x_root = linear_root(p0.x, p1.x); - let y_root = linear_root(p0.y, p1.y); - if let (Some(x_root_val), Some(y_root_val)) = (x_root, y_root) { - if (y_root_val - x_root_val).abs() < F64LOOSE { - return Some(x_root_val); - } - } - return None; - } - PathSeg::Quad(quad) => { - let [mut p0, p1, p2] = quadratic_bezier_coefficients(quad); - p0 -= p.to_vec2(); - let x_roots = quadratic_real_roots(p0.x, p1.x, p2.x); - quadratic_real_roots(p0.y, p1.y, p2.y) - .into_iter() - .find(|yt_option| { - x_roots - .iter() - .any(|xt_option| yt_option.is_some() && xt_option.is_some() && ((yt_option.unwrap() - xt_option.unwrap()).abs() < F64LOOSE)) - }) - .flatten() - } - PathSeg::Cubic(cubic) => { - let [mut p0, p1, p2, p3] = cubic_bezier_coefficients(cubic); - p0 -= p.to_vec2(); - let x_roots = cubic_real_roots(p0.x, p1.x, p2.x, p3.x); - cubic_real_roots(p0.y, p1.y, p2.y, p3.y) - .into_iter() - .find(|yt_option| { - x_roots - .iter() - .any(|xt_option| yt_option.is_some() && xt_option.is_some() && ((yt_option.unwrap() - xt_option.unwrap()).abs() < F64LOOSE)) - }) - .flatten() - } - } - .and_then(|t| if valid_t(t) { Some(t) } else { None }) -} - -pub fn intersections(a: &BezPath, b: &BezPath) -> Vec { - let mut intersections: Vec = Vec::new(); - // There is some duplicate computation of b_extrema here, but I doubt it's significant - a.segments().enumerate().for_each(|(a_index, a_seg)| { - let a_extrema = SubCurve::subcurve_extrema(&a_seg); - b.segments().enumerate().for_each(|(b_index, b_seg)| { - let b_extrema = SubCurve::subcurve_extrema(&b_seg); - let mut intersects = Vec::new(); - path_intersections(&SubCurve::new(&a_seg, &a_extrema), &SubCurve::new(&b_seg, &b_extrema), &mut intersects); - for mut path_intersection in intersects { - path_intersection.add_index(a_index.try_into().unwrap(), b_index.try_into().unwrap()); - intersections.push(path_intersection); - } - }) - }); - - intersections -} - -/// Returns the intersection point as if lines extended forever. -pub fn line_intersect_point(a: &Line, b: &Line) -> Option { - line_intersection_unchecked(a, b).map(|intersect| intersect.point) -} - -/// Returns intersection point and `t` values, treating lines as Bezier curves. -pub fn line_intersection(a: &Line, b: &Line) -> Option { - line_intersection_unchecked(a, b).filter(|intersect| valid_t(intersect.t_a) && valid_t(intersect.t_b)) -} - -/// Returns intersection point and `t` values, treating lines as rays. -pub fn line_intersection_unchecked(a: &Line, b: &Line) -> Option { - let slopes = DMat2::from_cols_array(&[(b.p1 - b.p0).x, (b.p1 - b.p0).y, (a.p0 - a.p1).x, (a.p0 - a.p1).y]); - if slopes.determinant() == 0.0 { - return None; - } - let t_values = slopes.inverse() * DVec2::new(a.p0.x - b.p0.x, a.p0.y - b.p0.y); - Some(Intersect::from((b.eval(t_values[0]), t_values[1], t_values[0]))) -} - -/// Returns the `t_value` of the point nearest to `p` on `a`. -pub fn projection_on_line(a: &Line, p: &Point) -> f64 { - let ray = a.p1.to_vec2() - a.p0.to_vec2(); - ray.dot(p.to_vec2() - a.p0.to_vec2()) / ((ray.to_point().distance(Point::ORIGIN)) * (ray.to_point().distance(Point::ORIGIN))) -} - -pub fn cubic_line_intersect(a: &Line, b: &CubicBez) -> [Option; 3] { - let l_y = a.p1.x - a.p0.x; - let l_x = a.p1.y - a.p0.y; - let bp0 = b.p0.to_vec2(); - let bp1 = b.p1.to_vec2(); - let bp2 = b.p2.to_vec2(); - let bp3 = b.p3.to_vec2(); - let c0 = bp0; - let c1 = -3.0 * bp0 + 3.0 * bp1; - let c2 = 3.0 * bp0 - 6.0 * bp1 + 3.0 * bp2; - let c3 = -1.0 * bp0 + 3.0 * bp1 - 3.0 * bp2 + bp3; - - cubic_real_roots( - -a.p0.y * l_y + a.p0.x * l_x - l_x * c0.x + l_y * c0.y, - l_y * c1.y - l_x * c1.x, - l_y * c2.y - l_x * c2.x, - l_y * c3.y - l_x * c3.x, - ) -} - -pub fn quad_line_intersect(a: &Line, b: &QuadBez) -> [Option; 2] { - let l_y = a.p1.x - a.p0.x; - let l_x = a.p1.y - a.p0.y; - let bp0 = b.p0.to_vec2(); - let bp1 = b.p1.to_vec2(); - let bp2 = b.p2.to_vec2(); - let c0 = bp0; - let c1 = -2.0 * bp0 + 2.0 * bp1; - let c2 = bp0 - 2.0 * bp1 + bp2; - - quadratic_real_roots(-a.p0.y * l_y + a.p0.x * l_x - l_x * c0.x + l_y * c0.y, l_y * c1.y - l_x * c1.x, l_y * c2.y - l_x * c2.x) -} - -/// Returns real roots to cubic equation: `f(t) = a0 + t*a1 + t^2*a2 + t^3*a3`. -/// This function uses the Cardano-Viete and Numerical Recipes algorithm, found here: https://quarticequations.com/Cubic.pdf -pub fn cubic_real_roots(mut a0: f64, mut a1: f64, mut a2: f64, a3: f64) -> [Option; 3] { - use std::f64::consts::FRAC_PI_3 as PI_3; - - a0 /= a3; - a1 /= a3; - a2 /= a3; - - let q: f64 = a1 / 3.0 - a2 * a2 / 9.0; - let r: f64 = (a1 * a2 - 3.0 * a0) / 6.0 - a2 * a2 * a2 / 27.0; - - let r2_q3 = r * r + q * q * q; - if r2_q3 > 0.0 { - #[allow(non_snake_case)] // Allow name `A` for consistency with algorithm - let A = (r.abs() + r2_q3.sqrt()).cbrt(); - - let t1 = match r { - r if r >= 0.0 => A - q / A, - r if r < 0.0 => q / A - A, - _ => 0.0, // Should never occur - }; - - [Some(t1 - a2 / 3.0), None, None] - } else { - let phi = match q > -F64PRECISE && q < F64PRECISE { - true => 0.0, - false => (r / (-q).powf(3.0 / 2.0)).acos() / 3.0, - }; - - [ - Some(2.0 * (-q).sqrt() * (phi).cos() - a2 / 3.0), - Some(2.0 * (-q).sqrt() * (phi + 2.0 * PI_3).cos() - a2 / 3.0), - Some(2.0 * (-q).sqrt() * (phi - 2.0 * PI_3).cos() - a2 / 3.0), - ] - } -} - -/// A quadratic bezier can be written `x = p0 + t*p1 + t^2*p2 + t^3*p3`, where `x`, `p0`, `p1`, `p2`, and `p3` are vectors. -/// This function returns `[p0, p1, p2, p3]`. -pub fn cubic_bezier_coefficients(cubic: &CubicBez) -> [Vec2; 4] { - let p0 = cubic.p0.to_vec2(); - let p1 = cubic.p1.to_vec2(); - let p2 = cubic.p2.to_vec2(); - let p3 = cubic.p3.to_vec2(); - let c0 = p0; - let c1 = -3.0 * p0 + 3.0 * p1; - let c2 = 3.0 * p0 - 6.0 * p1 + 3.0 * p2; - let c3 = -1.0 * p0 + 3.0 * p1 - 3.0 * p2 + p3; - [c0, c1, c2, c3] -} - -/// Returns real roots to the quadratic equation: `f(t) = a0 + t*a1 + t^2*a2`. -// TODO: make numerically stable -pub fn quadratic_real_roots(a0: f64, a1: f64, a2: f64) -> [Option; 2] { - let radicand = a1 * a1 - 4.0 * a2 * a0; - if radicand < 0.0 { - return [None, None]; - } - [Some((-a1 + radicand.sqrt()) / (2.0 * a2)), Some((-a1 - radicand.sqrt()) / (2.0 * a2))] -} - -/// A quadratic bezier can be written `x = p0 + t*p1 + t^2*p2`, where `x`, `p0`, `p1`, and `p2` are vectors. -/// This function returns `[p0, p1, p2]`. -pub fn quadratic_bezier_coefficients(quad: &QuadBez) -> [Vec2; 3] { - let p0 = quad.p0.to_vec2(); - let p1 = quad.p1.to_vec2(); - let p2 = quad.p2.to_vec2(); - let c0 = p0; - let c1 = -2.0 * p0 + 2.0 * p1; - let c2 = p0 - 2.0 * p1 + p2; - [c0, c1, c2] -} - -/// Returns the root to the linear equation: `f(t) = a0 + t*a1`. -pub fn linear_root(a0: f64, a1: f64) -> Option { - if a1 == 0.0 { - return None; - } - if a1.is_infinite() { - return Some(a0); - } - Some(-a0 / a1) -} - -/// A line can be written `x = p0 + t*p1`, where `x`, `p0` and `p1` are vectors. -/// Returns `[p0, p1]`. -pub fn linear_bezier_coefficients(line: &Line) -> [Vec2; 2] { - let p0 = line.p0.to_vec2(); - let p1 = line.p1.to_vec2(); - [p0, p1 - p0] -} - -/// Returns `true` if rectangles overlap, even if either rectangle has 0 area. -/// Uses `kurbo::Rect{x0, y0, x1, y1}` where `x0 <= x1` and `y0 <= y1`. -pub fn overlap(a: &Rect, b: &Rect) -> bool { - a.x0 <= b.x1 && a.y0 <= b.y1 && b.x0 <= a.x1 && b.y0 <= a.y1 -} - -/// Tests if a `t` value belongs to `[0.0, 1.0)`. -/// Uses [crate::consts::F64PRECISE] to allow a slightly larger range of values. -pub fn valid_t(t: f64) -> bool { - t > -F64PRECISE && t < (1.0 - F64PRECISE) -} - -// Tests that a very simple line curve intersection computes two intersections -#[test] -fn line_curve_intersection() { - let mut alpha = BezPath::new(); - - alpha.move_to((10., 10.)); - alpha.curve_to((10., 10.), (30., 10.), (30., 10.)); - alpha.curve_to((30., 10.), (30., 30.), (30., 30.)); - alpha.curve_to((30., 30.), (10., 30.), (10., 30.)); - alpha.curve_to((10., 30.), (10., 10.), (10., 10.)); - alpha.close_path(); - let mut beta = BezPath::new(); - beta.move_to((0., 0.)); - beta.line_to((20., 0.)); - beta.line_to((20., 20.)); - beta.line_to((0., 20.)); - beta.close_path(); - - assert_eq!(intersections(&alpha, &beta).len(), 2); -} - -/// Each of these tests have been visually, but not mathematically, verified. -/// These tests are all ignored because each test looks for exact floating point comparisons, so isn't tolerant to small adjustments in the algorithm. -mod tests { - // These imports are used in the tests which are #[ignore] - #[allow(unused_imports)] - use super::*; - #[allow(unused_imports)] - use crate::boolean_ops::point_on_curve; - - #[allow(unused_imports)] - use std::fs::File; - #[allow(unused_imports)] - use std::io::Write; - - /// Two intersect points, on different `PathSegs`. - #[ignore] - #[test] - fn curve_intersection_basic() { - let a = - BezPath::from_svg("M-739.7999877929688 -50.89999999999998L-676.7999877929688 -50.89999999999998L-676.7999877929688 27.100000000000023L-739.7999877929688 27.100000000000023Z").expect(""); - let b = BezPath::from_svg("M-649.2999877929688 72.10000000000002L-694.7999877929688 72.10000000000002L-694.7999877929688 0.8222196224152754L-649.2999877929688 0.8222196224152754Z").expect(""); - let expected = [ - Intersect { - point: Point::new(-676.7999877929688, 0.8222196224152754), - t_a: 0.6631053797745545, - t_b: 0.3956043956043956, - a_seg_index: 1, - b_seg_index: 2, - quality: 0.0, - }, - Intersect { - point: Point::new(-694.7999877929688, 27.10000000000003), - t_a: 0.2857142857142857, - t_b: 0.6313327906904278, - a_seg_index: 2, - b_seg_index: 1, - quality: 0.0, - }, - ]; - let result = intersections(&a, &b); - assert_eq!(expected.len(), result.len()); - assert!(expected.iter().zip(result.iter()).fold(true, |equal, (a, b)| equal && a == b)); - - let a = - BezPath::from_svg("M-663.1000244140627 -549.4740810512067C-663.1000244140627 -516.8197385387762 -690.6345122994688 -490.3481636282921 -724.6000244140627 -490.3481636282921C-758.5655365286565 -490.3481636282921 -786.1000244140627 -516.8197385387762 -786.1000244140627 -549.4740810512067C-786.1000244140627 -582.128423563637 -758.5655365286565 -608.5999984741211 -724.6000244140627 -608.5999984741211C-690.6345122994688 -608.5999984741211 -663.1000244140627 -582.128423563637 -663.1000244140627 -549.4740810512067").expect(""); - let b = BezPath::from_svg("M-834.7843084184785 -566.2292363273158C-834.7843084184785 -597.2326143708982 -805.749982642916 -622.3658181414634 -769.9343267290242 -622.3658181414634C-734.1186708151323 -622.3658181414634 -705.0843450395697 -597.2326143708982 -705.0843450395697 -566.2292363273158C-705.0843450395697 -535.2258582837335 -734.1186708151323 -510.0926545131682 -769.9343267290242 -510.0926545131682C-805.749982642916 -510.0926545131682 -834.7843084184785 -535.2258582837334 -834.7843084184785 -566.2292363273158").expect(""); - let expected = [ - Intersect { - point: Point::new(-770.4753350264828, -510.09456728384305), - t_a: 0.5368149286026136, - t_b: 0.005039955230097687, - a_seg_index: 1, - b_seg_index: 3, - quality: 0.0, - }, - Intersect { - point: Point::new(-727.3175070060661, -608.5433117814998), - t_a: 0.9731908875121124, - t_b: 0.45548363569548905, - a_seg_index: 2, - b_seg_index: 1, - quality: 0.0, - }, - ]; - let result = intersections(&a, &b); - assert_eq!(expected.len(), result.len()); - assert!(expected.iter().zip(result.iter()).fold(true, |equal, (a, b)| equal && a == b)); - - let a = - BezPath::from_svg("M-421.6225245705596 -963.1740648809906L-446.65763791855386 -1011.5169335848782L-496.72786461454245 -1011.5169335848782L-521.7629779625368 -963.1740648809906L-496.7278646145425 -914.831196177103L-446.6576379185539 -914.831196177103Z").expect(""); - let b = BezPath::from_svg("M-561.0072096972251 -1026.4026766566521L-502.81748678026577 -1026.4026766566521L-502.81748678026577 -945.8843391320225L-561.0072096972251 -945.8843391320225Z") - .expect(""); - let expected = [ - Intersect { - point: Point::new(-502.81748678026577, -999.757857413672), - t_a: 0.24324324324304233, - t_b: 0.33091616223235865, - a_seg_index: 2, - b_seg_index: 1, - quality: 0.0, - }, - Intersect { - point: Point::new(-512.8092221038916, -945.8843391320225), - t_a: 0.35764790573087535, - t_b: 0.1717096219530834, - a_seg_index: 3, - b_seg_index: 2, - quality: 0.0, - }, - ]; - let result = intersections(&a, &b); - assert_eq!(expected.len(), result.len()); - assert!(expected.iter().zip(result.iter()).fold(true, |equal, (a, b)| equal && a == b)); - } - - /// Intersect points at ends of `PathSeg`s. - #[ignore] - #[test] - fn curve_intersection_seg_edges() { - let a = - BezPath::from_svg("M-355.41190151646936 -204.93220299904385C-355.41190151646936 -164.32790664074417 -389.9224217662629 -131.4116207799262 -432.4933059063151 -131.4116207799262C-475.06419004636723 -131.4116207799262 -509.5747102961608 -164.32790664074417 -509.5747102961608 -204.93220299904382C-509.5747102961608 -245.53649935734347 -475.06419004636723 -278.45278521816147 -432.4933059063151 -278.45278521816147C-389.9224217662629 -278.45278521816147 -355.41190151646936 -245.5364993573435 -355.41190151646936 -204.93220299904385").expect(""); - let b = BezPath::from_svg("M-450.7808181070286 -146.42509665727817C-450.7808181070286 -185.38383768558714 -421.2406499166092 -216.96613737992166 -384.8010057614469 -216.96613737992166C-348.3613616062847 -216.96613737992166 -318.82119341586525 -185.38383768558714 -318.82119341586525 -146.4250966572782C-318.82119341586525 -107.46635562896924 -348.3613616062846 -75.88405593463473 -384.8010057614469 -75.88405593463472C-421.2406499166092 -75.8840559346347 -450.78081810702855 -107.46635562896921 -450.7808181070286 -146.42509665727817").expect(""); - let expected = [ - Intersect { - point: Point::new(-449.629331039312, -133.2349088577284), - t_a: 0.1383488820074267, - t_b: 0.8842879656175459, - a_seg_index: 1, - b_seg_index: 3, - quality: 0.00000000000002842170943040401, - }, - Intersect { - point: Point::new(-355.5702650533912, -209.683276560014), - t_a: 0.9606918211578568, - t_b: 0.28804943846673475, - a_seg_index: 3, - b_seg_index: 1, - quality: 0.0, - }, - ]; - let result = intersections(&a, &b); - assert_eq!(expected.len(), result.len()); - assert!(expected.iter().zip(result.iter()).fold(true, |equal, (a, b)| equal && a == b)); - } - - #[test] - #[ignore] - fn cubic_roots_intersection() { - let roots = cubic_real_roots(1.5, 1.1, 3.6, 1.0); - assert_eq!(roots.iter().filter_map(|r| *r).last().unwrap(), -3.4063481215142195); - - let roots = cubic_real_roots(-7.1, 1.1, 3.6, 1.0); - assert_eq!(roots.iter().filter_map(|r| *r).last().unwrap(), 1.115909058984805); - - let roots = cubic_real_roots(-7.1, -9.5, -4.6, 1.0); - assert_eq!(roots.iter().filter_map(|r| *r).last().unwrap(), 6.289837710873103); - - let roots = cubic_real_roots(-1.5, -3.3, 1.6, 1.0); - assert_eq!(roots, [Some(1.4330896870185468), Some(-2.636017358627879), Some(-0.3970723283906693)]); - - // TODO: 3 real root case - // for root in roots { - // if let Some(num) = root { - // print!("{num:.32}"); - // } - // } - } - - #[test] - #[ignore] - fn test_colinear() { - let p1 = Point { x: 0.0001, y: 3.0002 }; - let p2 = Point { x: 0.029, y: 3.058 }; - let p3 = Point { x: 100.237, y: 203.474 }; - let p4 = Point { x: 720.297, y: 1443.594 }; - assert!(colinear(&[&p1, &p2, &p3, &p4])); - } - - #[test] - #[ignore] - fn test_point_t_value() { - let vertical_line = Line::new(Point::new(0.0, -10.0), Point::new(0.0, 10.0)); - let t_value = point_t_value(&PathSeg::Line(vertical_line), &Point::new(0.0, 1.0)); - assert_eq!(t_value.unwrap(), 0.55); - } - - #[test] - #[ignore] - fn test_kurbo_eval_stability() { - let mut test_results = File::create("..\\target\\debug\\test_kurbo_eval_results.txt").expect(""); - let test_curve = BezPath::from_svg("M-355.41190151646936 -204.93220299904385C-355.41190151646936 -164.32790664074417 -389.9224217662629 -131.4116207799262 -432.4933059063151 -131.4116207799262C-475.06419004636723 -131.4116207799262 -509.5747102961608 -164.32790664074417 -509.5747102961608 -204.93220299904382C-509.5747102961608 -245.53649935734347 -475.06419004636723 -278.45278521816147 -432.4933059063151 -278.45278521816147C-389.9224217662629 -278.45278521816147 -355.41190151646936 -245.5364993573435 -355.41190151646936 -204.93220299904385").expect("").segments().next().unwrap(); - let mut val = 0.0; - while val < 0.0 + 1000000.0 * f64::EPSILON { - writeln!(&mut test_results, "{:?}, {:?}", val, test_curve.eval(val).x).expect(""); - val += f64::EPSILON; - } - } - - #[test] - #[ignore] - fn test_quality_stability() { - let mut test_results = File::create("..\\target\\debug\\test_quality_results.txt").expect(""); - let mut val = 0.0; - - while val < 0.0 + 1000000.0 * f64::EPSILON { - let a = Line::new(Point::new(0.0, 0.0), Point::new(1.0 + val + val, 1.0 + val + val)); - let b = Line::new(Point::new(0.0, 1.0 + val + val), Point::new(1.0 + val + val, 0.0)); - let guess = Intersect::from((Point::new(0.5 + val, 0.5 + val), 0.5, 0.5)); - - writeln!(&mut test_results, "{:?}, {:?}", val, guess_quality(&PathSeg::Line(a), &PathSeg::Line(b), &guess)).expect(""); - val += f64::EPSILON; - } - } - - #[test] - #[ignore] - fn test_quality_cubic_stability() { - let mut test_results = File::create("..\\target\\debug\\test_quality_cubic_results.txt").expect(""); - let mut val = 0.0; - - while val < 0.0 + 1000000.0 * f64::EPSILON { - let a = PathSeg::Cubic(CubicBez::new( - Point::new(0.0 + val, 0.0), - Point::new(0.25 + val, 0.661437827766), - Point::new(0.75 + val, 0.968245836552), - Point::new(1.0 + val, 1.0), - )); - let b = PathSeg::Cubic(CubicBez::new( - Point::new(0.0, 1.0), - Point::new(0.25, 0.968245836552), - Point::new(0.75, 0.661437827766), - Point::new(1.0, 0.0), - )); - let guess = Intersect::from((Point::new(0.5 + val, 0.5 + val), 0.5 + val, 0.5 + val)); - - writeln!(&mut test_results, "{:?}, {:?}", val, guess_quality(&a, &b, &guess)).expect(""); - val += f64::EPSILON; - } - } - - #[test] - #[ignore] - fn test_line_intersection_stability() { - let mut test_results = File::create("..\\target\\debug\\test_line_intersect_results.txt").expect(""); - let mut val = 0.0; - - while val < 0.0 + 1000000.0 * f64::EPSILON { - let a = Line::new(Point::new(0.0 + val, 0.0), Point::new(1.0 + val, 1.0)); - let b = Line::new(Point::new(0.0, 1.0), Point::new(1.0, 0.0)); - - let line_intersection = line_intersection(&a, &b).unwrap(); - writeln!( - &mut test_results, - "{:?}, {:?}, {:?}, {:?}", - val, - line_intersection.t_a, - line_intersection.point.x, - guess_quality(&PathSeg::Line(a), &PathSeg::Line(b), &line_intersection) - ) - .expect(""); - val += f64::EPSILON; - } - } - - #[test] - #[ignore] - fn test_line_intersection_cancellation() { - let mut test_results = File::create("..\\target\\debug\\test_line_intersection_cancellation_results.txt").expect(""); - let val = 1.0; - let mut theta = F64PRECISE; - - while theta < std::f64::consts::FRAC_PI_2 - 0.1 { - let a = Line::new(Point::new(1.0, 1.0), Point::new(1.0 + val, 1.0 + val)); - let b = Line::new(Point::new(1.0, 1.0 + val * f64::cos(theta)), Point::new(1.0 + val, 1.0 + val * f64::sin(theta))); - - let line_intersection = line_intersection(&a, &b).unwrap(); - writeln!( - &mut test_results, - "{:?}, {:?}, {:?}, {:?}", - theta, line_intersection.t_a, line_intersection.t_b, line_intersection.point.x, - ) - .expect(""); - theta += f64::powf(2.0, 20.0) * F64LOOSE; - } - } - - #[test] - #[ignore] - fn test_intersections_stability() { - let mut test_results_intersection = File::create("..\\target\\debug\\test_curve_intersections_multi_results.txt").expect(""); - let mut val = 0.0; - - while val < 0.0 + 1000000.0 * f64::EPSILON { - let a = PathSeg::Cubic(CubicBez::new( - Point::new(0.0 + val, 0.0), - Point::new(0.25 + val, 0.661437827766), - Point::new(0.75 + val, 0.968245836552), - Point::new(1.0 + val, 1.0), - )); - let b = PathSeg::Cubic(CubicBez::new( - Point::new(0.0, 1.0), - Point::new(0.25, 0.968245836552), - Point::new(0.75, 0.661437827766), - Point::new(1.0, 0.0), - )); - let aex = SubCurve::subcurve_extrema(&a); - let bex = SubCurve::subcurve_extrema(&b); - let a_sub = SubCurve::new(&a, &aex); - let b_sub = SubCurve::new(&b, &bex); - let mut intersections = Vec::new(); - path_intersections(&a_sub, &b_sub, &mut intersections); - - writeln!( - &mut test_results_intersection, - "{:?}, {:?}, {:?}, {:?}", - val, - intersections.first().unwrap().point.x, - intersections.first().unwrap().quality, - intersections.first().unwrap().t_a - ) - .expect(""); - - val += f64::EPSILON; - } - } - - #[ignore] - #[test] - fn test_test_dir() { - use std::env::current_dir; - println!("{:?}", current_dir()); - } -} diff --git a/document-legacy/src/layers/base64_serde.rs b/document-legacy/src/layers/base64_serde.rs deleted file mode 100644 index 28289a19da..0000000000 --- a/document-legacy/src/layers/base64_serde.rs +++ /dev/null @@ -1,23 +0,0 @@ -//! Basic wrapper for [`serde`] for [`base64`] encoding - -use base64::Engine; -use serde::{Deserialize, Deserializer, Serializer}; - -pub fn as_base64(key: &std::sync::Arc>, serializer: S) -> Result -where - S: Serializer, -{ - serializer.serialize_str(&base64::engine::general_purpose::STANDARD.encode(key.as_slice())) -} - -pub fn from_base64<'a, D>(deserializer: D) -> Result>, D::Error> -where - D: Deserializer<'a>, -{ - use serde::de::Error; - - String::deserialize(deserializer) - .and_then(|string| base64::engine::general_purpose::STANDARD.decode(string).map_err(|err| Error::custom(err.to_string()))) - .map(std::sync::Arc::new) - .map_err(serde::de::Error::custom) -} diff --git a/document-legacy/src/layers/folder_layer.rs b/document-legacy/src/layers/folder_layer.rs index a4007faed1..9792e23fb5 100644 --- a/document-legacy/src/layers/folder_layer.rs +++ b/document-legacy/src/layers/folder_layer.rs @@ -1,242 +1,27 @@ -use super::layer_info::{LayerData, LegacyLayer, LegacyLayerType}; -use super::style::RenderData; -use crate::intersection::Quad; -use crate::{DocumentError, LayerId}; +use super::layer_info::LegacyLayer; +use crate::document::LayerId; +use crate::DocumentError; -use graphene_core::uuid::generate_uuid; - -use glam::DVec2; use serde::{Deserialize, Serialize}; /// A layer that encapsulates other layers, including potentially more folders. /// The contained layers are rendered in the same order they are stored. #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)] pub struct FolderLegacyLayer { - /// The ID that will be assigned to the next layer that is added to the folder - next_assignment_id: LayerId, /// The IDs of the [Layer]s contained within the Folder pub layer_ids: Vec, /// The [Layer]s contained in the folder pub layers: Vec, } -impl LayerData for FolderLegacyLayer { - fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec, render_data: &RenderData) -> bool { - let mut any_child_requires_redraw = false; - for layer in &mut self.layers { - let (svg_value, requires_redraw) = layer.render(transforms, svg_defs, render_data); - *svg += svg_value; - any_child_requires_redraw = any_child_requires_redraw || requires_redraw; - } - any_child_requires_redraw - } - - fn intersects_quad(&self, quad: Quad, path: &mut Vec, intersections: &mut Vec>, render_data: &RenderData) { - for (layer, layer_id) in self.layers().iter().zip(&self.layer_ids) { - path.push(*layer_id); - layer.intersects_quad(quad, path, intersections, render_data); - path.pop(); - } - } - - fn bounding_box(&self, transform: glam::DAffine2, render_data: &RenderData) -> Option<[DVec2; 2]> { - self.layers - .iter() - .filter_map(|layer| layer.data.bounding_box(transform * layer.transform, render_data)) - .reduce(|a, b| [a[0].min(b[0]), a[1].max(b[1])]) - } -} - impl FolderLegacyLayer { - /// When a insertion ID is provided, try to insert the layer with the given ID. - /// If that ID is already used, return `None`. - /// When no insertion ID is provided, search for the next free ID and insert it with that. - /// Negative values for `insert_index` represent distance from the end - /// - /// # Example - /// ``` - /// # use graphite_document_legacy::layers::shape_layer::ShapeLegacyLayer; - /// # use graphite_document_legacy::layers::folder_layer::FolderLegacyLayer; - /// # use graphite_document_legacy::layers::style::PathStyle; - /// # use graphite_document_legacy::layers::layer_info::LegacyLayerType; - /// let mut folder = FolderLegacyLayer::default(); - /// - /// // Create two layers to be added to the folder - /// let mut shape_layer = ShapeLegacyLayer::rectangle(PathStyle::default()); - /// let mut folder_layer = FolderLegacyLayer::default(); - /// - /// folder.add_layer(shape_layer.into(), None, -1); - /// folder.add_layer(folder_layer.into(), Some(123), 0); - /// ``` - pub fn add_layer(&mut self, layer: LegacyLayer, id: Option, insert_index: isize) -> Option { - let mut insert_index = insert_index as i128; - - // Bounds check for the insert index - if insert_index < 0 { - insert_index = self.layers.len() as i128 + insert_index + 1; - } - if insert_index > self.layers.len() as i128 || insert_index < 0 { - return None; - } - - if let Some(id) = id { - self.next_assignment_id = id; - } - if self.layer_ids.contains(&self.next_assignment_id) { - return None; - } - - let id = self.next_assignment_id; - self.layers.insert(insert_index as usize, layer); - self.layer_ids.insert(insert_index as usize, id); - - // Linear probing for collision avoidance - while self.layer_ids.contains(&self.next_assignment_id) { - self.next_assignment_id += 1; - } - - Some(id) + pub fn layer(&self, layer_id: LayerId) -> Option<&LegacyLayer> { + let index = self.layer_ids.iter().position(|x| *x == layer_id).ok_or_else(|| DocumentError::LayerNotFound([layer_id].into())).ok()?; + Some(&self.layers[index]) } - /// Remove a layer with a given ID from the folder. - /// This operation will fail if `id` is not present in the folder. - /// - /// # Example - /// ``` - /// # use graphite_document_legacy::layers::folder_layer::FolderLegacyLayer; - /// let mut folder = FolderLegacyLayer::default(); - /// - /// // Try to remove a layer that does not exist - /// assert!(folder.remove_layer(123).is_err()); - /// - /// // Add another folder to the folder - /// folder.add_layer(FolderLegacyLayer::default().into(), Some(123), -1); - /// - /// // Try to remove that folder again - /// assert!(folder.remove_layer(123).is_ok()); - /// assert_eq!(folder.layers().len(), 0) - /// ``` - pub fn remove_layer(&mut self, id: LayerId) -> Result<(), DocumentError> { - let pos = self.position_of_layer(id)?; - self.layers.remove(pos); - self.layer_ids.remove(pos); - Ok(()) - } - - /// Returns a list of [LayerId]s in the folder. - pub fn list_layers(&self) -> &[LayerId] { - self.layer_ids.as_slice() - } - - /// Get references to all the [Layer]s in the folder. - pub fn layers(&self) -> &[LegacyLayer] { - self.layers.as_slice() - } - - /// Get mutable references to all the [Layer]s in the folder. - pub fn layers_mut(&mut self) -> &mut [LegacyLayer] { - self.layers.as_mut_slice() - } - - pub fn layer(&self, id: LayerId) -> Option<&LegacyLayer> { - let pos = self.position_of_layer(id).ok()?; - Some(&self.layers[pos]) - } - - pub fn layer_mut(&mut self, id: LayerId) -> Option<&mut LegacyLayer> { - let pos = self.position_of_layer(id).ok()?; - Some(&mut self.layers[pos]) - } - - pub fn generate_new_folder_ids(&mut self) { - self.next_assignment_id = generate_uuid(); - } - - /// Returns `true` if the folder contains a layer with the given [LayerId]. - /// - /// # Example - /// ``` - /// # use graphite_document_legacy::layers::folder_layer::FolderLegacyLayer; - /// let mut folder = FolderLegacyLayer::default(); - /// - /// // Search for an id that does not exist - /// assert!(!folder.folder_contains(123)); - /// - /// // Add layer with the id "123" to the folder - /// folder.add_layer(FolderLegacyLayer::default().into(), Some(123), -1); - /// - /// // Search for the id "123" - /// assert!(folder.folder_contains(123)); - /// ``` - pub fn folder_contains(&self, id: LayerId) -> bool { - self.layer_ids.contains(&id) - } - - /// Tries to find the index of a layer with the given [LayerId] within the folder. - /// This operation will fail if no layer with a matching ID is present in the folder. - /// - /// # Example - /// ``` - /// # use graphite_document_legacy::layers::folder_layer::FolderLegacyLayer; - /// let mut folder = FolderLegacyLayer::default(); - /// - /// // Search for an id that does not exist - /// assert!(folder.position_of_layer(123).is_err()); - /// - /// // Add layer with the id "123" to the folder - /// folder.add_layer(FolderLegacyLayer::default().into(), Some(123), -1); - /// folder.add_layer(FolderLegacyLayer::default().into(), Some(42), -1); - /// - /// assert_eq!(folder.position_of_layer(123), Ok(0)); - /// assert_eq!(folder.position_of_layer(42), Ok(1)); - /// ``` - pub fn position_of_layer(&self, layer_id: LayerId) -> Result { - self.layer_ids.iter().position(|x| *x == layer_id).ok_or_else(|| DocumentError::LayerNotFound([layer_id].into())) - } - - /// Tries to get a reference to a folder with the given [LayerId]. - /// This operation will return `None` if either no layer with `id` exists - /// in the folder, or the layer with matching ID is not a folder. - /// - /// # Example - /// ``` - /// # use graphite_document_legacy::layers::folder_layer::FolderLegacyLayer; - /// # use graphite_document_legacy::layers::shape_layer::ShapeLegacyLayer; - /// # use graphite_document_legacy::layers::style::PathStyle; - /// let mut folder = FolderLegacyLayer::default(); - /// - /// // Search for an id that does not exist - /// assert!(folder.folder(132).is_none()); - /// - /// // add a folder and search for it - /// folder.add_layer(FolderLegacyLayer::default().into(), Some(123), -1); - /// assert!(folder.folder(123).is_some()); - /// - /// // add a non-folder layer and search for it - /// folder.add_layer(ShapeLegacyLayer::rectangle(PathStyle::default()).into(), Some(42), -1); - /// assert!(folder.folder(42).is_none()); - /// ``` - pub fn folder(&self, id: LayerId) -> Option<&FolderLegacyLayer> { - match self.layer(id) { - Some(LegacyLayer { - data: LegacyLayerType::Folder(folder), - .. - }) => Some(folder), - _ => None, - } - } - - /// Tries to get a mutable reference to folder with the given `id`. - /// This operation will return `None` if either no layer with `id` exists - /// in the folder or the layer with matching ID is not a folder. - /// See the [FolderLegacyLayer::folder] method for a usage example. - pub fn folder_mut(&mut self, id: LayerId) -> Option<&mut FolderLegacyLayer> { - match self.layer_mut(id) { - Some(LegacyLayer { - data: LegacyLayerType::Folder(folder), - .. - }) => Some(folder), - _ => None, - } + pub fn layer_mut(&mut self, layer_id: LayerId) -> Option<&mut LegacyLayer> { + let index = self.layer_ids.iter().position(|x| *x == layer_id).ok_or_else(|| DocumentError::LayerNotFound([layer_id].into())).ok()?; + Some(&mut self.layers[index]) } } diff --git a/document-legacy/src/layers/layer_info.rs b/document-legacy/src/layers/layer_info.rs index 4ac070d836..2a3615f3fd 100644 --- a/document-legacy/src/layers/layer_info.rs +++ b/document-legacy/src/layers/layer_info.rs @@ -1,70 +1,44 @@ use super::folder_layer::FolderLegacyLayer; use super::layer_layer::LayerLegacyLayer; -use super::shape_layer::ShapeLegacyLayer; -use super::style::{PathStyle, RenderData}; -use crate::intersection::Quad; use crate::DocumentError; -use crate::LayerId; - -use graphene_core::raster::BlendMode; -use graphene_core::vector::VectorData; -use graphene_std::vector::subpath::Subpath; use core::fmt; -use glam::{DAffine2, DMat2, DVec2}; use serde::{Deserialize, Serialize}; -use std::fmt::Write; + +// =============== +// LegacyLayerType +// =============== #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] /// Represents different types of layers. pub enum LegacyLayerType { /// A layer that wraps a [FolderLegacyLayer] struct. Folder(FolderLegacyLayer), - /// A layer that wraps a [ShapeLegacyLayer] struct. Still used by the overlays system, but will be removed in the future. - Shape(ShapeLegacyLayer), /// A layer that wraps an [LayerLegacyLayer] struct. Layer(LayerLegacyLayer), } impl Default for LegacyLayerType { fn default() -> Self { - LegacyLayerType::Folder(FolderLegacyLayer::default()) + LegacyLayerType::Layer(Default::default()) } } -impl LegacyLayerType { - pub fn inner(&self) -> &dyn LayerData { - match self { - LegacyLayerType::Shape(shape) => shape, - LegacyLayerType::Folder(folder) => folder, - LegacyLayerType::Layer(layer) => layer, - } - } - - pub fn inner_mut(&mut self) -> &mut dyn LayerData { - match self { - LegacyLayerType::Shape(shape) => shape, - LegacyLayerType::Folder(folder) => folder, - LegacyLayerType::Layer(layer) => layer, - } - } -} +// ========================= +// LayerDataTypeDiscriminant +// ========================= #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, specta::Type)] pub enum LayerDataTypeDiscriminant { Folder, - Shape, Layer, - Artboard, } impl fmt::Display for LayerDataTypeDiscriminant { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { LayerDataTypeDiscriminant::Folder => write!(f, "Folder"), - LayerDataTypeDiscriminant::Shape => write!(f, "Shape"), LayerDataTypeDiscriminant::Layer => write!(f, "Layer"), - LayerDataTypeDiscriminant::Artboard => write!(f, "Artboard"), } } } @@ -75,385 +49,33 @@ impl From<&LegacyLayerType> for LayerDataTypeDiscriminant { match data { Folder(_) => LayerDataTypeDiscriminant::Folder, - Shape(_) => LayerDataTypeDiscriminant::Shape, Layer(_) => LayerDataTypeDiscriminant::Layer, } } } -// ** CONVERSIONS ** +// =========== +// LegacyLayer +// =========== -impl<'a> TryFrom<&'a mut LegacyLayer> for &'a mut Subpath { - type Error = &'static str; - /// Convert a mutable layer into a mutable [Subpath]. - fn try_from(layer: &'a mut LegacyLayer) -> Result<&'a mut Subpath, Self::Error> { - match &mut layer.data { - LegacyLayerType::Shape(layer) => Ok(&mut layer.shape), - _ => Err("Did not find any shape data in the layer"), - } - } -} - -impl<'a> TryFrom<&'a LegacyLayer> for &'a Subpath { - type Error = &'static str; - /// Convert a reference to a layer into a reference of a [Subpath]. - fn try_from(layer: &'a LegacyLayer) -> Result<&'a Subpath, Self::Error> { - match &layer.data { - LegacyLayerType::Shape(layer) => Ok(&layer.shape), - _ => Err("Did not find any shape data in the layer"), - } - } -} - -/// Defines shared behavior for every layer type. -pub trait LayerData { - /// Render the layer as an SVG tag to a given string, returning a boolean to indicate if a redraw is required next frame. - /// - /// # Example - /// ``` - /// # use graphite_document_legacy::layers::shape_layer::ShapeLegacyLayer; - /// # use graphite_document_legacy::layers::style::{Fill, PathStyle, ViewMode, RenderData}; - /// # use graphite_document_legacy::layers::layer_info::LayerData; - /// # use std::collections::HashMap; - /// - /// let mut shape = ShapeLegacyLayer::rectangle(PathStyle::new(None, Fill::None)); - /// let mut svg = String::new(); - /// - /// // Render the shape without any transforms, in normal view mode - /// # let font_cache = Default::default(); - /// let render_data = RenderData::new(&font_cache, ViewMode::Normal, None); - /// shape.render(&mut svg, &mut String::new(), &mut vec![], &render_data); - /// - /// assert_eq!( - /// svg, - /// "\ - /// \ - /// " - /// ); - /// ``` - fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec, render_data: &RenderData) -> bool; - - /// Determine the layers within this layer that intersect a given quad. - /// # Example - /// ``` - /// # use graphite_document_legacy::layers::shape_layer::ShapeLegacyLayer; - /// # use graphite_document_legacy::layers::style::{Fill, PathStyle, ViewMode, RenderData}; - /// # use graphite_document_legacy::layers::layer_info::LayerData; - /// # use graphite_document_legacy::intersection::Quad; - /// # use glam::f64::{DAffine2, DVec2}; - /// # use std::collections::HashMap; - /// - /// let mut shape = ShapeLegacyLayer::ellipse(PathStyle::new(None, Fill::None)); - /// let shape_id = 42; - /// let mut svg = String::new(); - /// - /// let quad = Quad::from_box([DVec2::ZERO, DVec2::ONE]); - /// let mut intersections = vec![]; - /// - /// let font_cache = Default::default(); - /// let render_data = RenderData::new(&font_cache, Default::default(), None); - /// shape.intersects_quad(quad, &mut vec![shape_id], &mut intersections, &render_data); - /// - /// assert_eq!(intersections, vec![vec![shape_id]]); - /// ``` - fn intersects_quad(&self, quad: Quad, path: &mut Vec, intersections: &mut Vec>, render_data: &RenderData); - - // TODO: this doctest fails because 0 != 1e-32, maybe assert difference < epsilon? - /// Calculate the bounding box for the layer's contents after applying a given transform. - /// # Example - /// ```no_run - /// # use graphite_document_legacy::layers::shape_layer::ShapeLegacyLayer; - /// # use graphite_document_legacy::layers::style::{Fill, PathStyle, RenderData}; - /// # use graphite_document_legacy::layers::layer_info::LayerData; - /// # use glam::f64::{DAffine2, DVec2}; - /// # use std::collections::HashMap; - /// let shape = ShapeLegacyLayer::ellipse(PathStyle::new(None, Fill::None)); - /// - /// // Calculate the bounding box without applying any transformations. - /// // (The identity transform maps every vector to itself.) - /// let transform = DAffine2::IDENTITY; - /// let font_cache = Default::default(); - /// let render_data = RenderData::new(&font_cache, Default::default(), None); - /// let bounding_box = shape.bounding_box(transform, &render_data); - /// - /// assert_eq!(bounding_box, Some([DVec2::ZERO, DVec2::ONE])); - /// ``` - fn bounding_box(&self, transform: glam::DAffine2, render_data: &RenderData) -> Option<[DVec2; 2]>; -} - -impl LayerData for LegacyLayerType { - fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec, render_data: &RenderData) -> bool { - self.inner_mut().render(svg, svg_defs, transforms, render_data) - } - - fn intersects_quad(&self, quad: Quad, path: &mut Vec, intersections: &mut Vec>, render_data: &RenderData) { - self.inner().intersects_quad(quad, path, intersections, render_data) - } - - fn bounding_box(&self, transform: glam::DAffine2, render_data: &RenderData) -> Option<[DVec2; 2]> { - self.inner().bounding_box(transform, render_data) - } -} - -#[derive(Serialize, Deserialize)] -#[serde(remote = "glam::DAffine2")] -struct DAffine2Ref { - pub matrix2: DMat2, - pub translation: DVec2, -} - -/// Utility function for providing a default boolean value to serde. -#[inline(always)] -fn return_true() -> bool { - true -} - -#[derive(Debug, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] pub struct LegacyLayer { - /// Whether the layer is currently visible or hidden. - pub visible: bool, /// The user-given name of the layer. pub name: Option, + /// Whether the layer is currently visible or hidden. + pub visible: bool, /// The type of layer, such as folder or shape. pub data: LegacyLayerType, - /// A transformation applied to the layer (translation, rotation, scaling, and shear). - #[serde(with = "DAffine2Ref")] - pub transform: glam::DAffine2, - /// Should the aspect ratio of this layer be preserved? - #[serde(default = "return_true")] - pub preserve_aspect: bool, - /// The center of transformations like rotation or scaling with the shift key. - /// This is in local space (so the layer's transform should be applied). - pub pivot: DVec2, - /// The cached SVG thumbnail view of the layer. - #[serde(skip)] - pub thumbnail_cache: String, - /// The cached SVG render of the layer. - #[serde(skip)] - pub cache: String, - /// The cached definition(s) used by the layer's SVG tag, placed at the top in the SVG defs tag. - #[serde(skip)] - pub svg_defs_cache: String, - /// Whether or not the [Cache](Layer::cache) and [Thumbnail Cache](Layer::thumbnail_cache) need to be updated. - #[serde(skip, default = "return_true")] - pub cache_dirty: bool, - /// The blend mode describing how this layer should composite with others underneath it. - pub blend_mode: BlendMode, - /// The opacity, in the range of 0 to 1. - pub opacity: f64, -} - -impl Default for LegacyLayer { - fn default() -> Self { - Self { - visible: Default::default(), - name: Default::default(), - data: Default::default(), - transform: Default::default(), - preserve_aspect: Default::default(), - pivot: Default::default(), - thumbnail_cache: Default::default(), - cache: Default::default(), - svg_defs_cache: Default::default(), - cache_dirty: Default::default(), - blend_mode: Default::default(), - opacity: Default::default(), - } - } } impl LegacyLayer { - pub fn new(data: LegacyLayerType, transform: [f64; 6]) -> Self { - Self { - visible: true, - name: None, - data, - transform: glam::DAffine2::from_cols_array(&transform), - preserve_aspect: true, - pivot: DVec2::splat(0.5), - cache: String::new(), - thumbnail_cache: String::new(), - svg_defs_cache: String::new(), - cache_dirty: true, - blend_mode: BlendMode::Normal, - opacity: 1., - } - } - - /// Gets a child layer of this layer, by a path. If the layer with id 1 is inside a folder with id 0, the path will be [0, 1]. - pub fn child(&self, path: &[LayerId]) -> Option<&LegacyLayer> { - let mut layer = self; - for id in path { - layer = layer.as_folder().ok()?.layer(*id)?; - } - Some(layer) - } - - /// Gets a child layer of this layer, by a path. If the layer with id 1 is inside a folder with id 0, the path will be [0, 1]. - pub fn child_mut(&mut self, path: &[LayerId]) -> Option<&mut LegacyLayer> { - let mut layer = self; - for id in path { - layer = layer.as_folder_mut().ok()?.layer_mut(*id)?; - } - Some(layer) - } - /// Iterate over the layers encapsulated by this layer. /// If the [Layer type](Layer::data) is not a folder, the only item in the iterator will be the layer itself. /// If the [Layer type](Layer::data) wraps a [Folder](LegacyLayerType::Folder), the iterator will recursively yield all the layers contained in the folder as well as potential sub-folders. - /// - /// # Example - /// ``` - /// # use graphite_document_legacy::layers::shape_layer::ShapeLegacyLayer; - /// # use graphite_document_legacy::layers::layer_info::Layer; - /// # use graphite_document_legacy::layers::style::PathStyle; - /// # use graphite_document_legacy::layers::folder_layer::FolderLegacyLayer; - /// let mut root_folder = FolderLegacyLayer::default(); - /// - /// // Add a shape to the root folder - /// let child_1: Layer = ShapeLegacyLayer::rectangle(PathStyle::default()).into(); - /// root_folder.add_layer(child_1.clone(), None, -1); - /// - /// // Add a folder containing another shape to the root layer - /// let mut child_folder = FolderLegacyLayer::default(); - /// let grandchild: Layer = ShapeLegacyLayer::rectangle(PathStyle::default()).into(); - /// child_folder.add_layer(grandchild.clone(), None, -1); - /// let child_2: Layer = child_folder.into(); - /// root_folder.add_layer(child_2.clone(), None, -1); - /// let root: Layer = root_folder.into(); - /// - /// let mut iter = root.iter(); - /// assert_eq!(iter.next(), Some(&root)); - /// assert_eq!(iter.next(), Some(&child_2)); - /// assert_eq!(iter.next(), Some(&grandchild)); - /// assert_eq!(iter.next(), Some(&child_1)); - /// assert_eq!(iter.next(), None); - /// ``` pub fn iter(&self) -> LayerIter<'_> { LayerIter { stack: vec![self] } } - /// Renders the layer, returning the result and if a redraw is required - pub fn render(&mut self, transforms: &mut Vec, svg_defs: &mut String, render_data: &RenderData) -> (&str, bool) { - if !self.visible { - return ("", false); - } - - transforms.push(self.transform); - - // Skip rendering if outside the viewport bounds - if let Some(viewport_bounds) = render_data.culling_bounds { - if let Some(bounding_box) = self.data.bounding_box(transforms.iter().cloned().reduce(|a, b| a * b).unwrap_or(DAffine2::IDENTITY), render_data) { - let is_overlapping = - viewport_bounds[0].x < bounding_box[1].x && bounding_box[0].x < viewport_bounds[1].x && viewport_bounds[0].y < bounding_box[1].y && bounding_box[0].y < viewport_bounds[1].y; - if !is_overlapping { - transforms.pop(); - self.cache.clear(); - self.cache_dirty = true; - return ("", true); - } - } - } - - let mut requires_redraw = false; - - if self.cache_dirty { - self.thumbnail_cache.clear(); - self.svg_defs_cache.clear(); - requires_redraw = self.data.render(&mut self.thumbnail_cache, &mut self.svg_defs_cache, transforms, render_data); - - self.cache.clear(); - let _ = writeln!(self.cache, r#"{}"#, self.opacity, self.blend_mode.render(), self.thumbnail_cache.as_str()); - - self.cache_dirty = false; - } - - transforms.pop(); - svg_defs.push_str(&self.svg_defs_cache); - - // If a redraw is required then set the cache to dirty. - if requires_redraw { - self.cache_dirty = true; - } - - (self.cache.as_str(), requires_redraw) - } - - pub fn intersects_quad(&self, quad: Quad, path: &mut Vec, intersections: &mut Vec>, render_data: &RenderData) { - if !self.visible { - return; - } - - let transformed_quad = self.transform.inverse() * quad; - self.data.intersects_quad(transformed_quad, path, intersections, render_data) - } - - /// Compute the bounding box of the layer after applying a transform to it. - /// - /// # Example - /// ``` - /// # use graphite_document_legacy::layers::shape_layer::ShapeLegacyLayer; - /// # use graphite_document_legacy::layers::layer_info::Layer; - /// # use graphite_document_legacy::layers::style::{PathStyle, RenderData}; - /// # use glam::DVec2; - /// # use glam::f64::DAffine2; - /// # use std::collections::HashMap; - /// // Create a rectangle with the default dimensions, from `(0|0)` to `(1|1)` - /// let layer: Layer = ShapeLegacyLayer::rectangle(PathStyle::default()).into(); - /// - /// // Apply the Identity transform, which leaves the points unchanged - /// let transform = DAffine2::IDENTITY; - /// let font_cache = Default::default(); - /// let render_data = RenderData::new(&font_cache, Default::default(), None); - /// assert_eq!( - /// layer.aabb_for_transform(transform, &render_data), - /// Some([DVec2::ZERO, DVec2::ONE]), - /// ); - /// - /// // Apply a transform that scales every point by a factor of two - /// let transform = DAffine2::from_scale(DVec2::ONE * 2.); - /// assert_eq!( - /// layer.aabb_for_transform(transform, &render_data), - /// Some([DVec2::ZERO, DVec2::ONE * 2.]), - /// ); - pub fn aabb_for_transform(&self, transform: DAffine2, render_data: &RenderData) -> Option<[DVec2; 2]> { - self.data.bounding_box(transform, render_data) - } - - pub fn aabb(&self, render_data: &RenderData) -> Option<[DVec2; 2]> { - self.aabb_for_transform(self.transform, render_data) - } - - pub fn bounding_transform(&self, render_data: &RenderData) -> DAffine2 { - let scale = match self.aabb_for_transform(DAffine2::IDENTITY, render_data) { - Some([a, b]) => { - let dimensions = b - a; - DAffine2::from_scale(dimensions) - } - None => DAffine2::IDENTITY, - }; - - self.transform * scale - } - - pub fn layerspace_pivot(&self, render_data: &RenderData) -> DVec2 { - let [mut min, max] = self.aabb_for_transform(DAffine2::IDENTITY, render_data).unwrap_or([DVec2::ZERO, DVec2::ONE]); - - // If the layer bounds are 0 in either axis then set them to one (to avoid div 0) - if (max.x - min.x) < f64::EPSILON * 1000. { - min.x = max.x - 1.; - } - if (max.y - min.y) < f64::EPSILON * 1000. { - min.y = max.y - 1.; - } - - self.pivot * (max - min) + min - } - /// Get a mutable reference to the Folder wrapped by the layer. /// This operation will fail if the [Layer type](Layer::data) is not `LegacyLayerType::Folder`. pub fn as_folder_mut(&mut self) -> Result<&mut FolderLegacyLayer, DocumentError> { @@ -463,20 +85,6 @@ impl LegacyLayer { } } - pub fn as_vector_data(&self) -> Option<&VectorData> { - match &self.data { - LegacyLayerType::Layer(layer) => layer.as_vector_data(), - _ => None, - } - } - - pub fn as_subpath_mut(&mut self) -> Option<&mut Subpath> { - match &mut self.data { - LegacyLayerType::Shape(s) => Some(&mut s.shape), - _ => None, - } - } - /// Get a reference to the Folder wrapped by the layer. /// This operation will fail if the [Layer type](Layer::data) is not `LegacyLayerType::Folder`. pub fn as_folder(&self) -> Result<&FolderLegacyLayer, DocumentError> { @@ -485,87 +93,11 @@ impl LegacyLayer { _ => Err(DocumentError::NotFolder), } } - - /// Get a mutable reference to the NodeNetwork - /// This operation will fail if the [Layer type](Layer::data) is not `LegacyLayerType::Layer`. - pub fn as_layer_network_mut(&mut self) -> Result<&mut graph_craft::document::NodeNetwork, DocumentError> { - match &mut self.data { - LegacyLayerType::Layer(layer) => Ok(&mut layer.network), - _ => Err(DocumentError::NotLayer), - } - } - - /// Get a reference to the NodeNetwork - /// This operation will fail if the [Layer type](Layer::data) is not `LegacyLayerType::Layer`. - pub fn as_layer_network(&self) -> Result<&graph_craft::document::NodeNetwork, DocumentError> { - match &self.data { - LegacyLayerType::Layer(layer) => Ok(&layer.network), - _ => Err(DocumentError::NotLayer), - } - } - - pub fn as_layer(&self) -> Result<&LayerLegacyLayer, DocumentError> { - match &self.data { - LegacyLayerType::Layer(layer) => Ok(layer), - _ => Err(DocumentError::NotLayer), - } - } - - pub fn style(&self) -> Result<&PathStyle, DocumentError> { - match &self.data { - LegacyLayerType::Shape(shape) => Ok(&shape.style), - LegacyLayerType::Layer(layer) => layer.as_vector_data().map(|vector| &vector.style).ok_or(DocumentError::NotShape), - _ => Err(DocumentError::NotShape), - } - } - - pub fn style_mut(&mut self) -> Result<&mut PathStyle, DocumentError> { - match &mut self.data { - LegacyLayerType::Shape(s) => Ok(&mut s.style), - _ => Err(DocumentError::NotShape), - } - } } -impl Clone for LegacyLayer { - fn clone(&self) -> Self { - Self { - visible: self.visible, - name: self.name.clone(), - data: self.data.clone(), - transform: self.transform, - preserve_aspect: self.preserve_aspect, - pivot: self.pivot, - cache: String::new(), - thumbnail_cache: String::new(), - svg_defs_cache: String::new(), - cache_dirty: true, - blend_mode: self.blend_mode, - opacity: self.opacity, - } - } -} - -impl From for LegacyLayer { - fn from(from: FolderLegacyLayer) -> LegacyLayer { - LegacyLayer::new(LegacyLayerType::Folder(from), DAffine2::IDENTITY.to_cols_array()) - } -} - -impl From for LegacyLayer { - fn from(from: ShapeLegacyLayer) -> LegacyLayer { - LegacyLayer::new(LegacyLayerType::Shape(from), DAffine2::IDENTITY.to_cols_array()) - } -} - -impl<'a> IntoIterator for &'a LegacyLayer { - type Item = &'a LegacyLayer; - type IntoIter = LayerIter<'a>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} +// ========= +// LayerIter +// ========= /// An iterator over the layers encapsulated by this layer. /// See [Layer::iter] for more information. @@ -581,7 +113,7 @@ impl<'a> Iterator for LayerIter<'a> { match self.stack.pop() { Some(layer) => { if let LegacyLayerType::Folder(folder) = &layer.data { - let layers = folder.layers(); + let layers = folder.layers.as_slice(); self.stack.extend(layers); }; Some(layer) diff --git a/document-legacy/src/layers/layer_layer.rs b/document-legacy/src/layers/layer_layer.rs index 5c8665cb1a..c09f142a25 100644 --- a/document-legacy/src/layers/layer_layer.rs +++ b/document-legacy/src/layers/layer_layer.rs @@ -1,172 +1,11 @@ -use super::layer_info::LayerData; -use super::style::{RenderData, ViewMode}; -use crate::intersection::{intersect_quad_bez_path, intersect_quad_subpath, Quad}; -use crate::LayerId; - -use glam::{DAffine2, DMat2, DVec2}; -use graphene_core::vector::VectorData; -use graphene_core::SurfaceId; -use kurbo::{Affine, BezPath, Shape as KurboShape}; use serde::{Deserialize, Serialize}; -use std::fmt::Write; -#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] -pub enum CachedOutputData { - #[default] - None, - BlobURL(String), - VectorPath(Box), - SurfaceId(SurfaceId), - Svg(String), -} +// ================ +// LayerLegacyLayer +// ================ #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] pub struct LayerLegacyLayer { /// The document node network that this layer contains pub network: graph_craft::document::NodeNetwork, - - #[serde(skip)] - pub cached_output_data: CachedOutputData, -} - -impl LayerData for LayerLegacyLayer { - fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec, render_data: &RenderData) -> bool { - let transform = self.transform(transforms, render_data.view_mode); - let inverse = transform.inverse(); - - let (width, height) = (transform.transform_vector2(DVec2::new(1., 0.)).length(), transform.transform_vector2(DVec2::new(0., 1.)).length()); - - if !inverse.is_finite() { - let _ = write!(svg, ""); - return false; - } - - let _ = writeln!(svg, r#""#); - - let matrix = (transform * DAffine2::from_scale((width, height).into()).inverse()) - .to_cols_array() - .iter() - .enumerate() - .fold(String::new(), |val, (i, entry)| val + &(entry.to_string() + if i == 5 { "" } else { "," })); - - // Render any paths if they exist - match &self.cached_output_data { - CachedOutputData::VectorPath(vector_data) => { - let layer_bounds = vector_data.bounding_box().unwrap_or_default(); - let transformed_bounds = vector_data.bounding_box_with_transform(transform).unwrap_or_default(); - - let _ = write!(svg, ""); - } - CachedOutputData::BlobURL(blob_url) => { - // Render the image if it exists - let _ = write!( - svg, - r#""#, - width.abs(), - height.abs(), - blob_url, - matrix - ); - } - CachedOutputData::SurfaceId(SurfaceId(id)) => { - // Render the image if it exists - let _ = write!( - svg, - r#" -
- "#, - width.abs(), - height.abs(), - matrix, - id - ); - } - CachedOutputData::Svg(new_svg) => svg.push_str(new_svg), - _ => { - // Render a dotted blue outline if there is no image or vector data - let _ = write!( - svg, - r#""#, - width.abs(), - height.abs(), - matrix, - ); - } - } - - let _ = svg.write_str(r#"
"#); - - false - } - - fn bounding_box(&self, transform: glam::DAffine2, _render_data: &RenderData) -> Option<[DVec2; 2]> { - if let CachedOutputData::VectorPath(vector_data) = &self.cached_output_data { - return vector_data.bounding_box_with_transform(transform); - } - - let mut path = self.bounds(); - - if transform.matrix2 == DMat2::ZERO { - return None; - } - path.apply_affine(glam_to_kurbo(transform)); - - let kurbo::Rect { x0, y0, x1, y1 } = path.bounding_box(); - Some([(x0, y0).into(), (x1, y1).into()]) - } - - fn intersects_quad(&self, quad: Quad, path: &mut Vec, intersections: &mut Vec>, _render_data: &RenderData) { - if let CachedOutputData::VectorPath(vector_data) = &self.cached_output_data { - let filled_style = vector_data.style.fill().is_some(); - if vector_data.subpaths.iter().any(|subpath| intersect_quad_subpath(quad, subpath, filled_style || subpath.closed())) { - intersections.push(path.clone()); - } - } else if intersect_quad_bez_path(quad, &self.bounds(), true) { - intersections.push(path.clone()); - } - } -} - -impl LayerLegacyLayer { - pub fn transform(&self, transforms: &[DAffine2], mode: ViewMode) -> DAffine2 { - let start = match mode { - ViewMode::Outline => 0, - _ => (transforms.len() as i32 - 1).max(0) as usize, - }; - transforms.iter().skip(start).cloned().reduce(|a, b| a * b).unwrap_or(DAffine2::IDENTITY) - } - - fn bounds(&self) -> BezPath { - kurbo::Rect::from_origin_size(kurbo::Point::ZERO, kurbo::Size::new(1., 1.)).to_path(0.) - } - - pub fn as_vector_data(&self) -> Option<&VectorData> { - if let CachedOutputData::VectorPath(vector_data) = &self.cached_output_data { - Some(vector_data) - } else { - None - } - } - pub fn as_blob_url(&self) -> Option<&String> { - if let CachedOutputData::BlobURL(blob_url) = &self.cached_output_data { - Some(blob_url) - } else { - None - } - } -} - -fn glam_to_kurbo(transform: DAffine2) -> Affine { - Affine::new(transform.to_cols_array()) } diff --git a/document-legacy/src/layers/mod.rs b/document-legacy/src/layers/mod.rs index 5f3efbde23..86bfb286d6 100644 --- a/document-legacy/src/layers/mod.rs +++ b/document-legacy/src/layers/mod.rs @@ -3,7 +3,6 @@ //! Layers allow the user to mutate part of the document while leaving the rest unchanged. //! There are currently these different types of layers: //! * [Folder layers](folder_layer::FolderLegacyLayer), which encapsulate sub-layers -//! * [Shape layers](shape_layer::ShapeLegacyLayer), which contain generic SVG [``](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path)s (deprecated but still used by the overlays system). //! * [Layer layers](layer_layer::LayerLegacyLayer), which contain a node graph layer //! //! Refer to the module-level documentation for detailed information on each layer. @@ -13,21 +12,9 @@ //! When different layers overlap, they are blended together according to the [BlendMode](blend_mode::BlendMode) //! using the CSS [`mix-blend-mode`](https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode) property and the layer opacity. -pub mod base64_serde; /// Contains the [FolderLegacyLayer](folder_layer::FolderLegacyLayer) type that encapsulates other layers, including more folders. pub mod folder_layer; /// Contains the base [Layer](layer_info::Layer) type, an abstraction over the different types of layers. pub mod layer_info; /// Contains the [LayerLegacyLayer](nodegraph_layer::LayerLegacyLayer) type that contains a node graph. pub mod layer_layer; -// TODO: Remove shape layers after rewriting the overlay system -/// Contains the [ShapeLegacyLayer](shape_layer::ShapeLegacyLayer) type, a generic SVG element defined using Bezier paths. -pub mod shape_layer; - -mod render_data; -pub use render_data::RenderData; - -pub mod style { - pub use super::RenderData; - pub use graphene_core::vector::style::*; -} diff --git a/document-legacy/src/layers/render_data.rs b/document-legacy/src/layers/render_data.rs deleted file mode 100644 index ac19ddc86a..0000000000 --- a/document-legacy/src/layers/render_data.rs +++ /dev/null @@ -1,22 +0,0 @@ -use super::style::ViewMode; -use graphene_std::text::FontCache; - -use glam::DVec2; - -/// Contains metadata for rendering the document as an svg -#[derive(Debug, Clone, Copy)] -pub struct RenderData<'a> { - pub font_cache: &'a FontCache, - pub view_mode: ViewMode, - pub culling_bounds: Option<[DVec2; 2]>, -} - -impl<'a> RenderData<'a> { - pub fn new(font_cache: &'a FontCache, view_mode: ViewMode, culling_bounds: Option<[DVec2; 2]>) -> Self { - Self { - font_cache, - view_mode, - culling_bounds, - } - } -} diff --git a/document-legacy/src/layers/shape_layer.rs b/document-legacy/src/layers/shape_layer.rs deleted file mode 100644 index a7193f08e0..0000000000 --- a/document-legacy/src/layers/shape_layer.rs +++ /dev/null @@ -1,176 +0,0 @@ -use super::layer_info::LayerData; -use super::style::{self, PathStyle, RenderData, ViewMode}; -use crate::intersection::{intersect_quad_bez_path, Quad}; -use crate::LayerId; - -use graphene_std::vector::subpath::Subpath; - -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 -/// [``](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path) -/// elements inside a -/// [``](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, specta::Type)] -pub struct ShapeLegacyLayer { - /// The geometry of the layer. - pub shape: Subpath, - /// 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 ShapeLegacyLayer { - fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec, render_data: &RenderData) -> bool { - let mut subpath = self.shape.clone(); - - let layer_bounds = subpath.bounding_box().unwrap_or_default(); - - let transform = self.transform(transforms, render_data.view_mode); - if !transform.is_finite() || transform.matrix2.determinant() == 0. { - let _ = write!(svg, ""); - return false; - } - let inverse = transform.inverse(); - - subpath.apply_affine(transform); - - let transformed_bounds = subpath.bounding_box().unwrap_or_default(); - - let _ = writeln!(svg, r#""#); - let _ = write!( - svg, - r#""#, - subpath.to_svg(), - self.style.render(render_data.view_mode, svg_defs, transform, layer_bounds, transformed_bounds) - ); - let _ = svg.write_str(""); - - false - } - - fn bounding_box(&self, transform: glam::DAffine2, _render_data: &RenderData) -> Option<[DVec2; 2]> { - let mut subpath = self.shape.clone(); - if transform.matrix2 == DMat2::ZERO || !transform.is_finite() { - return None; - } - subpath.apply_affine(transform); - - subpath.bounding_box() - } - - fn intersects_quad(&self, quad: Quad, path: &mut Vec, intersections: &mut Vec>, _render_data: &RenderData) { - let filled = self.style.fill().is_some() || self.shape.manipulator_groups().last().filter(|manipulator_group| manipulator_group.is_close()).is_some(); - if intersect_quad_bez_path(quad, &(&self.shape).into(), filled) { - intersections.push(path.clone()); - } - } -} - -impl ShapeLegacyLayer { - /// Construct a new [ShapeLegacyLayer] with the specified [Subpath] and [PathStyle] - pub fn new(shape: Subpath, 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: Subpath::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: Subpath::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: Subpath::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: Subpath::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>, style: PathStyle) -> Self { - Self { - shape: Subpath::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: - pub fn spline(points: Vec>, style: PathStyle) -> Self { - Self { - shape: Subpath::new_spline(points), - style, - render_index: 0, - } - } -} diff --git a/document-legacy/src/lib.rs b/document-legacy/src/lib.rs index 5a76b65ffa..dbb2c49315 100644 --- a/document-legacy/src/lib.rs +++ b/document-legacy/src/lib.rs @@ -1,18 +1,16 @@ // `macro_use` puts the log macros (`error!`, `warn!`, `debug!`, `info!` and `trace!`) in scope for the crate -#[macro_use] +// #[macro_use] extern crate log; -pub mod boolean_ops; -pub mod consts; pub mod document; pub mod document_metadata; -pub mod error; -pub mod intersection; pub mod layers; -pub mod operation; -pub mod response; -pub use document::LayerId; -pub use error::DocumentError; -pub use operation::Operation; -pub use response::DocumentResponse; +/// A set of different errors that can occur when using this crate. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum DocumentError { + LayerNotFound(Vec), + InvalidPath, + NotFolder, + InvalidFile(String), +} diff --git a/document-legacy/src/operation.rs b/document-legacy/src/operation.rs deleted file mode 100644 index 21752628d0..0000000000 --- a/document-legacy/src/operation.rs +++ /dev/null @@ -1,176 +0,0 @@ -use crate::layers::layer_info::LegacyLayer; -use crate::layers::style::{self, Stroke}; -use crate::LayerId; - -use graphene_core::raster::BlendMode; -use graphene_std::vector::subpath::Subpath; - -use serde::{Deserialize, Serialize}; -use std::collections::hash_map::DefaultHasher; -use std::hash::{Hash, Hasher}; - -#[repr(C)] -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] -// TODO: Rename all instances of `path` to `layer_path` -/// Operations that can be performed to mutate the document. -pub enum Operation { - // TODO: Remove - AddFrame { - path: Vec, - insert_index: isize, - transform: [f64; 6], - network: graph_craft::document::NodeNetwork, - }, - /// Sets a blob URL as the image source for an Image or Imaginate layer type. - /// **Be sure to call `FrontendMessage::TriggerRevokeBlobUrl` together with this.** - SetLayerBlobUrl { - layer_path: Vec, - blob_url: String, - resolution: (f64, f64), - }, - /// Clears the image to leave the layer un-rendered. - /// **Be sure to call `FrontendMessage::TriggerRevokeBlobUrl` together with this.** - ClearBlobURL { - path: Vec, - }, - SetPivot { - layer_path: Vec, - pivot: (f64, f64), - }, - DeleteLayer { - path: Vec, - }, - DuplicateLayer { - path: Vec, - }, - RenameLayer { - layer_path: Vec, - new_name: String, - }, - InsertLayer { - layer: Box, - destination_path: Vec, - insert_index: isize, - duplicating: bool, - }, - CreateFolder { - path: Vec, - insert_index: isize, - }, - TransformLayer { - path: Vec, - transform: [f64; 6], - }, - TransformLayerInViewport { - path: Vec, - transform: [f64; 6], - }, - SetLayerTransformInViewport { - path: Vec, - transform: [f64; 6], - }, - SetShapePath { - path: Vec, - subpath: Subpath, - }, - SetVectorData { - path: Vec, - vector_data: graphene_core::vector::VectorData, - }, - SetSurface { - path: Vec, - surface_id: graphene_core::SurfaceId, - }, - SetSvg { - path: Vec, - svg: String, - }, - TransformLayerInScope { - path: Vec, - transform: [f64; 6], - scope: [f64; 6], - }, - SetLayerTransformInScope { - path: Vec, - transform: [f64; 6], - scope: [f64; 6], - }, - SetLayerScaleAroundPivot { - path: Vec, - new_scale: (f64, f64), - }, - SetLayerTransform { - path: Vec, - transform: [f64; 6], - }, - SetLayerVisibility { - path: Vec, - visible: bool, - }, - SetLayerPreserveAspect { - layer_path: Vec, - preserve_aspect: bool, - }, - SetLayerBlendMode { - path: Vec, - blend_mode: BlendMode, - }, - SetLayerOpacity { - path: Vec, - opacity: f64, - }, - SetLayerFill { - path: Vec, - fill: style::Fill, - }, - SetLayerStroke { - path: Vec, - stroke: Stroke, - }, - - // The following are used only by the legacy overlays system - AddEllipse { - path: Vec, - insert_index: isize, - transform: [f64; 6], - style: style::PathStyle, - }, - AddRect { - path: Vec, - insert_index: isize, - transform: [f64; 6], - style: style::PathStyle, - }, - AddLine { - path: Vec, - insert_index: isize, - transform: [f64; 6], - style: style::PathStyle, - }, - AddPolyline { - path: Vec, - insert_index: isize, - transform: [f64; 6], - style: style::PathStyle, - points: Vec<(f64, f64)>, - }, - AddShape { - path: Vec, - insert_index: isize, - transform: [f64; 6], - style: style::PathStyle, - subpath: Subpath, - }, - SetLayerStyle { - path: Vec, - style: style::PathStyle, - }, -} - -impl Operation { - pub fn pseudo_hash(&self) -> u64 { - let mut s = DefaultHasher::new(); - std::mem::discriminant(self).hash(&mut s); - s.finish() - } -} diff --git a/document-legacy/src/response.rs b/document-legacy/src/response.rs deleted file mode 100644 index e681246433..0000000000 --- a/document-legacy/src/response.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::LayerId; - -use serde::{Deserialize, Serialize}; -use std::fmt; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] -#[repr(C)] -pub enum DocumentResponse { - /// For the purposes of rendering, this triggers a re-render of the entire document. - DocumentChanged, - FolderChanged { - path: Vec, - }, - CreatedLayer { - path: Vec, - is_selected: bool, - }, - DeletedLayer { - path: Vec, - }, - /// Triggers an update of the layer in the layer panel. - LayerChanged { - path: Vec, - }, - DeletedSelectedManipulatorPoints, -} - -impl fmt::Display for DocumentResponse { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - DocumentResponse::DocumentChanged { .. } => write!(f, "DocumentChanged"), - DocumentResponse::FolderChanged { .. } => write!(f, "FolderChanged"), - DocumentResponse::CreatedLayer { .. } => write!(f, "CreatedLayer"), - DocumentResponse::LayerChanged { .. } => write!(f, "LayerChanged"), - DocumentResponse::DeletedLayer { .. } => write!(f, "DeleteLayer"), - DocumentResponse::DeletedSelectedManipulatorPoints { .. } => write!(f, "DeletedSelectedManipulatorPoints"), - } - } -} diff --git a/editor/src/dispatcher.rs b/editor/src/dispatcher.rs index 0a95b9cb86..f94e4c5c37 100644 --- a/editor/src/dispatcher.rs +++ b/editor/src/dispatcher.rs @@ -35,7 +35,7 @@ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[ MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::RenderDocument)), MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::NodeGraph(NodeGraphMessageDiscriminant::SendGraph))), MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel( - PropertiesPanelMessageDiscriminant::ResendActiveProperties, + PropertiesPanelMessageDiscriminant::Refresh, ))), MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::FolderChanged)), MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::DocumentStructureChanged)), @@ -262,8 +262,8 @@ mod test { use crate::messages::tool::tool_messages::tool_prelude::ToolType; use crate::test_utils::EditorTestUtils; + use document_legacy::document::LayerId; use document_legacy::document_metadata::LayerNodeIdentifier; - use document_legacy::LayerId; use graphene_core::raster::color::Color; fn init_logger() { @@ -475,10 +475,6 @@ mod test { ) }; - editor.handle_message(DocumentMessage::SetSelectedLayers { - replacement_selected_layers: sorted_layers[..2].to_vec(), - }); - editor.handle_message(DocumentMessage::SelectedLayersRaise); let (all, non_selected, selected) = verify_order(editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut().unwrap()); assert_eq!(all, non_selected.into_iter().chain(selected).collect::>()); diff --git a/editor/src/messages/frontend/utility_types.rs b/editor/src/messages/frontend/utility_types.rs index fff7e87d14..b35ad3f9f5 100644 --- a/editor/src/messages/frontend/utility_types.rs +++ b/editor/src/messages/frontend/utility_types.rs @@ -1,5 +1,5 @@ +use document_legacy::document::LayerId; use document_legacy::document_metadata::LayerNodeIdentifier; -use document_legacy::LayerId; use serde::{Deserialize, Serialize}; #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, specta::Type)] diff --git a/editor/src/messages/input_mapper/utility_types/input_keyboard.rs b/editor/src/messages/input_mapper/utility_types/input_keyboard.rs index 5fe7ce8eff..796ed25c36 100644 --- a/editor/src/messages/input_mapper/utility_types/input_keyboard.rs +++ b/editor/src/messages/input_mapper/utility_types/input_keyboard.rs @@ -1,8 +1,6 @@ use crate::messages::portfolio::utility_types::KeyboardPlatformLayout; use crate::messages::prelude::*; -pub use document_legacy::DocumentResponse; - use bitflags::bitflags; use serde::{Deserialize, Serialize}; use std::fmt::{self, Display, Formatter}; diff --git a/editor/src/messages/layout/layout_message_handler.rs b/editor/src/messages/layout/layout_message_handler.rs index 500870b206..2fdae29347 100644 --- a/editor/src/messages/layout/layout_message_handler.rs +++ b/editor/src/messages/layout/layout_message_handler.rs @@ -2,7 +2,7 @@ use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::prelude::*; -use document_legacy::LayerId; +use document_legacy::document::LayerId; use graphene_core::raster::color::Color; use graphene_core::text::Font; diff --git a/editor/src/messages/layout/utility_types/widgets/input_widgets.rs b/editor/src/messages/layout/utility_types/widgets/input_widgets.rs index 79b1438b61..0f7a7faf7b 100644 --- a/editor/src/messages/layout/utility_types/widgets/input_widgets.rs +++ b/editor/src/messages/layout/utility_types/widgets/input_widgets.rs @@ -1,8 +1,8 @@ use crate::messages::input_mapper::utility_types::misc::ActionKeys; use crate::messages::layout::utility_types::widget_prelude::*; +use document_legacy::document::LayerId; use document_legacy::layers::layer_info::LayerDataTypeDiscriminant; -use document_legacy::LayerId; use graphene_core::raster::curve::Curve; use graphite_proc_macros::WidgetBuilder; diff --git a/editor/src/messages/portfolio/document/document_message.rs b/editor/src/messages/portfolio/document/document_message.rs index 1f9537fb27..5378ac8e8e 100644 --- a/editor/src/messages/portfolio/document/document_message.rs +++ b/editor/src/messages/portfolio/document/document_message.rs @@ -4,13 +4,12 @@ use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, use crate::messages::prelude::*; use document_legacy::document::Document as DocumentLegacy; +use document_legacy::document::LayerId; use document_legacy::document_metadata::LayerNodeIdentifier; -use document_legacy::layers::style::ViewMode; -use document_legacy::LayerId; -use document_legacy::Operation as DocumentOperation; use graph_craft::document::NodeId; use graphene_core::raster::BlendMode; use graphene_core::raster::Image; +use graphene_core::vector::style::ViewMode; use graphene_core::Color; use serde::{Deserialize, Serialize}; @@ -20,8 +19,6 @@ use serde::{Deserialize, Serialize}; pub enum DocumentMessage { // Sub-messages #[remain::unsorted] - DispatchOperation(Box), - #[remain::unsorted] #[child] Navigation(NavigationMessage), #[remain::unsorted] @@ -52,9 +49,6 @@ pub enum DocumentMessage { }, ClearLayerTree, CommitTransaction, - CopyToClipboardLayerImageOutput { - layer_path: Vec, - }, CreateEmptyFolder { parent: LayerNodeIdentifier, }, @@ -64,14 +58,9 @@ pub enum DocumentMessage { }, DeleteSelectedLayers, DeselectAllLayers, - DirtyRenderDocument, - DirtyRenderDocumentInOutlineView, DocumentHistoryBackward, DocumentHistoryForward, DocumentStructureChanged, - DownloadLayerImageOutput { - layer_path: Vec, - }, DuplicateSelectedLayers, FlipSelectedLayers { flip_axis: FlipAxis, @@ -79,7 +68,6 @@ pub enum DocumentMessage { FolderChanged { affected_folder_path: Vec, }, - FrameClear, GroupSelectedLayers, ImaginateClear { layer_path: Vec, @@ -116,14 +104,9 @@ pub enum DocumentMessage { RenameDocument { new_name: String, }, - RenameLayer { - layer_path: Vec, - new_name: String, - }, RenderDocument, RenderRulers, RenderScrollbars, - RollbackTransaction, SaveDocument, SelectAllLayers, SelectedLayersLower, @@ -141,16 +124,6 @@ pub enum DocumentMessage { SetBlendModeForSelectedLayers { blend_mode: BlendMode, }, - SetImageBlobUrl { - layer_path: Vec, - blob_url: String, - resolution: (f64, f64), - document_id: u64, - }, - SetLayerExpansion { - layer_path: Vec, - set_expanded: bool, - }, SetOpacityForSelectedLayers { opacity: f64, }, @@ -160,9 +133,6 @@ pub enum DocumentMessage { SetRangeSelectionLayer { new_layer: Option, }, - SetSelectedLayers { - replacement_selected_layers: Vec>, - }, SetSnapping { snapping_enabled: Option, bounding_box_snapping: Option, @@ -181,23 +151,7 @@ pub enum DocumentMessage { UpdateDocumentTransform { transform: glam::DAffine2, }, - UpdateLayerMetadata { - layer_path: Vec, - layer_metadata: LayerMetadata, - }, ZoomCanvasTo100Percent, ZoomCanvasTo200Percent, ZoomCanvasToFitAll, } - -impl From for DocumentMessage { - fn from(operation: DocumentOperation) -> DocumentMessage { - DocumentMessage::DispatchOperation(Box::new(operation)) - } -} - -impl From for Message { - fn from(operation: DocumentOperation) -> Message { - DocumentMessage::DispatchOperation(Box::new(operation)).into() - } -} diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index f457ce1509..2373895aa9 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -17,14 +17,15 @@ use crate::messages::tool::utility_types::ToolType; use crate::node_graph_executor::NodeGraphExecutor; use document_legacy::document::Document as DocumentLegacy; +use document_legacy::document::LayerId; use document_legacy::document_metadata::LayerNodeIdentifier; -use document_legacy::layers::layer_info::{LayerDataTypeDiscriminant, LegacyLayerType}; -use document_legacy::layers::style::{RenderData, ViewMode}; -use document_legacy::{DocumentError, DocumentResponse, LayerId, Operation as DocumentOperation}; +use document_legacy::layers::layer_info::LayerDataTypeDiscriminant; +use document_legacy::DocumentError; use graph_craft::document::value::TaggedValue; use graph_craft::document::{NodeInput, NodeNetwork}; use graphene_core::raster::BlendMode; use graphene_core::raster::ImageFrame; +use graphene_core::vector::style::ViewMode; use glam::{DAffine2, DVec2}; use serde::{Deserialize, Serialize}; @@ -121,46 +122,10 @@ impl MessageHandler> for DocumentMessageHand } = document_inputs; use DocumentMessage::*; - let render_data = RenderData::new(&persistent_data.font_cache, self.view_mode, Some(ipp.document_bounds())); - #[remain::sorted] match message { // Sub-messages #[remain::unsorted] - DispatchOperation(op) => { - match self.document_legacy.handle_operation(*op) { - Ok(Some(document_responses)) => { - for response in document_responses { - match &response { - DocumentResponse::FolderChanged { path } => responses.add(FolderChanged { affected_folder_path: path.clone() }), - DocumentResponse::DeletedLayer { path } => { - self.layer_metadata.remove(path); - } - DocumentResponse::LayerChanged { path } => responses.add(LayerChanged { affected_layer_path: path.clone() }), - DocumentResponse::CreatedLayer { .. } => { - unimplemented!("We should no longer be creating layers in the document and should instead be using the node graph.") - } - DocumentResponse::DocumentChanged => responses.add(RenderDocument), - DocumentResponse::DeletedSelectedManipulatorPoints => { - // Clear Properties panel after deleting all points by updating backend widget state. - responses.add(LayoutMessage::SendLayout { - layout: Layout::WidgetLayout(WidgetLayout::new(vec![])), - layout_target: LayoutTarget::PropertiesOptions, - }); - responses.add(LayoutMessage::SendLayout { - layout: Layout::WidgetLayout(WidgetLayout::new(vec![])), - layout_target: LayoutTarget::PropertiesSections, - }); - } - }; - responses.add(BroadcastEvent::DocumentIsDirty); - } - } - Err(e) => error!("DocumentError: {e:?}"), - Ok(_) => (), - } - } - #[remain::unsorted] Navigation(message) => { let document_bounds = self.metadata().document_bounds_viewport_space(); self.navigation_handler.process_message( @@ -212,7 +177,7 @@ impl MessageHandler> for DocumentMessageHand } AddSelectedLayers { additional_layers } => { for layer_path in &additional_layers { - responses.extend(self.select_layer(layer_path, &render_data)); + responses.extend(self.select_layer(layer_path)); } // TODO: Correctly update layer panel in clear_selection instead of here @@ -269,15 +234,6 @@ impl MessageHandler> for DocumentMessageHand }); } CommitTransaction => (), - CopyToClipboardLayerImageOutput { layer_path } => { - let layer = self.document_legacy.layer(&layer_path).ok(); - - let blob_url = layer.and_then(|layer| layer.as_layer().ok()).and_then(|layer_layer| layer_layer.as_blob_url()).cloned(); - - if let Some(blob_url) = blob_url { - responses.add(FrontendMessage::TriggerCopyToClipboardBlobUrl { blob_url }); - } - } CreateEmptyFolder { parent } => { let id = generate_uuid(); @@ -295,7 +251,6 @@ impl MessageHandler> for DocumentMessageHand DeleteLayer { layer_path } => { responses.add(GraphOperationMessage::DeleteLayer { id: layer_path[0] }); responses.add_front(BroadcastEvent::ToolAbort); - responses.add(PropertiesPanelMessage::CheckSelectedWasDeleted { path: layer_path }); } DeleteSelectedLayers => { self.backup(responses); @@ -313,40 +268,19 @@ impl MessageHandler> for DocumentMessageHand responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] }); self.layer_range_selection_reference = None; } - DirtyRenderDocument => { - // Mark all non-overlay caches as dirty - DocumentLegacy::mark_children_as_dirty(&mut self.document_legacy.root); - responses.add(DocumentMessage::RenderDocument); - } - DirtyRenderDocumentInOutlineView => { - if self.view_mode == ViewMode::Outline { - responses.add_front(DocumentMessage::DirtyRenderDocument); - } - } DocumentHistoryBackward => self.undo(responses), DocumentHistoryForward => self.redo(responses), DocumentStructureChanged => { let data_buffer: RawBuffer = self.serialize_root().as_slice().into(); responses.add(FrontendMessage::UpdateDocumentLayerTreeStructure { data_buffer }) } - DownloadLayerImageOutput { layer_path } => { - let layer = self.document_legacy.layer(&layer_path).ok(); - - let layer_name = layer.map(|layer| layer.name.clone().unwrap_or_else(|| "Untitled Layer".to_string())); - - let blob_url = layer.and_then(|layer| layer.as_layer().ok()).and_then(|layer_layer| layer_layer.as_blob_url()).cloned(); - - if let (Some(layer_name), Some(blob_url)) = (layer_name, blob_url) { - responses.add(FrontendMessage::TriggerDownloadBlobUrl { layer_name, blob_url }); - } - } DuplicateSelectedLayers => { - self.backup(responses); - responses.add_front(SetSelectedLayers { replacement_selected_layers: vec![] }); - self.layer_range_selection_reference = None; - for path in self.selected_layers_sorted() { - responses.add(DocumentOperation::DuplicateLayer { path: path.to_vec() }); - } + // TODO: Reimplement selected layer duplication + // self.backup(responses); + // self.layer_range_selection_reference = None; + // for path in self.selected_layers_sorted() { + // responses.add(DocumentOperation::DuplicateLayer { path: path.to_vec() }); + // } } FlipSelectedLayers { flip_axis } => { self.backup(responses); @@ -372,27 +306,6 @@ impl MessageHandler> for DocumentMessageHand let affected_layer_path = affected_folder_path; responses.extend([LayerChanged { affected_layer_path }.into(), DocumentStructureChanged.into()]); } - FrameClear => { - let mut selected_frame_layers = self.selected_layers_with_type(LayerDataTypeDiscriminant::Layer); - // Get what is hopefully the only selected Layer layer - let layer_path = selected_frame_layers.next(); - // Abort if we didn't have any Layer layer, or if there are additional ones also selected - if layer_path.is_none() || selected_frame_layers.next().is_some() { - return; - } - let layer_path = layer_path.unwrap(); - - let layer = self.document_legacy.layer(layer_path).expect("Clearing Layer image for invalid layer"); - let previous_blob_url = match &layer.data { - LegacyLayerType::Layer(layer) => layer.as_blob_url(), - x => panic!("Cannot find blob url for layer type {}", LayerDataTypeDiscriminant::from(x)), - }; - - if let Some(url) = previous_blob_url { - responses.add(FrontendMessage::TriggerRevokeBlobUrl { url: url.clone() }); - } - responses.add(DocumentOperation::ClearBlobURL { path: layer_path.into() }); - } GroupSelectedLayers => { // TODO: Add code that changes the insert index of the new folder based on the selected layer let parent = self.metadata().deepest_common_ancestor(self.metadata().selected_layers(), true).unwrap_or(LayerNodeIdentifier::ROOT); @@ -442,10 +355,9 @@ impl MessageHandler> for DocumentMessageHand } InputFrameRasterizeRegionBelowLayer { layer_path } => responses.add(PortfolioMessage::SubmitGraphRender { document_id, layer_path }), LayerChanged { affected_layer_path } => { - if let Ok(layer_entry) = self.layer_panel_entry(affected_layer_path.clone(), &render_data) { + if let Ok(layer_entry) = self.layer_panel_entry(affected_layer_path.clone()) { responses.add(FrontendMessage::UpdateDocumentLayerDetails { data: layer_entry }); } - responses.add(PropertiesPanelMessage::CheckSelectedWasUpdated { path: affected_layer_path }); self.update_layers_panel_options_bar_widgets(responses); } MoveSelectedLayersTo { parent, insert_index } => { @@ -567,11 +479,7 @@ impl MessageHandler> for DocumentMessageHand responses.add(PortfolioMessage::UpdateOpenDocumentsList); responses.add(NodeGraphMessage::UpdateNewNodeGraph); } - RenameLayer { layer_path, new_name } => responses.add(DocumentOperation::RenameLayer { layer_path, new_name }), RenderDocument => { - // responses.add(FrontendMessage::UpdateDocumentArtwork { - // svg: self.document_legacy.render_root(&render_data), - // }); responses.add(OverlaysMessage::Draw); } RenderRulers => { @@ -610,10 +518,6 @@ impl MessageHandler> for DocumentMessageHand multiplier: scrollbar_multiplier.into(), }); } - RollbackTransaction => { - self.rollback(responses); - responses.extend([RenderDocument.into(), DocumentStructureChanged.into()]); - } SaveDocument => { self.set_save_state(true); responses.add(PortfolioMessage::AutoSaveActiveDocument); @@ -699,40 +603,6 @@ impl MessageHandler> for DocumentMessageHand responses.add(GraphOperationMessage::BlendModeSet { layer: layer.to_path(), blend_mode }); } } - SetImageBlobUrl { - layer_path, - blob_url, - resolution, - document_id, - } => { - let Ok(layer) = self.document_legacy.layer(&layer_path) else { - warn!("Setting blob URL for invalid layer"); - return; - }; - - // Revoke the old blob URL - match &layer.data { - LegacyLayerType::Layer(layer) => { - if let Some(url) = layer.as_blob_url() { - responses.add(FrontendMessage::TriggerRevokeBlobUrl { url: url.clone() }); - } - } - other => { - warn!("Setting blob URL for invalid layer type, which must be a `Layer` layer type. Found: `{other:?}`"); - return; - } - } - - responses.add(PortfolioMessage::DocumentPassMessage { - document_id, - message: DocumentOperation::SetLayerBlobUrl { layer_path, blob_url, resolution }.into(), - }); - } - SetLayerExpansion { layer_path, set_expanded } => { - self.layer_metadata_mut(&layer_path).expanded = set_expanded; - responses.add(DocumentStructureChanged); - responses.add(LayerChanged { affected_layer_path: layer_path }) - } SetOpacityForSelectedLayers { opacity } => { self.backup(responses); let opacity = opacity.clamp(0., 1.) as f32; @@ -749,16 +619,6 @@ impl MessageHandler> for DocumentMessageHand SetRangeSelectionLayer { new_layer } => { self.layer_range_selection_reference = new_layer; } - SetSelectedLayers { replacement_selected_layers } => { - let selected = self.layer_metadata.iter_mut().filter(|(_, layer_metadata)| layer_metadata.selected); - selected.for_each(|(path, layer_metadata)| { - layer_metadata.selected = false; - responses.add(LayerChanged { affected_layer_path: path.clone() }) - }); - - let additional_layers = replacement_selected_layers; - responses.add_front(AddSelectedLayers { additional_layers }); - } SetSnapping { snapping_enabled, bounding_box_snapping, @@ -829,9 +689,6 @@ impl MessageHandler> for DocumentMessageHand responses.add(DocumentMessage::RenderScrollbars); responses.add(NodeGraphMessage::RunDocumentGraph); } - UpdateLayerMetadata { layer_path, layer_metadata } => { - self.layer_metadata.insert(layer_path, layer_metadata); - } ZoomCanvasTo100Percent => { responses.add_front(NavigationMessage::SetCanvasZoom { zoom_factor: 1. }); } @@ -948,17 +805,15 @@ impl DocumentMessageHandler { && self.name.starts_with(DEFAULT_DOCUMENT_NAME) } - fn select_layer(&mut self, path: &[LayerId], render_data: &RenderData) -> Option { + fn select_layer(&mut self, path: &[LayerId]) -> Option { println!("Select_layer fail: {:?}", self.all_layers_sorted()); if let Some(layer) = self.layer_metadata.get_mut(path) { - let render_data = RenderData::new(render_data.font_cache, self.view_mode, None); - layer.selected = true; - let data = self.layer_panel_entry(path.to_vec(), &render_data).ok()?; + let data = self.layer_panel_entry(path.to_vec()).ok()?; (!path.is_empty()).then(|| FrontendMessage::UpdateDocumentLayerDetails { data }.into()) } else { - warn!("Tried to select non existing layer {path:?}"); + warn!("Tried to select non-existing layer {path:?}"); None } } @@ -998,11 +853,11 @@ impl DocumentMessageHandler { }) } - /// Returns the bounding boxes for all visible layers, optionally excluding any paths. - pub fn bounding_boxes<'a>(&'a self, ignore_document: Option<&'a Vec>>, _ignore_artboard: Option, render_data: &'a RenderData) -> impl Iterator + 'a { - self.visible_layers() - .filter(move |path| ignore_document.map_or(true, |ignore_document| !ignore_document.iter().any(|ig| ig.as_slice() == *path))) - .filter_map(|path| self.document_legacy.viewport_bounding_box(path, render_data).ok()?) + /// Returns the bounding boxes for all visible layers. + pub fn bounding_boxes<'a>(&'a self) -> impl Iterator + 'a { + // TODO: Remove this function entirely? + // self.visible_layers().filter_map(|path| self.document_legacy.viewport_bounding_box(path, font_cache).ok()?) + std::iter::empty() } fn serialize_structure(&self, folder: LayerNodeIdentifier, structure: &mut Vec, data: &mut Vec, path: &mut Vec) { @@ -1159,7 +1014,6 @@ impl DocumentMessageHandler { let old_root = self.metadata().document_to_viewport; let document = std::mem::replace(&mut self.document_legacy, document); self.document_legacy.metadata.document_to_viewport = old_root; - self.document_legacy.root.cache_dirty = true; let layer_metadata = std::mem::replace(&mut self.layer_metadata, layer_metadata); @@ -1260,31 +1114,21 @@ impl DocumentMessageHandler { } // TODO: This should probably take a slice not a vec, also why does this even exist when `layer_panel_entry_from_path` also exists? - pub fn layer_panel_entry(&mut self, path: Vec, render_data: &RenderData) -> Result { + pub fn layer_panel_entry(&mut self, path: Vec) -> Result { let data: LayerMetadata = *self .layer_metadata .get_mut(&path) .ok_or_else(|| EditorError::Document(format!("Could not get layer metadata for {path:?}")))?; let layer = self.document_legacy.layer(&path)?; - let entry = LayerPanelEntry::new(&data, self.document_legacy.multiply_transforms(&path)?, layer, path, render_data); + let entry = LayerPanelEntry::new(&data, layer, path); Ok(entry) } - /// Returns a list of `LayerPanelEntry`s intended for display purposes. These don't contain - /// any actual data, but rather attributes such as visibility and names of the layers. - pub fn layer_panel(&mut self, path: &[LayerId], render_data: &RenderData) -> Result, EditorError> { - let folder = self.document_legacy.folder(path)?; - let paths: Vec> = folder.layer_ids.iter().map(|id| [path, &[*id]].concat()).collect(); - let entries = paths.iter().rev().filter_map(|path| self.layer_panel_entry_from_path(path, render_data)).collect(); - Ok(entries) - } - - pub fn layer_panel_entry_from_path(&self, path: &[LayerId], render_data: &RenderData) -> Option { + pub fn layer_panel_entry_from_path(&self, path: &[LayerId]) -> Option { let layer_metadata = self.layer_metadata(path); - let transform = self.document_legacy.generate_transform_across_scope(path, Some(self.metadata().document_to_viewport.inverse())).ok()?; let layer = self.document_legacy.layer(path).ok()?; - Some(LayerPanelEntry::new(layer_metadata, transform, layer, path.to_vec(), render_data)) + Some(LayerPanelEntry::new(layer_metadata, layer, path.to_vec())) } /// When working with an insert index, deleting the layers may cause the insert index to point to a different location (if the layer being deleted was located before the insert index). @@ -1297,11 +1141,6 @@ impl DocumentMessageHandler { Ok(new_insert_index) } - /// Calculates the bounding box of all layers in the document - pub fn all_layer_bounds(&self, render_data: &RenderData) -> Option<[DVec2; 2]> { - self.document_legacy.viewport_bounding_box(&[], render_data).ok().flatten() - } - /// Calculate the path that new layers should be inserted to. /// Depends on the selected layers as well as their types (Folder/Non-Folder) pub fn get_path_for_new_layer(&self) -> Vec { diff --git a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs index 4f56de9062..2c948f11f4 100644 --- a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs +++ b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs @@ -103,7 +103,6 @@ impl MessageHandler, &InputPre } responses.add(BroadcastEvent::DocumentIsDirty); - responses.add(DocumentMessage::DirtyRenderDocumentInOutlineView); responses.add(PortfolioMessage::UpdateDocumentWidgets); self.create_document_transform(ipp.viewport_bounds.center(), responses); } @@ -235,7 +234,6 @@ impl MessageHandler, &InputPre self.zoom = zoom_factor.clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX); self.zoom *= Self::clamp_zoom(self.zoom, document_bounds, old_zoom, ipp); responses.add(BroadcastEvent::DocumentIsDirty); - responses.add(DocumentMessage::DirtyRenderDocumentInOutlineView); responses.add(PortfolioMessage::UpdateDocumentWidgets); self.create_document_transform(ipp.viewport_bounds.center(), responses); } diff --git a/editor/src/messages/portfolio/document/node_graph/graph_operation_message.rs b/editor/src/messages/portfolio/document/node_graph/graph_operation_message.rs index d45165c10e..62b8ecbd4e 100644 --- a/editor/src/messages/portfolio/document/node_graph/graph_operation_message.rs +++ b/editor/src/messages/portfolio/document/node_graph/graph_operation_message.rs @@ -15,7 +15,7 @@ use graphene_core::{Artboard, Color}; use glam::{DAffine2, DVec2, IVec2}; -pub type LayerIdentifier = Vec; +pub type LayerIdentifier = Vec; #[impl_message(Message, DocumentMessage, GraphOperation)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] diff --git a/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler.rs index ce4bfa4a46..8d36b3aa51 100644 --- a/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler.rs @@ -3,8 +3,8 @@ use crate::messages::prelude::*; use bezier_rs::Subpath; use document_legacy::document::Document; +use document_legacy::document::LayerId; use document_legacy::document_metadata::{DocumentMetadata, LayerNodeIdentifier}; -use document_legacy::{LayerId, Operation}; use graph_craft::document::value::TaggedValue; use graph_craft::document::{generate_uuid, DocumentNode, NodeId, NodeInput, NodeNetwork, NodeOutput}; use graphene_core::raster::{BlendMode, ImageFrame}; @@ -315,13 +315,13 @@ impl<'a> ModifyInputsContext<'a> { } self.node_graph.network.clear(); - self.responses.add(PropertiesPanelMessage::ResendActiveProperties); + self.responses.add(PropertiesPanelMessage::Refresh); let layer_path = self.layer.to_vec(); if !skip_rerender { self.responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path }); } else { - self.responses.add(DocumentMessage::FrameClear); + // Code was removed from here which cleared the frame } if existing_node_id.is_none() { self.responses.add(NodeGraphMessage::SendGraph { should_rerender: false }); @@ -340,13 +340,13 @@ impl<'a> ModifyInputsContext<'a> { self.modify_existing_node_inputs(existing_node_id, &mut update_input); } - self.responses.add(PropertiesPanelMessage::ResendActiveProperties); + self.responses.add(PropertiesPanelMessage::Refresh); let layer_path = self.layer.to_vec(); if !skip_rerender { self.responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path }); } else { - self.responses.add(DocumentMessage::FrameClear); + // Code was removed from here which cleared the frame } } @@ -582,8 +582,6 @@ impl MessageHandler { if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(&layer, document, node_graph, responses) { modify_inputs.fill_set(fill); - } else { - responses.add(Operation::SetLayerFill { path: layer, fill }); } } GraphOperationMessage::OpacitySet { layer, opacity } => { @@ -604,8 +602,6 @@ impl MessageHandler { if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(&layer, document, node_graph, responses) { modify_inputs.stroke_set(stroke); - } else { - responses.add(Operation::SetLayerStroke { path: layer, stroke }); } } GraphOperationMessage::TransformChange { diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs index 00d36b3497..f5a04e682a 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs @@ -1,6 +1,6 @@ use crate::messages::prelude::*; -use document_legacy::LayerId; +use document_legacy::document::LayerId; use graph_craft::document::value::TaggedValue; use graph_craft::document::{DocumentNode, NodeId, NodeInput}; @@ -10,7 +10,6 @@ pub enum NodeGraphMessage { // Messages Init, SelectedNodesUpdated, - CloseNodeGraph, ConnectNodesByLink { output_node: u64, output_node_connector_index: usize, @@ -57,9 +56,6 @@ pub enum NodeGraphMessage { displacement_x: i32, displacement_y: i32, }, - OpenNodeGraph { - layer_path: Vec, - }, PasteNodes { serialized_nodes: String, }, diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index 6a60c15164..478a23c815 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -6,8 +6,8 @@ use crate::messages::prelude::*; use crate::node_graph_executor::GraphIdentifier; use document_legacy::document::Document; +use document_legacy::document::LayerId; use document_legacy::document_metadata::LayerNodeIdentifier; -use document_legacy::LayerId; use graph_craft::document::value::TaggedValue; use graph_craft::document::{DocumentNode, NodeId, NodeInput, NodeNetwork, NodeOutput}; use graphene_core::*; @@ -271,7 +271,7 @@ impl NodeGraphMessageHandler { } fn send_graph(network: &NodeNetwork, layer_path: &Option>, graph_view_overlay_open: bool, responses: &mut VecDeque) { - responses.add(PropertiesPanelMessage::ResendActiveProperties); + responses.add(PropertiesPanelMessage::Refresh); if !graph_view_overlay_open { return; @@ -489,7 +489,6 @@ impl<'a> MessageHandler> for NodeGrap } responses.add(NodeGraphMessage::RunDocumentGraph); } - NodeGraphMessage::CloseNodeGraph => {} NodeGraphMessage::ConnectNodesByLink { output_node, output_node_connector_index, @@ -687,7 +686,7 @@ impl<'a> MessageHandler> for NodeGrap let should_rerender = network.connected_to_output(node_id); responses.add(NodeGraphMessage::SendGraph { should_rerender }); - responses.add(PropertiesPanelMessage::ResendActiveProperties); + responses.add(PropertiesPanelMessage::Refresh); } NodeGraphMessage::InsertNode { node_id, document_node } => { if let Some(network) = document.document_network.nested_network_mut(&self.network) { @@ -707,19 +706,6 @@ impl<'a> MessageHandler> for NodeGrap } Self::send_graph(network, &self.layer_path, graph_view_overlay_open, responses); } - NodeGraphMessage::OpenNodeGraph { layer_path } => { - self.layer_path = Some(layer_path); - - if let Some(network) = document.document_network.nested_network(&self.network) { - responses.add(document.metadata.clear_selected_nodes()); - - Self::send_graph(network, &self.layer_path, graph_view_overlay_open, responses); - - let node_types = document_node_types::collect_node_types(); - responses.add(FrontendMessage::UpdateNodeTypes { node_types }); - } - self.update_selected(document, responses); - } NodeGraphMessage::PasteNodes { serialized_nodes } => { let Some(network) = document.document_network.nested_network(&self.network) else { warn!("No network"); @@ -776,7 +762,7 @@ impl<'a> MessageHandler> for NodeGrap } NodeGraphMessage::SelectedNodesSet { nodes } => { responses.add(document.metadata.set_selected_nodes(nodes)); - responses.add(PropertiesPanelMessage::ResendActiveProperties); + responses.add(PropertiesPanelMessage::Refresh); } NodeGraphMessage::SendGraph { should_rerender } => { if let Some(network) = document.document_network.nested_network(&self.network) { @@ -797,7 +783,7 @@ impl<'a> MessageHandler> for NodeGrap let input = NodeInput::Value { tagged_value: value, exposed: false }; responses.add(NodeGraphMessage::SetNodeInput { node_id, input_index, input }); - responses.add(PropertiesPanelMessage::ResendActiveProperties); + responses.add(PropertiesPanelMessage::Refresh); if (node.name != "Imaginate" || input_index == 0) && network.connected_to_output(node_id) { if let Some(layer_path) = self.layer_path.clone() { responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path }); diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs index 67044d2c94..9232584fe4 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs @@ -71,7 +71,7 @@ pub struct NodePropertiesContext<'a> { pub persistent_data: &'a crate::messages::portfolio::utility_types::PersistentData, pub document: &'a document_legacy::document::Document, pub responses: &'a mut VecDeque, - pub layer_path: &'a [document_legacy::LayerId], + pub layer_path: &'a [document_legacy::document::LayerId], pub nested_path: &'a [NodeId], pub executor: &'a mut NodeGraphExecutor, pub network: &'a NodeNetwork, diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs index 4ad44f5cf9..d0a73585eb 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs @@ -5,8 +5,7 @@ use super::FrontendGraphDataType; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::prelude::*; -use document_legacy::{layers::layer_info::LayerDataTypeDiscriminant, Operation}; -use graph_craft::concrete; +use document_legacy::layers::layer_info::LayerDataTypeDiscriminant; use graph_craft::document::value::TaggedValue; use graph_craft::document::{DocumentNode, NodeId, NodeInput}; use graph_craft::imaginate_input::{ImaginateMaskStartingFill, ImaginateSamplingMethod, ImaginateServerStatus, ImaginateStatus}; @@ -867,34 +866,10 @@ pub fn load_image_properties(document_node: &DocumentNode, node_id: NodeId, _con vec![LayoutGroup::Row { widgets: url }] } -pub fn output_properties(_document_node: &DocumentNode, _node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { - let output_type = context.executor.previous_output_type(context.layer_path); - let disabled = match output_type { - Some(output_type) => output_type != concrete!(ImageFrame), - None => true, - }; +pub fn output_properties(_document_node: &DocumentNode, _node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { + let label = TextLabel::new("Graphics fed into the Output are drawn in the viewport").widget_holder(); - let layer_path_1 = context.layer_path.to_vec(); - let layer_path_2 = context.layer_path.to_vec(); - - let label = TextLabel::new("The graph's output is drawn in the layer").widget_holder(); - let download_button = TextButton::new("Download Render Output") - .tooltip("Download the rendered image output as a PNG file") - .disabled(disabled) - .on_update(move |_| DocumentMessage::DownloadLayerImageOutput { layer_path: layer_path_1.clone() }.into()) - .widget_holder(); - let copy_button = TextButton::new("Copy Render Output") - .tooltip("Copy the rendered image output to the clipboard") - .disabled(disabled) - .on_update(move |_| DocumentMessage::CopyToClipboardLayerImageOutput { layer_path: layer_path_2.clone() }.into()) - .widget_holder(); - - vec![ - LayoutGroup::Row { widgets: vec![label] }, - LayoutGroup::Row { - widgets: vec![download_button, Separator::new(SeparatorType::Related).widget_holder(), copy_button], - }, - ] + vec![LayoutGroup::Row { widgets: vec![label] }] } pub fn mask_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { @@ -1738,10 +1713,10 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte LayoutGroup::Row { widgets }.with_tooltip("Seed determines the random outcome, enabling limitless unique variations") }; - let transform = context - .executor - .introspect_node_in_network(context.network, &imaginate_node, |network| network.inputs.first().copied(), |frame: &ImageFrame| frame.transform) - .unwrap_or_default(); + // let transform = context + // .executor + // .introspect_node_in_network(context.network, &imaginate_node, |network| network.inputs.first().copied(), |frame: &ImageFrame| frame.transform) + // .unwrap_or_default(); let image_size = context .executor .introspect_node_in_network( @@ -1770,18 +1745,11 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte let dimensions_is_auto = vec2.is_none(); let vec2 = vec2.unwrap_or_else(|| round((image_size.0 as f64, image_size.1 as f64).into())); - let layer_path = context.layer_path.to_vec(); widgets.extend_from_slice(&[ Separator::new(SeparatorType::Unrelated).widget_holder(), IconButton::new("Rescale", 24) .tooltip("Set the layer dimensions to this resolution") - .on_update(move |_| { - Operation::SetLayerScaleAroundPivot { - path: layer_path.clone(), - new_scale: vec2.into(), - } - .into() - }) + .on_update(move |_| DialogMessage::RequestComingSoonDialog { issue: None }.into()) .widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder(), CheckboxInput::new(!dimensions_is_auto || transform_not_connected) diff --git a/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs b/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs index 6e5eb76fe3..174ea64bb3 100644 --- a/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs +++ b/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs @@ -1,9 +1,6 @@ -use super::utility_functions::overlay_canvas_element; -use super::utility_types::{OverlayContext, OverlayProvider}; +use super::utility_types::OverlayProvider; use crate::messages::prelude::*; -use wasm_bindgen::JsCast; - #[derive(Debug, Clone, Default)] pub struct OverlaysMessageHandler { pub overlay_providers: HashSet, @@ -16,6 +13,10 @@ impl MessageHandler f match message { #[cfg(target_arch = "wasm32")] OverlaysMessage::Draw => { + use super::utility_functions::overlay_canvas_element; + use super::utility_types::OverlayContext; + use wasm_bindgen::JsCast; + let canvas = self.canvas.get_or_insert_with(|| overlay_canvas_element().expect("Failed to get canvas element")); let context = self.context.get_or_insert_with(|| { @@ -36,7 +37,10 @@ impl MessageHandler f } #[cfg(not(target_arch = "wasm32"))] OverlaysMessage::Draw => { - warn!("Cannot render overlays on non-Wasm targets {overlays_visible} {ipp:?}."); + warn!( + "Cannot render overlays on non-Wasm targets.\n{responses:?} {overlays_visible} {ipp:?} {:?} {:?}", + self.canvas, self.context + ); } OverlaysMessage::AddProvider(message) => { self.overlay_providers.insert(message); diff --git a/editor/src/messages/portfolio/document/properties_panel/mod.rs b/editor/src/messages/portfolio/document/properties_panel/mod.rs index e984589242..7508ecada4 100644 --- a/editor/src/messages/portfolio/document/properties_panel/mod.rs +++ b/editor/src/messages/portfolio/document/properties_panel/mod.rs @@ -1,7 +1,6 @@ mod properties_panel_message; mod properties_panel_message_handler; -pub mod utility_functions; pub mod utility_types; #[doc(inline)] diff --git a/editor/src/messages/portfolio/document/properties_panel/properties_panel_message.rs b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message.rs index 610a1012af..4941bf1200 100644 --- a/editor/src/messages/portfolio/document/properties_panel/properties_panel_message.rs +++ b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message.rs @@ -1,10 +1,5 @@ -use super::utility_types::TransformOp; -use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::prelude::*; -use document_legacy::layers::style::{Fill, Stroke}; -use document_legacy::LayerId; - use serde::{Deserialize, Serialize}; #[remain::sorted] @@ -12,17 +7,6 @@ use serde::{Deserialize, Serialize}; #[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] pub enum PropertiesPanelMessage { // Messages - CheckSelectedWasDeleted { path: Vec }, - CheckSelectedWasUpdated { path: Vec }, - ClearSelection, - Deactivate, - Init, - ModifyFill { fill: Fill }, - ModifyPreserveAspect { preserve_aspect: bool }, - ModifyStroke { stroke: Stroke }, - ModifyTransform { value: f64, transform_op: TransformOp }, - ResendActiveProperties, - SetActiveLayers { paths: Vec> }, - SetPivot { new_position: PivotPosition }, - UpdateSelectedDocumentProperties, + Clear, + Refresh, } diff --git a/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs index 8b32e4239b..8781db68bc 100644 --- a/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs +++ b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs @@ -1,20 +1,13 @@ -use super::utility_functions::{register_artwork_layer_properties, register_document_graph_properties}; use super::utility_types::PropertiesPanelMessageHandlerData; use crate::messages::layout::utility_types::widget_prelude::*; -use crate::messages::portfolio::document::properties_panel::utility_functions::apply_transform_operation; +use crate::messages::portfolio::document::node_graph::NodePropertiesContext; use crate::messages::portfolio::utility_types::PersistentData; use crate::messages::prelude::*; -use document_legacy::layers::layer_info::LayerDataTypeDiscriminant; -use document_legacy::layers::style::{RenderData, ViewMode}; -use document_legacy::{LayerId, Operation}; - use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize, Default)] -pub struct PropertiesPanelMessageHandler { - active_selection: Option>, // TODO: Delete this if it's indeed dead code? -} +pub struct PropertiesPanelMessageHandler; impl<'a> MessageHandler)> for PropertiesPanelMessageHandler { #[remain::check] @@ -24,42 +17,13 @@ impl<'a> MessageHandler { - if paths.len() != 1 { - // TODO: Allow for multiple selected layers - responses.add(PropertiesPanelMessage::ClearSelection); - responses.add(NodeGraphMessage::CloseNodeGraph); - } else { - let path = paths.into_iter().next().unwrap(); - if self.active_selection.as_ref() != Some(&path) { - // Update the layer visibility - if artwork_document - .layer(&path) - .ok() - .filter(|layer| LayerDataTypeDiscriminant::from(&layer.data) == LayerDataTypeDiscriminant::Layer) - .is_some() - { - responses.add(NodeGraphMessage::OpenNodeGraph { layer_path: path.clone() }); - } else { - responses.add(NodeGraphMessage::CloseNodeGraph); - } - - self.active_selection = Some(path); - responses.add(PropertiesPanelMessage::ResendActiveProperties); - } - } - } - ClearSelection => { - // This causes the Properties panel to change, so this needs to happen before the following lines clear the Properties panel - responses.add(NodeGraphMessage::CloseNodeGraph); - + Clear => { responses.add(LayoutMessage::SendLayout { layout: Layout::WidgetLayout(WidgetLayout::new(vec![])), layout_target: LayoutTarget::PropertiesOptions, @@ -68,84 +32,41 @@ impl<'a> MessageHandler responses.add(BroadcastMessage::UnsubscribeEvent { - on: BroadcastEvent::SelectionChanged, - message: Box::new(PropertiesPanelMessage::UpdateSelectedDocumentProperties.into()), - }), - Init => responses.add(BroadcastMessage::SubscribeEvent { - on: BroadcastEvent::SelectionChanged, - send: Box::new(PropertiesPanelMessage::UpdateSelectedDocumentProperties.into()), - }), - ModifyTransform { value, transform_op } => { - let path = self.active_selection.as_ref().expect("Received update for properties panel with no active layer"); - let layer = artwork_document.layer(path).unwrap(); + Refresh => { + let mut context = NodePropertiesContext { + persistent_data, + document: artwork_document, + responses, + nested_path: &node_graph_message_handler.network, + layer_path: &[], + executor, + network: &artwork_document.document_network, + }; - let transform = apply_transform_operation(layer, transform_op, value, &render_data); + let properties_sections = node_graph_message_handler.collate_properties(&mut context); - self.create_document_operation(Operation::SetLayerTransform { path: path.clone(), transform }, true, responses); - } - ModifyPreserveAspect { preserve_aspect } => { - let layer_path = self.active_selection.clone().expect("Received update for properties panel with no active layer"); - self.create_document_operation(Operation::SetLayerPreserveAspect { layer_path, preserve_aspect }, true, responses); - } - ModifyFill { fill } => { - let path = self.active_selection.clone().expect("Received update for properties panel with no active layer"); - self.create_document_operation(Operation::SetLayerFill { path, fill }, true, responses); - } - ModifyStroke { stroke } => { - let path = self.active_selection.clone().expect("Received update for properties panel with no active layer"); - self.create_document_operation(Operation::SetLayerStroke { path, stroke }, true, responses); - } - SetPivot { new_position } => { - let layer = self.active_selection.clone().expect("Received update for properties panel with no active layer"); - let position: Option = new_position.into(); - let pivot = position.unwrap(); + let options_bar = vec![LayoutGroup::Row { + widgets: vec![ + IconLabel::new("File").tooltip("Document").widget_holder(), + Separator::new(SeparatorType::Unrelated).widget_holder(), + TextInput::new(document_name) + .on_update(|text_input| DocumentMessage::RenameDocument { new_name: text_input.value.clone() }.into()) + .widget_holder(), + Separator::new(SeparatorType::Related).widget_holder(), + PopoverButton::new("Additional Options", "Coming soon").widget_holder(), + ], + }]; - responses.add(DocumentMessage::StartTransaction); - responses.add(GraphOperationMessage::TransformSetPivot { layer, pivot }); + context.responses.add(LayoutMessage::SendLayout { + layout: Layout::WidgetLayout(WidgetLayout::new(options_bar)), + layout_target: LayoutTarget::PropertiesOptions, + }); + context.responses.add(LayoutMessage::SendLayout { + layout: Layout::WidgetLayout(WidgetLayout::new(properties_sections)), + layout_target: LayoutTarget::PropertiesSections, + }); } - CheckSelectedWasUpdated { path } => { - if self.matches_selected(&path) { - responses.add(PropertiesPanelMessage::ResendActiveProperties) - } - } - CheckSelectedWasDeleted { path } => { - if self.matches_selected(&path) { - self.active_selection = None; - responses.add(LayoutMessage::SendLayout { - layout_target: LayoutTarget::PropertiesOptions, - layout: Layout::WidgetLayout(WidgetLayout::default()), - }); - responses.add(LayoutMessage::SendLayout { - layout_target: LayoutTarget::PropertiesSections, - layout: Layout::WidgetLayout(WidgetLayout::default()), - }); - responses.add(NodeGraphMessage::CloseNodeGraph); - } - } - ResendActiveProperties => { - if let Some(path) = self.active_selection.clone() { - // TODO: Remove this conditional now that the document graph is the only form of graph? (Also any other related code.) - let layer = artwork_document.layer(&path).unwrap(); - register_artwork_layer_properties(artwork_document, path, layer, responses, persistent_data, node_graph_message_handler, executor); - } else { - let context = crate::messages::portfolio::document::node_graph::NodePropertiesContext { - persistent_data, - document: artwork_document, - responses, - nested_path: &node_graph_message_handler.network, - layer_path: &[], - executor, - network: &artwork_document.document_network, - }; - register_document_graph_properties(context, node_graph_message_handler, document_name); - } - } - UpdateSelectedDocumentProperties => responses.add(PropertiesPanelMessage::SetActiveLayers { - paths: selected_layers.map(|path| path.to_vec()).collect(), - }), } } @@ -153,21 +74,3 @@ impl<'a> MessageHandler bool { - let last_active_path_id = self.active_selection.as_ref().and_then(|v| v.last().copied()); - let last_modified = path.last().copied(); - matches!((last_active_path_id, last_modified), (Some(active_last), Some(modified_last)) if active_last == modified_last) - } - - fn create_document_operation(&self, operation: Operation, commit_history: bool, responses: &mut VecDeque) { - // Commit to history before the modification - if commit_history { - responses.add(DocumentMessage::StartTransaction); - } - - // Dispatch the relevant operation to the main document - responses.add(DocumentMessage::DispatchOperation(Box::new(operation))); - } -} diff --git a/editor/src/messages/portfolio/document/properties_panel/utility_functions.rs b/editor/src/messages/portfolio/document/properties_panel/utility_functions.rs deleted file mode 100644 index 7dd97a3f04..0000000000 --- a/editor/src/messages/portfolio/document/properties_panel/utility_functions.rs +++ /dev/null @@ -1,793 +0,0 @@ -use super::utility_types::TransformOp; -use crate::application::generate_uuid; -use crate::messages::layout::utility_types::widget_prelude::*; -use crate::messages::portfolio::document::node_graph::NodePropertiesContext; -use crate::messages::portfolio::utility_types::PersistentData; -use crate::messages::prelude::*; -use crate::node_graph_executor::NodeGraphExecutor; - -use document_legacy::document::Document; -use document_legacy::layers::layer_info::{LegacyLayer, LegacyLayerType}; -use document_legacy::layers::style::{Fill, Gradient, GradientType, LineCap, LineJoin, RenderData, Stroke, ViewMode}; -use graphene_core::raster::color::Color; - -use glam::{DAffine2, DVec2}; -use std::f64::consts::PI; -use std::sync::Arc; - -pub fn apply_transform_operation(layer: &LegacyLayer, transform_op: TransformOp, value: f64, render_data: &RenderData) -> [f64; 6] { - let transformation = match transform_op { - TransformOp::X => DAffine2::update_x, - TransformOp::Y => DAffine2::update_y, - TransformOp::ScaleX | TransformOp::Width => DAffine2::update_scale_x, - TransformOp::ScaleY | TransformOp::Height => DAffine2::update_scale_y, - TransformOp::Rotation => DAffine2::update_rotation, - }; - - let scale = match transform_op { - TransformOp::Width => layer.bounding_transform(render_data).scale_x() / layer.transform.scale_x(), - TransformOp::Height => layer.bounding_transform(render_data).scale_y() / layer.transform.scale_y(), - _ => 1., - }; - - // Apply the operation - let transform = transformation(layer.transform, value / scale); - - // Return this transform if it is not a dimensions change - if !matches!(transform_op, TransformOp::ScaleX | TransformOp::Width | TransformOp::ScaleY | TransformOp::Height) { - return transform.to_cols_array(); - } - - // Find the layerspace pivot - let pivot = DAffine2::from_translation(layer.transform.transform_point2(layer.layerspace_pivot(render_data))); - - // Find the delta transform - let mut delta = layer.transform.inverse() * transform; - if !delta.is_finite() { - return layer.transform.to_cols_array(); - } - - // Preserve aspect ratio - if matches!(transform_op, TransformOp::ScaleX | TransformOp::Width) && layer.preserve_aspect { - let scale_x = layer.transform.scale_x(); - if scale_x != 0. { - delta = DAffine2::from_scale((1., (value / scale) / scale_x).into()) * delta; - } - } else if layer.preserve_aspect { - let scale_y = layer.transform.scale_y(); - if scale_y != 0. { - delta = DAffine2::from_scale(((value / scale) / scale_y, 1.).into()) * delta; - } - } - - // Transform around pivot - ((pivot * delta * pivot.inverse()) * layer.transform).to_cols_array() -} - -pub fn register_artwork_layer_properties( - document: &Document, - layer_path: Vec, - layer: &LegacyLayer, - responses: &mut VecDeque, - persistent_data: &PersistentData, - node_graph_message_handler: &NodeGraphMessageHandler, - executor: &mut NodeGraphExecutor, -) { - let options_bar = vec![LayoutGroup::Row { - widgets: vec![ - match &layer.data { - LegacyLayerType::Folder(_) => IconLabel::new("Folder").tooltip("Folder").widget_holder(), - LegacyLayerType::Shape(_) => IconLabel::new("NodeShape").tooltip("Shape").widget_holder(), - LegacyLayerType::Layer(_) => IconLabel::new("Layer").tooltip("Layer").widget_holder(), - }, - Separator::new(SeparatorType::Unrelated).widget_holder(), - TextInput::new(layer.name.clone().unwrap_or_else(|| "Untitled Layer".to_string())) - .on_update(|_text_input: &TextInput| panic!("This is presumed to be dead code, but if you are seeing this crash, please file a bug report.")) - .widget_holder(), - Separator::new(SeparatorType::Related).widget_holder(), - PopoverButton::new("Additional Options", "Coming soon").widget_holder(), - ], - }]; - - let properties_body = match &layer.data { - LegacyLayerType::Shape(shape) => { - if let Some(fill_layout) = node_section_fill(shape.style.fill()) { - vec![ - node_section_transform(layer, persistent_data), - fill_layout, - node_section_stroke(&shape.style.stroke().unwrap_or_default()), - ] - } else { - vec![node_section_transform(layer, persistent_data), node_section_stroke(&shape.style.stroke().unwrap_or_default())] - } - } - LegacyLayerType::Layer(layer) => { - let mut context = NodePropertiesContext { - persistent_data, - document, - responses, - nested_path: &node_graph_message_handler.network, - layer_path: &layer_path, - executor, - network: &layer.network, - }; - let properties_sections = node_graph_message_handler.collate_properties(&mut context); - - properties_sections - } - LegacyLayerType::Folder(_) => { - vec![node_section_transform(layer, persistent_data)] - } - }; - - responses.add(LayoutMessage::SendLayout { - layout: Layout::WidgetLayout(WidgetLayout::new(options_bar)), - layout_target: LayoutTarget::PropertiesOptions, - }); - responses.add(LayoutMessage::SendLayout { - layout: Layout::WidgetLayout(WidgetLayout::new(properties_body)), - layout_target: LayoutTarget::PropertiesSections, - }); -} - -pub fn register_document_graph_properties(mut context: NodePropertiesContext, node_graph_message_handler: &NodeGraphMessageHandler, document_name: &str) { - let properties_sections = node_graph_message_handler.collate_properties(&mut context); - - let options_bar = vec![LayoutGroup::Row { - widgets: vec![ - IconLabel::new("File").tooltip("Document").widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), - TextInput::new(document_name) - .on_update(|text_input| DocumentMessage::RenameDocument { new_name: text_input.value.clone() }.into()) - .widget_holder(), - Separator::new(SeparatorType::Related).widget_holder(), - PopoverButton::new("Additional Options", "Coming soon").widget_holder(), - ], - }]; - - context.responses.add(LayoutMessage::SendLayout { - layout: Layout::WidgetLayout(WidgetLayout::new(options_bar)), - layout_target: LayoutTarget::PropertiesOptions, - }); - context.responses.add(LayoutMessage::SendLayout { - layout: Layout::WidgetLayout(WidgetLayout::new(properties_sections)), - layout_target: LayoutTarget::PropertiesSections, - }); -} - -fn node_section_transform(layer: &LegacyLayer, persistent_data: &PersistentData) -> LayoutGroup { - let render_data = RenderData::new(&persistent_data.font_cache, ViewMode::default(), None); - let pivot = layer.transform.transform_vector2(layer.layerspace_pivot(&render_data)); - LayoutGroup::Section { - name: "Transform".into(), - layout: vec![ - LayoutGroup::Row { - widgets: vec![ - TextLabel::new("Location").widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), - PivotInput::new(layer.pivot.into()) - .on_update(|pivot_input: &PivotInput| PropertiesPanelMessage::SetPivot { new_position: pivot_input.position }.into()) - .widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), - NumberInput::new(Some(layer.transform.x() + pivot.x)) - .label("X") - .unit(" px") - .min(-((1u64 << std::f64::MANTISSA_DIGITS) as f64)) - .max((1u64 << std::f64::MANTISSA_DIGITS) as f64) - .on_update(move |number_input: &NumberInput| { - PropertiesPanelMessage::ModifyTransform { - value: number_input.value.unwrap() - pivot.x, - transform_op: TransformOp::X, - } - .into() - }) - .widget_holder(), - Separator::new(SeparatorType::Related).widget_holder(), - NumberInput::new(Some(layer.transform.y() + pivot.y)) - .label("Y") - .unit(" px") - .min(-((1u64 << std::f64::MANTISSA_DIGITS) as f64)) - .max((1u64 << std::f64::MANTISSA_DIGITS) as f64) - .on_update(move |number_input: &NumberInput| { - PropertiesPanelMessage::ModifyTransform { - value: number_input.value.unwrap() - pivot.y, - transform_op: TransformOp::Y, - } - .into() - }) - .widget_holder(), - ], - }, - LayoutGroup::Row { - widgets: vec![ - TextLabel::new("Rotation").widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: These three separators add up to 24px, - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: which is the width of the Assist area. - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists. - Separator::new(SeparatorType::Unrelated).widget_holder(), - NumberInput::new(Some(layer.transform.rotation() * 180. / PI)) - .unit("°") - .mode(NumberInputMode::Range) - .range_min(Some(-180.)) - .range_max(Some(180.)) - .on_update(|number_input: &NumberInput| { - PropertiesPanelMessage::ModifyTransform { - value: number_input.value.unwrap() / 180. * PI, - transform_op: TransformOp::Rotation, - } - .into() - }) - .widget_holder(), - ], - }, - LayoutGroup::Row { - widgets: vec![ - TextLabel::new("Scale").widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), - Separator::new(SeparatorType::Related).widget_holder(), - CheckboxInput::new(layer.preserve_aspect) - .icon("Link") - .tooltip("Preserve Aspect Ratio") - .on_update(|input: &CheckboxInput| PropertiesPanelMessage::ModifyPreserveAspect { preserve_aspect: input.checked }.into()) - .widget_holder(), - Separator::new(SeparatorType::Related).widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), - NumberInput::new(Some(layer.transform.scale_x())) - .label("X") - .unit("") - .min(0.) - .max((1u64 << std::f64::MANTISSA_DIGITS) as f64) - .on_update(|number_input: &NumberInput| { - PropertiesPanelMessage::ModifyTransform { - value: number_input.value.unwrap(), - transform_op: TransformOp::ScaleX, - } - .into() - }) - .widget_holder(), - Separator::new(SeparatorType::Related).widget_holder(), - NumberInput::new(Some(layer.transform.scale_y())) - .label("Y") - .unit("") - .max((1u64 << std::f64::MANTISSA_DIGITS) as f64) - .on_update(|number_input: &NumberInput| { - PropertiesPanelMessage::ModifyTransform { - value: number_input.value.unwrap(), - transform_op: TransformOp::ScaleY, - } - .into() - }) - .widget_holder(), - ], - }, - LayoutGroup::Row { - widgets: vec![ - TextLabel::new("Dimensions").widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: These three separators add up to 24px, - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: which is the width of the Assist area. - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists. - Separator::new(SeparatorType::Unrelated).widget_holder(), - NumberInput::new(Some(layer.bounding_transform(&render_data).scale_x())) - .label("W") - .unit(" px") - .max((1u64 << f64::MANTISSA_DIGITS) as f64) - .on_update(|number_input: &NumberInput| { - PropertiesPanelMessage::ModifyTransform { - value: number_input.value.unwrap(), - transform_op: TransformOp::Width, - } - .into() - }) - .widget_holder(), - Separator::new(SeparatorType::Related).widget_holder(), - NumberInput::new(Some(layer.bounding_transform(&render_data).scale_y())) - .label("H") - .unit(" px") - .max((1u64 << f64::MANTISSA_DIGITS) as f64) - .on_update(|number_input: &NumberInput| { - PropertiesPanelMessage::ModifyTransform { - value: number_input.value.unwrap(), - transform_op: TransformOp::Height, - } - .into() - }) - .widget_holder(), - ], - }, - ], - } -} - -fn node_gradient_type(gradient: &Gradient) -> LayoutGroup { - let selected_index = match gradient.gradient_type { - GradientType::Linear => 0, - GradientType::Radial => 1, - }; - let mut cloned_gradient_linear = gradient.clone(); - cloned_gradient_linear.gradient_type = GradientType::Linear; - let mut cloned_gradient_radial = gradient.clone(); - cloned_gradient_radial.gradient_type = GradientType::Radial; - LayoutGroup::Row { - widgets: vec![ - TextLabel::new("Gradient Type").widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: These three separators add up to 24px, - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: which is the width of the Assist area. - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists. - Separator::new(SeparatorType::Unrelated).widget_holder(), - RadioInput::new(vec![ - RadioEntryData::new("linear") - .label("Linear") - .tooltip("Linear gradient changes colors from one side to the other along a line") - .on_update(move |_| { - PropertiesPanelMessage::ModifyFill { - fill: Fill::Gradient(cloned_gradient_linear.clone()), - } - .into() - }), - RadioEntryData::new("radial") - .label("Radial") - .tooltip("Radial gradient changes colors from the inside to the outside of a circular area") - .on_update(move |_| { - PropertiesPanelMessage::ModifyFill { - fill: Fill::Gradient(cloned_gradient_radial.clone()), - } - .into() - }), - ]) - .selected_index(Some(selected_index)) - .widget_holder(), - ], - } -} - -fn node_gradient_color(gradient: &Gradient, position: usize) -> LayoutGroup { - let gradient_clone = Arc::new(gradient.clone()); - let gradient_2 = gradient_clone.clone(); - let gradient_3 = gradient_clone.clone(); - let send_fill_message = move |new_gradient: Gradient| PropertiesPanelMessage::ModifyFill { fill: Fill::Gradient(new_gradient) }.into(); - - let value = format!("Gradient: {:.0}%", gradient_clone.positions[position].0 * 100.); - let mut widgets = vec![ - TextLabel::new(value) - .tooltip("Adjustable by dragging the gradient stops in the viewport with the Gradient tool active") - .widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: These three separators add up to 24px, - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: which is the width of the Assist area. - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists. - Separator::new(SeparatorType::Unrelated).widget_holder(), - ColorButton::new(gradient_clone.positions[position].1) - .on_update(move |text_input: &ColorButton| { - let mut new_gradient = (*gradient_clone).clone(); - new_gradient.positions[position].1 = text_input.value; - send_fill_message(new_gradient) - }) - .widget_holder(), - ]; - - let mut skip_separator = false; - // Remove button - if gradient.positions.len() != position + 1 && position != 0 { - let on_update = move |_: &IconButton| { - let mut new_gradient = (*gradient_3).clone(); - new_gradient.positions.remove(position); - send_fill_message(new_gradient) - }; - - skip_separator = true; - widgets.push(Separator::new(SeparatorType::Related).widget_holder()); - widgets.push(IconButton::new("Remove", 16).tooltip("Remove this gradient stop").on_update(on_update).widget_holder()); - } - // Add button - if gradient.positions.len() != position + 1 { - let on_update = move |_: &IconButton| { - let mut gradient = (*gradient_2).clone(); - - let get_color = |index: usize| match (gradient.positions[index].1, gradient.positions.get(index + 1).and_then(|x| x.1)) { - (Some(a), Some(b)) => Color::from_rgbaf32((a.r() + b.r()) / 2., (a.g() + b.g()) / 2., (a.b() + b.b()) / 2., ((a.a() + b.a()) / 2.).clamp(0., 1.)), - (Some(v), _) | (_, Some(v)) => Some(v), - _ => Some(Color::WHITE), - }; - let get_pos = |index: usize| (gradient.positions[index].0 + gradient.positions.get(index + 1).map(|v| v.0).unwrap_or(1.)) / 2.; - - gradient.positions.push((get_pos(position), get_color(position))); - - gradient.positions.sort_unstable_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); - - send_fill_message(gradient) - }; - - if !skip_separator { - widgets.push(Separator::new(SeparatorType::Related).widget_holder()); - } - widgets.push(IconButton::new("Add", 16).tooltip("Add a gradient stop after this").on_update(on_update).widget_holder()); - } - LayoutGroup::Row { widgets } -} - -fn node_section_fill(fill: &Fill) -> Option { - let initial_color = if let Fill::Solid(color) = fill { *color } else { Color::BLACK }; - - match fill { - Fill::Solid(_) | Fill::None => Some(LayoutGroup::Section { - name: "Fill".into(), - layout: vec![ - LayoutGroup::Row { - widgets: vec![ - TextLabel::new("Color").widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: These three separators add up to 24px, - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: which is the width of the Assist area. - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists. - Separator::new(SeparatorType::Unrelated).widget_holder(), - ColorButton::new(if let Fill::Solid(color) = fill { Some(*color) } else { None }) - .on_update(|text_input: &ColorButton| { - let fill = if let Some(value) = text_input.value { Fill::Solid(value) } else { Fill::None }; - PropertiesPanelMessage::ModifyFill { fill }.into() - }) - .widget_holder(), - ], - }, - LayoutGroup::Row { - widgets: vec![ - TextLabel::new("").widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: These three separators add up to 24px, - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: which is the width of the Assist area. - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists. - Separator::new(SeparatorType::Unrelated).widget_holder(), - TextButton::new("Use Gradient") - .tooltip("Change this fill from a solid color to a gradient") - .on_update(move |_: &TextButton| { - let (r, g, b, _) = initial_color.components(); - let opposite_color = Color::from_rgbaf32(1. - r, 1. - g, 1. - b, 1.).unwrap(); - - PropertiesPanelMessage::ModifyFill { - fill: Fill::Gradient(Gradient::new( - DVec2::new(0., 0.5), - initial_color, - DVec2::new(1., 0.5), - opposite_color, - DAffine2::IDENTITY, - generate_uuid(), - GradientType::Linear, - )), - } - .into() - }) - .widget_holder(), - ], - }, - ], - }), - Fill::Gradient(gradient) => Some(LayoutGroup::Section { - name: "Fill".into(), - layout: { - let cloned_gradient = gradient.clone(); - let first_color = gradient.positions.get(0).unwrap_or(&(0., None)).1; - - let mut layout = vec![node_gradient_type(gradient)]; - layout.extend((0..gradient.positions.len()).map(|pos| node_gradient_color(gradient, pos))); - layout.push(LayoutGroup::Row { - widgets: vec![ - TextLabel::new("").widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: These three separators add up to 24px, - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: which is the width of the Assist area. - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists. - Separator::new(SeparatorType::Unrelated).widget_holder(), - TextButton::new("Invert") - .icon(Some("Swap".into())) - .tooltip("Reverse the order of each color stop") - .on_update(move |_: &TextButton| { - let mut new_gradient = cloned_gradient.clone(); - new_gradient.positions = new_gradient.positions.iter().map(|(distance, color)| (1. - distance, *color)).collect(); - new_gradient.positions.reverse(); - PropertiesPanelMessage::ModifyFill { fill: Fill::Gradient(new_gradient) }.into() - }) - .widget_holder(), - ], - }); - layout.push(LayoutGroup::Row { - widgets: vec![ - TextLabel::new("").widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: These three separators add up to 24px, - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: which is the width of the Assist area. - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists. - Separator::new(SeparatorType::Unrelated).widget_holder(), - TextButton::new("Use Solid Color") - .tooltip("Change this fill from a gradient to a solid color, keeping the 0% stop color") - .on_update(move |_: &TextButton| { - PropertiesPanelMessage::ModifyFill { - fill: Fill::Solid(first_color.unwrap_or_default()), - } - .into() - }) - .widget_holder(), - ], - }); - layout - }, - }), - } -} - -fn node_section_stroke(stroke: &Stroke) -> LayoutGroup { - // We have to make multiple variables because they get moved into different closures. - let internal_stroke1 = stroke.clone(); - let internal_stroke2 = stroke.clone(); - let internal_stroke3 = stroke.clone(); - let internal_stroke4 = stroke.clone(); - let internal_stroke5 = stroke.clone(); - let internal_stroke6 = stroke.clone(); - let internal_stroke7 = stroke.clone(); - let internal_stroke8 = stroke.clone(); - let internal_stroke9 = stroke.clone(); - let internal_stroke10 = stroke.clone(); - let internal_stroke11 = stroke.clone(); - - LayoutGroup::Section { - name: "Stroke".into(), - layout: vec![ - LayoutGroup::Row { - widgets: vec![ - TextLabel::new("Color").widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: These three separators add up to 24px, - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: which is the width of the Assist area. - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists. - Separator::new(SeparatorType::Unrelated).widget_holder(), - ColorButton::new(stroke.color()) - .on_update(move |text_input: &ColorButton| { - internal_stroke1 - .clone() - .with_color(&text_input.value) - .map_or(PropertiesPanelMessage::ResendActiveProperties.into(), |stroke| PropertiesPanelMessage::ModifyStroke { stroke }.into()) - }) - .widget_holder(), - ], - }, - LayoutGroup::Row { - widgets: vec![ - TextLabel::new("Weight").widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: These three separators add up to 24px, - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: which is the width of the Assist area. - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists. - Separator::new(SeparatorType::Unrelated).widget_holder(), - NumberInput::new(Some(stroke.weight())) - .is_integer(false) - .min(0.) - .max((1u64 << std::f64::MANTISSA_DIGITS) as f64) - .unit(" px") - .on_update(move |number_input: &NumberInput| { - PropertiesPanelMessage::ModifyStroke { - stroke: internal_stroke2.clone().with_weight(number_input.value.unwrap()), - } - .into() - }) - .widget_holder(), - ], - }, - LayoutGroup::Row { - widgets: vec![ - TextLabel::new("Dash Lengths").widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: These three separators add up to 24px, - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: which is the width of the Assist area. - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists. - Separator::new(SeparatorType::Unrelated).widget_holder(), - TextInput::new(stroke.dash_lengths()) - .centered(true) - .on_update(move |text_input: &TextInput| { - internal_stroke3 - .clone() - .with_dash_lengths(&text_input.value) - .map_or(PropertiesPanelMessage::ResendActiveProperties.into(), |stroke| PropertiesPanelMessage::ModifyStroke { stroke }.into()) - }) - .widget_holder(), - ], - }, - LayoutGroup::Row { - widgets: vec![ - TextLabel::new("Dash Offset").widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: These three separators add up to 24px, - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: which is the width of the Assist area. - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists. - Separator::new(SeparatorType::Unrelated).widget_holder(), - NumberInput::new(Some(stroke.dash_offset())) - .is_integer(true) - .min(0.) - .max((1u64 << std::f64::MANTISSA_DIGITS) as f64) - .unit(" px") - .on_update(move |number_input: &NumberInput| { - PropertiesPanelMessage::ModifyStroke { - stroke: internal_stroke4.clone().with_dash_offset(number_input.value.unwrap()), - } - .into() - }) - .widget_holder(), - ], - }, - LayoutGroup::Row { - widgets: vec![ - TextLabel::new("Line Cap").widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: These three separators add up to 24px, - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: which is the width of the Assist area. - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists. - Separator::new(SeparatorType::Unrelated).widget_holder(), - RadioInput::new(vec![ - RadioEntryData::new("Butt").on_update(move |_| { - PropertiesPanelMessage::ModifyStroke { - stroke: internal_stroke6.clone().with_line_cap(LineCap::Butt), - } - .into() - }), - RadioEntryData::new("Round").on_update(move |_| { - PropertiesPanelMessage::ModifyStroke { - stroke: internal_stroke7.clone().with_line_cap(LineCap::Round), - } - .into() - }), - RadioEntryData::new("Square").on_update(move |_| { - PropertiesPanelMessage::ModifyStroke { - stroke: internal_stroke8.clone().with_line_cap(LineCap::Square), - } - .into() - }), - ]) - .selected_index(Some(stroke.line_cap_index())) - .widget_holder(), - ], - }, - LayoutGroup::Row { - widgets: vec![ - TextLabel::new("Line Join").widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: These three separators add up to 24px, - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: which is the width of the Assist area. - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists. - Separator::new(SeparatorType::Unrelated).widget_holder(), - RadioInput::new(vec![ - RadioEntryData::new("Miter").on_update(move |_| { - PropertiesPanelMessage::ModifyStroke { - stroke: internal_stroke9.clone().with_line_join(LineJoin::Miter), - } - .into() - }), - RadioEntryData::new("Bevel").on_update(move |_| { - PropertiesPanelMessage::ModifyStroke { - stroke: internal_stroke10.clone().with_line_join(LineJoin::Bevel), - } - .into() - }), - RadioEntryData::new("Round").on_update(move |_| { - PropertiesPanelMessage::ModifyStroke { - stroke: internal_stroke11.clone().with_line_join(LineJoin::Round), - } - .into() - }), - ]) - .selected_index(Some(stroke.line_join_index())) - .widget_holder(), - ], - }, - // TODO: Gray out this row when Line Join isn't set to Miter - LayoutGroup::Row { - widgets: vec![ - TextLabel::new("Miter Limit").widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: These three separators add up to 24px, - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: which is the width of the Assist area. - Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists. - Separator::new(SeparatorType::Unrelated).widget_holder(), - NumberInput::new(Some(stroke.line_join_miter_limit() as f64)) - .is_integer(true) - .min(0.) - .max((1u64 << std::f64::MANTISSA_DIGITS) as f64) - .unit("") - .on_update(move |number_input: &NumberInput| { - PropertiesPanelMessage::ModifyStroke { - stroke: internal_stroke5.clone().with_line_join_miter_limit(number_input.value.unwrap()), - } - .into() - }) - .widget_holder(), - ], - }, - ], - } -} - -trait DAffine2Utils { - fn scale_x(&self) -> f64; - fn update_scale_x(self, new_width: f64) -> Self; - fn scale_y(&self) -> f64; - fn update_scale_y(self, new_height: f64) -> Self; - fn x(&self) -> f64; - fn update_x(self, new_x: f64) -> Self; - fn y(&self) -> f64; - fn update_y(self, new_y: f64) -> Self; - fn rotation(&self) -> f64; - fn update_rotation(self, new_rotation: f64) -> Self; -} - -impl DAffine2Utils for DAffine2 { - fn scale_x(&self) -> f64 { - self.transform_vector2((1., 0.).into()).length() - } - - fn update_scale_x(self, new_width: f64) -> Self { - let scale_x = self.scale_x(); - if scale_x != 0. { - self * DAffine2::from_scale((new_width / scale_x, 1.).into()) - } else { - self - } - } - - fn scale_y(&self) -> f64 { - self.transform_vector2((0., 1.).into()).length() - } - - fn update_scale_y(self, new_height: f64) -> Self { - let scale_y = self.scale_y(); - if scale_y != 0. { - self * DAffine2::from_scale((1., new_height / scale_y).into()) - } else { - self - } - } - - fn x(&self) -> f64 { - self.translation.x - } - - fn update_x(mut self, new_x: f64) -> Self { - self.translation.x = new_x; - self - } - - fn y(&self) -> f64 { - self.translation.y - } - - fn update_y(mut self, new_y: f64) -> Self { - self.translation.y = new_y; - self - } - - fn rotation(&self) -> f64 { - if self.scale_x() != 0. { - let cos = self.matrix2.col(0).x / self.scale_x(); - let sin = self.matrix2.col(0).y / self.scale_x(); - sin.atan2(cos) - } else if self.scale_y() != 0. { - let sin = -self.matrix2.col(1).x / self.scale_y(); - let cos = self.matrix2.col(1).y / self.scale_y(); - sin.atan2(cos) - } else { - // Rotation information does not exists anymore in the matrix - // return 0 for user experience. - 0. - } - } - - fn update_rotation(self, new_rotation: f64) -> Self { - let width = self.scale_x(); - let height = self.scale_y(); - let half_width = width / 2.; - let half_height = height / 2.; - - let angle_translation_offset = |angle: f64| DVec2::new(-half_width * angle.cos() + half_height * angle.sin(), -half_width * angle.sin() - half_height * angle.cos()); - let angle_translation_adjustment = angle_translation_offset(new_rotation) - angle_translation_offset(self.rotation()); - - DAffine2::from_scale_angle_translation((width, height).into(), new_rotation, self.translation + angle_translation_adjustment) - } -} diff --git a/editor/src/messages/portfolio/document/properties_panel/utility_types.rs b/editor/src/messages/portfolio/document/properties_panel/utility_types.rs index 72be949703..dbe0142819 100644 --- a/editor/src/messages/portfolio/document/properties_panel/utility_types.rs +++ b/editor/src/messages/portfolio/document/properties_panel/utility_types.rs @@ -1,7 +1,5 @@ use document_legacy::document::Document as DocumentLegacy; -use document_legacy::LayerId; - -use serde::{Deserialize, Serialize}; +use document_legacy::document::LayerId; use crate::{messages::prelude::NodeGraphMessageHandler, node_graph_executor::NodeGraphExecutor}; @@ -12,14 +10,3 @@ pub struct PropertiesPanelMessageHandlerData<'a> { pub node_graph_message_handler: &'a NodeGraphMessageHandler, pub executor: &'a mut NodeGraphExecutor, } - -#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize, specta::Type)] -pub enum TransformOp { - X, - Y, - ScaleX, - ScaleY, - Width, - Height, - Rotation, -} diff --git a/editor/src/messages/portfolio/document/utility_types/layer_panel.rs b/editor/src/messages/portfolio/document/utility_types/layer_panel.rs index f93c52495f..a65d08f9b2 100644 --- a/editor/src/messages/portfolio/document/utility_types/layer_panel.rs +++ b/editor/src/messages/portfolio/document/utility_types/layer_panel.rs @@ -1,8 +1,6 @@ -use document_legacy::layers::layer_info::{LayerData, LayerDataTypeDiscriminant, LegacyLayer}; -use document_legacy::layers::style::RenderData; -use document_legacy::LayerId; +use document_legacy::document::LayerId; +use document_legacy::layers::layer_info::{LayerDataTypeDiscriminant, LegacyLayer}; -use glam::{DAffine2, DVec2}; use serde::ser::SerializeStruct; use serde::{Deserialize, Serialize}; @@ -60,45 +58,15 @@ pub struct LayerPanelEntry { impl LayerPanelEntry { // TODO: Deprecate this because it's using document-legacy layer data which is no longer linked to data from the node graph, // TODO: so this doesn't feed `name` (that's fed elsewhere) or `visible` (that's broken entirely), etc. - pub fn new(layer_metadata: &LayerMetadata, transform: DAffine2, layer: &LegacyLayer, path: Vec, render_data: &RenderData) -> Self { - let name = layer.name.clone().unwrap_or_else(|| String::from("")); - - let mut tooltip = name.clone(); - if cfg!(debug_assertions) { - tooltip += "\nLayer Path: "; - tooltip += &path.iter().map(|id| id.to_string()).collect::>().join(" / "); - tooltip = tooltip.trim().to_string(); - } - - let arr = layer.data.bounding_box(transform, render_data).unwrap_or([DVec2::ZERO, DVec2::ZERO]); - let arr = arr.iter().map(|x| (*x).into()).collect::>(); - let mut thumbnail = String::new(); - let mut svg_defs = String::new(); - layer.data.clone().render(&mut thumbnail, &mut svg_defs, &mut vec![transform], render_data); - let transform = transform.to_cols_array().iter().map(ToString::to_string).collect::>().join(","); - let thumbnail = if let [(x_min, y_min), (x_max, y_max)] = arr.as_slice() { - format!( - r#"{}{}"#, - x_min, - y_min, - x_max - x_min, - y_max - y_min, - svg_defs, - transform, - thumbnail, - ) - } else { - String::new() - }; - - LayerPanelEntry { - name, - tooltip, + pub fn new(layer_metadata: &LayerMetadata, layer: &LegacyLayer, path: Vec) -> Self { + Self { + name: "".to_string(), // Replaced before it gets used + tooltip: "".to_string(), // Replaced before it gets used visible: layer.visible, layer_type: (&layer.data).into(), layer_metadata: *layer_metadata, path, - thumbnail, + thumbnail: r#""#.to_string(), } } } diff --git a/editor/src/messages/portfolio/document/utility_types/misc.rs b/editor/src/messages/portfolio/document/utility_types/misc.rs index 1792d08175..4d5efda92c 100644 --- a/editor/src/messages/portfolio/document/utility_types/misc.rs +++ b/editor/src/messages/portfolio/document/utility_types/misc.rs @@ -1,6 +1,6 @@ pub use super::layer_panel::{LayerMetadata, LayerPanelEntry}; use document_legacy::document::Document as DocumentLegacy; -use document_legacy::LayerId; +use document_legacy::document::LayerId; use graphene_core::raster::color::Color; use serde::{Deserialize, Serialize}; diff --git a/editor/src/messages/portfolio/portfolio_message.rs b/editor/src/messages/portfolio/portfolio_message.rs index 501a75d581..f731b18606 100644 --- a/editor/src/messages/portfolio/portfolio_message.rs +++ b/editor/src/messages/portfolio/portfolio_message.rs @@ -1,9 +1,8 @@ use crate::messages::frontend::utility_types::{ExportBounds, FileType}; use crate::messages::portfolio::document::utility_types::clipboards::Clipboard; use crate::messages::prelude::*; +use document_legacy::document::LayerId; use document_legacy::document_metadata::LayerNodeIdentifier; -use document_legacy::LayerId; -use graph_craft::document::NodeId; use graphene_core::text::Font; use serde::{Deserialize, Serialize}; @@ -103,13 +102,6 @@ pub enum PortfolioMessage { SetActiveDocument { document_id: u64, }, - SetImageBlobUrl { - document_id: u64, - layer_path: Vec, - node_id: Option, - blob_url: String, - resolution: (f64, f64), - }, SubmitDocumentExport { file_name: String, file_type: FileType, diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 63f1bd7378..b6a8d80dcc 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -11,7 +11,6 @@ use crate::messages::prelude::*; use crate::messages::tool::utility_types::{HintData, HintGroup}; use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor}; -use document_legacy::layers::style::RenderData; use graph_craft::document::NodeId; use graphene_core::text::Font; @@ -105,12 +104,11 @@ impl MessageHandler { if self.active_document_id.is_some() { - responses.add(PropertiesPanelMessage::Deactivate); responses.add(BroadcastEvent::ToolAbort); responses.add(ToolMessage::DeactivateTools); // Clear relevant UI layouts if there are no documents - responses.add(PropertiesPanelMessage::ClearSelection); + responses.add(PropertiesPanelMessage::Clear); responses.add(DocumentMessage::ClearLayerTree); let hint_data = HintData(vec![HintGroup(vec![])]); responses.add(FrontendMessage::UpdateInputHints { hint_data }); @@ -134,7 +132,7 @@ impl MessageHandler { self.persistent_data.imaginate.poll_server_check(); - responses.add(PropertiesPanelMessage::ResendActiveProperties); + responses.add(PropertiesPanelMessage::Refresh); } PortfolioMessage::ImaginatePreferences => self.executor.update_imaginate_preferences(preferences.get_imaginate_preferences()), PortfolioMessage::ImaginateServerHostname => { @@ -492,25 +490,6 @@ impl MessageHandler { - if let Some(node_id) = node_id { - self.executor.insert_thumbnail_blob_url(blob_url, node_id, responses); - return; - } - let message = DocumentMessage::SetImageBlobUrl { - layer_path, - blob_url, - resolution, - document_id, - }; - responses.add(PortfolioMessage::DocumentPassMessage { document_id, message }); - } PortfolioMessage::SubmitDocumentExport { file_name, file_type, @@ -668,15 +647,13 @@ impl PortfolioMessageHandler { // TODO: Fix how this doesn't preserve tab order upon loading new document from *File > Load* fn load_document(&mut self, new_document: DocumentMessageHandler, document_id: u64, responses: &mut VecDeque) { - let render_data = RenderData::new(&self.persistent_data.font_cache, new_document.view_mode, None); - self.document_ids.push(document_id); responses.extend( new_document .layer_metadata .keys() - .filter_map(|path| new_document.layer_panel_entry_from_path(path, &render_data)) + .filter_map(|path| new_document.layer_panel_entry_from_path(path)) .map(|entry| FrontendMessage::UpdateDocumentLayerDetails { data: entry }.into()) .collect::>(), ); @@ -685,7 +662,6 @@ impl PortfolioMessageHandler { self.documents.insert(document_id, new_document); if self.active_document().is_some() { - responses.add(PropertiesPanelMessage::Deactivate); responses.add(BroadcastEvent::ToolAbort); responses.add(ToolMessage::DeactivateTools); } @@ -696,12 +672,10 @@ impl PortfolioMessageHandler { responses.add(PortfolioMessage::UpdateDocumentWidgets); responses.add(PortfolioMessage::GraphViewOverlay { open: self.graph_view_overlay_open }); responses.add(ToolMessage::InitTools); - responses.add(PropertiesPanelMessage::Init); responses.add(NodeGraphMessage::Init); responses.add(NavigationMessage::TranslateCanvas { delta: (0., 0.).into() }); responses.add(DocumentMessage::DocumentStructureChanged); - responses.add(PropertiesPanelMessage::ClearSelection); - responses.add(PropertiesPanelMessage::UpdateSelectedDocumentProperties); + responses.add(PropertiesPanelMessage::Clear); responses.add(NodeGraphMessage::UpdateNewNodeGraph); } diff --git a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs index 81887576ee..820ee8855f 100644 --- a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs +++ b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs @@ -2,7 +2,7 @@ use crate::messages::portfolio::document::node_graph::VectorDataModification; use crate::messages::prelude::*; use bezier_rs::{ManipulatorGroup, Subpath}; -use document_legacy::{document::Document, document_metadata::LayerNodeIdentifier, LayerId, Operation}; +use document_legacy::{document::Document, document_metadata::LayerNodeIdentifier}; use graph_craft::document::{value::TaggedValue, DocumentNode, NodeId, NodeInput, NodeNetwork}; use graphene_core::raster::{BlendMode, ImageFrame}; use graphene_core::text::Font; @@ -10,7 +10,7 @@ use graphene_core::uuid::ManipulatorGroupId; use graphene_core::vector::style::{FillType, Gradient}; use graphene_core::Color; -use glam::{DAffine2, DVec2}; +use glam::DVec2; use std::collections::VecDeque; /// Create a new vector layer from a vector of [`bezier_rs::Subpath`]. @@ -34,18 +34,6 @@ pub fn new_image_layer(image_frame: ImageFrame, id: NodeId, parent: Layer LayerNodeIdentifier::new_unchecked(id) } -/// Create a legacy node graph frame TODO: remove -pub fn new_custom_layer(network: NodeNetwork, layer_path: Vec, responses: &mut VecDeque) { - responses.add(DocumentMessage::DeselectAllLayers); - responses.add(Operation::AddFrame { - path: layer_path.clone(), - insert_index: -1, - transform: DAffine2::ZERO.to_cols_array(), - network, - }); - responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path }); -} - /// Batch set all of the manipulator groups to mirror on a specific layer pub fn set_manipulator_mirror_angle(manipulator_groups: &[ManipulatorGroup], layer: LayerNodeIdentifier, mirror_angle: bool, responses: &mut VecDeque) { for manipulator_group in manipulator_groups { @@ -77,12 +65,6 @@ pub fn get_pivot(layer: LayerNodeIdentifier, document: &Document) -> Option DVec2 { - let [min, max] = document.metadata.nonzero_bounding_box(layer); - let pivot = get_pivot(layer, document).unwrap_or(DVec2::splat(0.5)); - document.metadata.transform_to_document(layer).transform_point2(min + (max - min) * pivot) -} - pub fn get_viewport_pivot(layer: LayerNodeIdentifier, document: &Document) -> DVec2 { let [min, max] = document.metadata.nonzero_bounding_box(layer); let pivot = get_pivot(layer, document).unwrap_or(DVec2::splat(0.5)); @@ -234,25 +216,6 @@ impl<'a> NodeGraphLayer<'a> { }) } - /// Get the nearest layer node from the path and the document - pub fn new_from_path(layer: &[LayerId], document: &'a document_legacy::document::Document) -> Option { - let node_graph = &document.document_network; - let outwards_links = document.document_network.collect_outwards_links(); - - let Some(mut layer_node) = layer.last().copied() else { - error!("Tried to modify root layer"); - return None; - }; - while !node_graph.nodes.get(&layer_node)?.is_layer() { - layer_node = outwards_links.get(&layer_node)?.first().copied()?; - } - Some(Self { - node_graph, - _outwards_links: outwards_links, - layer_node, - }) - } - /// Return an iterator up the primary flow of the layer pub fn primary_layer_flow(&self) -> impl Iterator { self.node_graph.upstream_flow_back_from_nodes(vec![self.layer_node], true) diff --git a/editor/src/messages/tool/common_functionality/resize.rs b/editor/src/messages/tool/common_functionality/resize.rs index f9fb739e6f..93e414af73 100644 --- a/editor/src/messages/tool/common_functionality/resize.rs +++ b/editor/src/messages/tool/common_functionality/resize.rs @@ -4,7 +4,6 @@ use crate::messages::prelude::*; use crate::messages::tool::common_functionality::snapping::SnapManager; use document_legacy::document_metadata::LayerNodeIdentifier; -use document_legacy::layers::style::RenderData; use glam::{DAffine2, DVec2, Vec2Swizzles}; @@ -17,16 +16,16 @@ pub struct Resize { impl Resize { /// Starts a resize, assigning the snap targets and snapping the starting position. - pub fn start(&mut self, responses: &mut VecDeque, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, render_data: &RenderData) { - self.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true); + pub fn start(&mut self, responses: &mut VecDeque, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler) { + self.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true); self.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); let root_transform = document.metadata().document_to_viewport; self.drag_start = root_transform.inverse().transform_point2(self.snap_manager.snap_position(responses, document, input.mouse.position)); } /// Recalculates snap targets without snapping the starting position. - pub fn recalculate_snaps(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, render_data: &RenderData) { - self.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true); + pub fn recalculate_snaps(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler) { + self.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true); self.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); } diff --git a/editor/src/messages/tool/common_functionality/snapping.rs b/editor/src/messages/tool/common_functionality/snapping.rs index 6ed172535d..12f0ef7cca 100644 --- a/editor/src/messages/tool/common_functionality/snapping.rs +++ b/editor/src/messages/tool/common_functionality/snapping.rs @@ -2,10 +2,8 @@ use super::shape_editor::ManipulatorPointInfo; use crate::consts::{SNAP_AXIS_TOLERANCE, SNAP_POINT_TOLERANCE}; use crate::messages::prelude::*; -use document_legacy::document_metadata::LayerNodeIdentifier; +use document_legacy::document::LayerId; use document_legacy::layers::layer_info::LegacyLayer; -use document_legacy::LayerId; -use graphene_core::vector::{ManipulatorPointId, SelectedType}; use glam::DVec2; @@ -109,42 +107,44 @@ impl SnapManager { /// This should be called after start_snap pub fn add_snap_path( &mut self, - document_message_handler: &DocumentMessageHandler, - input: &InputPreprocessorMessageHandler, - layer: &LegacyLayer, - path: &[LayerId], - include_handles: bool, - ignore_points: &[ManipulatorPointInfo], + _document_message_handler: &DocumentMessageHandler, + _input: &InputPreprocessorMessageHandler, + _layer: &LegacyLayer, + _path: &[LayerId], + _include_handles: bool, + _ignore_points: &[ManipulatorPointInfo], ) { - let Some(vector_data) = &layer.as_vector_data() else { return }; + todo!(); - if !document_message_handler.snapping_state.node_snapping { - return; - }; + // let Some(vector_data) = &layer.as_vector_data() else { return }; - let transform = document_message_handler.document_legacy.multiply_transforms(path).unwrap(); - let snap_points = vector_data - .manipulator_groups() - .flat_map(|group| { - if include_handles { - [ - Some((ManipulatorPointId::new(group.id, SelectedType::Anchor), group.anchor)), - group.in_handle.map(|pos| (ManipulatorPointId::new(group.id, SelectedType::InHandle), pos)), - group.out_handle.map(|pos| (ManipulatorPointId::new(group.id, SelectedType::OutHandle), pos)), - ] - } else { - [Some((ManipulatorPointId::new(group.id, SelectedType::Anchor), group.anchor)), None, None] - } - }) - .flatten() - .filter(|&(point_id, _)| { - !ignore_points.contains(&ManipulatorPointInfo { - layer: LayerNodeIdentifier::from_path(path, document_message_handler.network()), - point_id, - }) - }) - .map(|(_, pos)| transform.transform_point2(pos)); - self.add_snap_points(document_message_handler, input, snap_points); + // if !document_message_handler.snapping_state.node_snapping { + // return; + // }; + + // let transform = document_message_handler.document_legacy.multiply_transforms(path).unwrap(); + // let snap_points = vector_data + // .manipulator_groups() + // .flat_map(|group| { + // if include_handles { + // [ + // Some((ManipulatorPointId::new(group.id, SelectedType::Anchor), group.anchor)), + // group.in_handle.map(|pos| (ManipulatorPointId::new(group.id, SelectedType::InHandle), pos)), + // group.out_handle.map(|pos| (ManipulatorPointId::new(group.id, SelectedType::OutHandle), pos)), + // ] + // } else { + // [Some((ManipulatorPointId::new(group.id, SelectedType::Anchor), group.anchor)), None, None] + // } + // }) + // .flatten() + // .filter(|&(point_id, _)| { + // !ignore_points.contains(&ManipulatorPointInfo { + // layer: LayerNodeIdentifier::from_path(path, document_message_handler.network()), + // point_id, + // }) + // }) + // .map(|(_, pos)| transform.transform_point2(pos)); + // self.add_snap_points(document_message_handler, input, snap_points); } /// Adds all of the shape handles in the document, including bézier handles of the points specified diff --git a/editor/src/messages/tool/tool_message_handler.rs b/editor/src/messages/tool/tool_message_handler.rs index 12f4901042..5e83424499 100644 --- a/editor/src/messages/tool/tool_message_handler.rs +++ b/editor/src/messages/tool/tool_message_handler.rs @@ -7,7 +7,6 @@ use crate::messages::prelude::*; use crate::messages::tool::utility_types::ToolType; use crate::node_graph_executor::NodeGraphExecutor; -use document_legacy::layers::style::RenderData; use graphene_core::raster::color::Color; #[derive(Debug, Default)] @@ -25,7 +24,7 @@ impl MessageHandler, (document, document_id, input, persistent_data, node_graph): (&DocumentMessageHandler, u64, &InputPreprocessorMessageHandler, &PersistentData, &NodeGraphExecutor), ) { - let render_data = RenderData::new(&persistent_data.font_cache, document.view_mode, None); + let font_cache = &persistent_data.font_cache; #[remain::sorted] match message { @@ -89,7 +88,7 @@ impl MessageHandler) -> bool { + fn select_artboard(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) -> bool { responses.add(DocumentMessage::StartTransaction); let mut intersections = document @@ -143,15 +140,14 @@ impl ArtboardToolData { if let Some(intersection) = intersections.next() { self.selected_artboard = Some(intersection); - self.snap_manager - .start_snap(document, input, document.bounding_boxes(None, Some(intersection.to_node()), render_data), true, true); + self.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true); self.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); true } else { self.selected_artboard = None; - responses.add(PropertiesPanelMessage::ClearSelection); + responses.add(PropertiesPanelMessage::Clear); false } @@ -183,7 +179,7 @@ impl Fsm for ArtboardToolFsmState { type ToolOptions = (); fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, _tool_options: &(), responses: &mut VecDeque) -> Self { - let ToolActionHandlerData { document, input, render_data, .. } = tool_action_data; + let ToolActionHandlerData { document, input, .. } = tool_action_data; let ToolMessage::Artboard(event) = event else { return self; @@ -210,10 +206,10 @@ impl Fsm for ArtboardToolFsmState { if let Some(selected_edges) = tool_data.check_dragging_bounds(input.mouse.position) { responses.add(DocumentMessage::StartTransaction); - tool_data.start_resizing(selected_edges, document, render_data, input); + tool_data.start_resizing(selected_edges, document, input); ArtboardToolFsmState::ResizingBounds - } else if tool_data.select_artboard(document, render_data, input, responses) { + } else if tool_data.select_artboard(document, input, responses) { ArtboardToolFsmState::Dragging } else { ArtboardToolFsmState::Drawing @@ -284,7 +280,7 @@ impl Fsm for ArtboardToolFsmState { let id = generate_uuid(); tool_data.selected_artboard = Some(LayerNodeIdentifier::new_unchecked(id)); - tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, Some(id), render_data), true, true); + tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true); tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); responses.add(GraphOperationMessage::NewArtboard { @@ -367,11 +363,6 @@ impl Fsm for ArtboardToolFsmState { ArtboardToolFsmState::Ready } (_, ArtboardToolMessage::Abort) => { - // Register properties when switching back to other tools - responses.add(PropertiesPanelMessage::SetActiveLayers { - paths: document.selected_layers().map(|path| path.to_vec()).collect(), - }); - tool_data.snap_manager.cleanup(responses); responses.add(OverlaysMessage::Draw); ArtboardToolFsmState::Ready diff --git a/editor/src/messages/tool/tool_messages/ellipse_tool.rs b/editor/src/messages/tool/tool_messages/ellipse_tool.rs index de89d71e66..89f05a4aea 100644 --- a/editor/src/messages/tool/tool_messages/ellipse_tool.rs +++ b/editor/src/messages/tool/tool_messages/ellipse_tool.rs @@ -185,11 +185,7 @@ impl Fsm for EllipseToolFsmState { fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { let ToolActionHandlerData { - document, - global_tool_data, - input, - render_data, - .. + document, global_tool_data, input, .. } = tool_action_data; let shape_data = &mut tool_data.data; @@ -199,11 +195,11 @@ impl Fsm for EllipseToolFsmState { }; match (self, event) { (EllipseToolFsmState::Drawing, EllipseToolMessage::CanvasTransformed) => { - tool_data.data.recalculate_snaps(document, input, render_data); + tool_data.data.recalculate_snaps(document, input); self } (EllipseToolFsmState::Ready, EllipseToolMessage::DragStart) => { - shape_data.start(responses, document, input, render_data); + shape_data.start(responses, document, input); responses.add(DocumentMessage::StartTransaction); // Create a new ellipse vector shape diff --git a/editor/src/messages/tool/tool_messages/fill_tool.rs b/editor/src/messages/tool/tool_messages/fill_tool.rs index 5642661c0a..efd562d014 100644 --- a/editor/src/messages/tool/tool_messages/fill_tool.rs +++ b/editor/src/messages/tool/tool_messages/fill_tool.rs @@ -1,5 +1,6 @@ use super::tool_prelude::*; -use document_legacy::layers::style::Fill; + +use graphene_core::vector::style::Fill; #[derive(Default)] pub struct FillTool { @@ -80,9 +81,6 @@ impl Fsm for FillToolFsmState { let fill = Fill::Solid(color); responses.add(DocumentMessage::StartTransaction); - responses.add(DocumentMessage::SetSelectedLayers { - replacement_selected_layers: vec![layer.clone()], - }); responses.add(GraphOperationMessage::FillSet { layer, fill }); responses.add(DocumentMessage::CommitTransaction); diff --git a/editor/src/messages/tool/tool_messages/gradient_tool.rs b/editor/src/messages/tool/tool_messages/gradient_tool.rs index 656592752f..a4d88fefd6 100644 --- a/editor/src/messages/tool/tool_messages/gradient_tool.rs +++ b/editor/src/messages/tool/tool_messages/gradient_tool.rs @@ -6,8 +6,8 @@ use crate::messages::tool::common_functionality::graph_modification_utils::get_g use crate::messages::tool::common_functionality::snapping::SnapManager; use document_legacy::document_metadata::LayerNodeIdentifier; -use document_legacy::layers::style::{Fill, Gradient, GradientType, RenderData}; use graphene_core::raster::color::Color; +use graphene_core::vector::style::{Fill, Gradient, GradientType}; #[derive(Default)] pub struct GradientTool { @@ -267,8 +267,8 @@ struct GradientToolData { drag_start: DVec2, } -pub fn start_snap(snap_manager: &mut SnapManager, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, render_data: &RenderData) { - snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true); +pub fn start_snap(snap_manager: &mut SnapManager, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler) { + snap_manager.start_snap(document, input, document.bounding_boxes(), true, true); snap_manager.add_all_document_handles(document, input, &[], &[], &[]); } @@ -278,11 +278,7 @@ impl Fsm for GradientToolFsmState { fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { let ToolActionHandlerData { - document, - global_tool_data, - input, - render_data, - .. + document, global_tool_data, input, .. } = tool_action_data; let ToolMessage::Gradient(event) = event else { @@ -434,7 +430,7 @@ impl Fsm for GradientToolFsmState { let pos = transform.transform_point2(pos); if pos.distance_squared(mouse) < tolerance { dragging = true; - start_snap(&mut tool_data.snap_manager, document, input, render_data); + start_snap(&mut tool_data.snap_manager, document, input); tool_data.selected_gradient = Some(SelectedGradient { layer, transform, @@ -479,7 +475,7 @@ impl Fsm for GradientToolFsmState { tool_data.selected_gradient = Some(selected_gradient); - start_snap(&mut tool_data.snap_manager, document, input, render_data); + start_snap(&mut tool_data.snap_manager, document, input); GradientToolFsmState::Drawing } else { diff --git a/editor/src/messages/tool/tool_messages/imaginate_tool.rs b/editor/src/messages/tool/tool_messages/imaginate_tool.rs index b7b127d291..a8e6f3337a 100644 --- a/editor/src/messages/tool/tool_messages/imaginate_tool.rs +++ b/editor/src/messages/tool/tool_messages/imaginate_tool.rs @@ -3,9 +3,7 @@ use crate::messages::portfolio::document::node_graph::{self, IMAGINATE_NODE}; use crate::messages::tool::common_functionality::resize::Resize; use document_legacy::document_metadata::LayerNodeIdentifier; -use document_legacy::Operation; -use glam::DAffine2; use serde::{Deserialize, Serialize}; #[derive(Default)] @@ -105,7 +103,7 @@ impl Fsm for ImaginateToolFsmState { self, event: ToolMessage, tool_data: &mut Self::ToolData, - ToolActionHandlerData { document, input, render_data, .. }: &mut ToolActionHandlerData, + ToolActionHandlerData { document, input, .. }: &mut ToolActionHandlerData, _tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { @@ -116,12 +114,12 @@ impl Fsm for ImaginateToolFsmState { }; match (self, event) { (_, ImaginateToolMessage::DocumentIsDirty | ImaginateToolMessage::SelectionChanged) => { - //tool_data.path_outlines.update_selected(document.document_legacy.selected_visible_layers(), document, responses, render_data); + //tool_data.path_outlines.update_selected(document.document_legacy.selected_visible_layers(), document, responses, font_cache); self } (ImaginateToolFsmState::Ready, ImaginateToolMessage::DragStart) => { - shape_data.start(responses, document, input, render_data); + shape_data.start(responses, document, input); responses.add(DocumentMessage::StartTransaction); shape_data.layer = Some(LayerNodeIdentifier::new(generate_uuid(), document.network())); responses.add(DocumentMessage::DeselectAllLayers); @@ -156,16 +154,16 @@ impl Fsm for ImaginateToolFsmState { imaginate_node_id, imaginate_node_type.to_document_node_default_inputs([Some(graph_craft::document::NodeInput::node(transform_node_id, 0))], next_pos()), ); - - // Add a layer with a frame to the document - responses.add(Operation::AddFrame { - path: shape_data.layer.unwrap().to_path(), - insert_index: -1, - transform: DAffine2::ZERO.to_cols_array(), - network, - }); responses.add(NodeGraphMessage::ShiftNode { node_id: imaginate_node_id }); + // // Add a layer with a frame to the document + // responses.add(Operation::AddFrame { + // path: shape_data.layer.unwrap().to_path(), + // insert_index: -1, + // transform: DAffine2::ZERO.to_cols_array(), + // network, + // }); + ImaginateToolFsmState::Drawing } (state, ImaginateToolMessage::Resize { center, lock_ratio }) => { diff --git a/editor/src/messages/tool/tool_messages/line_tool.rs b/editor/src/messages/tool/tool_messages/line_tool.rs index 43ccf1fc99..cdf1b0d0ba 100644 --- a/editor/src/messages/tool/tool_messages/line_tool.rs +++ b/editor/src/messages/tool/tool_messages/line_tool.rs @@ -166,11 +166,7 @@ impl Fsm for LineToolFsmState { fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { let ToolActionHandlerData { - document, - global_tool_data, - input, - render_data, - .. + document, global_tool_data, input, .. } = tool_action_data; let ToolMessage::Line(event) = event else { @@ -178,7 +174,7 @@ impl Fsm for LineToolFsmState { }; match (self, event) { (LineToolFsmState::Ready, LineToolMessage::DragStart) => { - tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true); + tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true); tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); let viewport_start = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index a69729e570..1987281a22 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -257,7 +257,7 @@ impl PathToolData { //self // .snap_manager - // .start_snap(document, input, document.bounding_boxes(Some(&selected_layers), None, render_data), true, true); + // .start_snap(document, input, document.bounding_boxes(Some(&selected_layers), None, font_cache), true, true); // Do not snap against handles when anchor is selected let mut additional_selected_points = Vec::new(); diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 122de53aba..3424755ad8 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -543,7 +543,6 @@ impl Fsm for PenToolFsmState { document, global_tool_data, input, - render_data, shape_editor, .. } = tool_action_data; @@ -568,7 +567,7 @@ impl Fsm for PenToolFsmState { }; match (self, event) { (_, PenToolMessage::CanvasTransformed) => { - tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true); + tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true); self } (_, PenToolMessage::SelectionChanged) => { @@ -591,7 +590,7 @@ impl Fsm for PenToolFsmState { responses.add(DocumentMessage::StartTransaction); // Initialize snapping - tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true); + tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true); tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); // Disable this tool's mirroring diff --git a/editor/src/messages/tool/tool_messages/polygon_tool.rs b/editor/src/messages/tool/tool_messages/polygon_tool.rs index 37b6266ae2..20c9d278c2 100644 --- a/editor/src/messages/tool/tool_messages/polygon_tool.rs +++ b/editor/src/messages/tool/tool_messages/polygon_tool.rs @@ -225,11 +225,7 @@ impl Fsm for PolygonToolFsmState { fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { let ToolActionHandlerData { - document, - global_tool_data, - input, - render_data, - .. + document, global_tool_data, input, .. } = tool_action_data; let polygon_data = &mut tool_data.data; @@ -239,11 +235,11 @@ impl Fsm for PolygonToolFsmState { }; match (self, event) { (PolygonToolFsmState::Drawing, PolygonToolMessage::CanvasTransformed) => { - tool_data.data.recalculate_snaps(document, input, render_data); + tool_data.data.recalculate_snaps(document, input); self } (PolygonToolFsmState::Ready, PolygonToolMessage::DragStart) => { - polygon_data.start(responses, document, input, render_data); + polygon_data.start(responses, document, input); responses.add(DocumentMessage::StartTransaction); let subpath = match tool_options.primitive_shape_type { diff --git a/editor/src/messages/tool/tool_messages/rectangle_tool.rs b/editor/src/messages/tool/tool_messages/rectangle_tool.rs index 3f4330ab71..730641b1a2 100644 --- a/editor/src/messages/tool/tool_messages/rectangle_tool.rs +++ b/editor/src/messages/tool/tool_messages/rectangle_tool.rs @@ -189,11 +189,7 @@ impl Fsm for RectangleToolFsmState { event: ToolMessage, tool_data: &mut Self::ToolData, ToolActionHandlerData { - document, - global_tool_data, - input, - render_data, - .. + document, global_tool_data, input, .. }: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque, @@ -209,11 +205,11 @@ impl Fsm for RectangleToolFsmState { match (self, event) { (Drawing, CanvasTransformed) => { - tool_data.data.recalculate_snaps(document, input, render_data); + tool_data.data.recalculate_snaps(document, input); self } (Ready, DragStart) => { - shape_data.start(responses, document, input, render_data); + shape_data.start(responses, document, input); let subpath = bezier_rs::Subpath::new_rect(DVec2::ZERO, DVec2::ONE); diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index b683638490..a194393d7a 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -382,7 +382,7 @@ impl Fsm for SelectToolFsmState { type ToolOptions = (); fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, _tool_options: &(), responses: &mut VecDeque) -> Self { - let ToolActionHandlerData { document, input, render_data, .. } = tool_action_data; + let ToolActionHandlerData { document, input, .. } = tool_action_data; let ToolMessage::Select(event) = event else { return self; @@ -482,7 +482,7 @@ impl Fsm for SelectToolFsmState { let state = if tool_data.pivot.is_over(input.mouse.position) { responses.add(DocumentMessage::StartTransaction); - tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true); + tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true); tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); SelectToolFsmState::DraggingPivot @@ -494,7 +494,7 @@ impl Fsm for SelectToolFsmState { // // tool_data // .snap_manager - // .start_snap(document, input, document.bounding_boxes(Some(&selected), None, render_data), snap_x, snap_y); + // .start_snap(document, input, document.bounding_boxes(Some(&selected), None, font_cache), snap_x, snap_y); // tool_data // .snap_manager // .add_all_document_handles(document, input, &[], &selected.iter().map(|x| x.as_slice()).collect::>(), &[]); @@ -550,7 +550,7 @@ impl Fsm for SelectToolFsmState { // tool_data // .snap_manager - // .start_snap(document, input, document.bounding_boxes(Some(&tool_data.layers_dragging), None, render_data), true, true); + // .start_snap(document, input, document.bounding_boxes(Some(&tool_data.layers_dragging), None, font_cache), true, true); SelectToolFsmState::Dragging } else { @@ -922,7 +922,7 @@ fn drag_shallowest_manipulation(responses: &mut VecDeque, selected: Vec }); // tool_data // .snap_manager - // .start_snap(document, input, document.bounding_boxes(Some(&tool_data.layers_dragging), None, render_data), true, true); + // .start_snap(document, input, document.bounding_boxes(Some(&tool_data.layers_dragging), None, font_cache), true, true); } fn drag_deepest_manipulation(responses: &mut VecDeque, mut selected: Vec, tool_data: &mut SelectToolData) { @@ -932,7 +932,7 @@ fn drag_deepest_manipulation(responses: &mut VecDeque, mut selected: Ve }); // tool_data // .snap_manager - // .start_snap(document, input, document.bounding_boxes(Some(&tool_data.layers_dragging), None, render_data), true, true); + // .start_snap(document, input, document.bounding_boxes(Some(&tool_data.layers_dragging), None, font_cache), true, true); } fn edit_layer_shallowest_manipulation(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, responses: &mut VecDeque) { diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index 1e1ef7d4be..4c44e095d2 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -196,11 +196,7 @@ impl Fsm for SplineToolFsmState { fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { let ToolActionHandlerData { - document, - global_tool_data, - input, - render_data, - .. + document, global_tool_data, input, .. } = tool_action_data; let ToolMessage::Spline(event) = event else { @@ -208,7 +204,7 @@ impl Fsm for SplineToolFsmState { }; match (self, event) { (_, SplineToolMessage::CanvasTransformed) => { - tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true); + tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true); self } (SplineToolFsmState::Ready, SplineToolMessage::DragStart) => { @@ -218,7 +214,7 @@ impl Fsm for SplineToolFsmState { let parent = document.new_layer_parent(); let transform = document.metadata().transform_to_viewport(parent); - tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true); + tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true); tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); diff --git a/editor/src/messages/tool/tool_messages/text_tool.rs b/editor/src/messages/tool/tool_messages/text_tool.rs index 720843e1cb..cbeeca8b55 100644 --- a/editor/src/messages/tool/tool_messages/text_tool.rs +++ b/editor/src/messages/tool/tool_messages/text_tool.rs @@ -7,10 +7,10 @@ use crate::messages::tool::common_functionality::color_selector::{ToolColorOptio use crate::messages::tool::common_functionality::graph_modification_utils::{self, is_layer_fed_by_node_of_name}; use document_legacy::document_metadata::LayerNodeIdentifier; -use document_legacy::layers::style::{Fill, RenderData}; use graph_craft::document::value::TaggedValue; use graphene_core::renderer::Quad; -use graphene_core::text::{load_face, Font}; +use graphene_core::text::{load_face, Font, FontCache}; +use graphene_core::vector::style::Fill; use graphene_core::Color; #[derive(Default)] @@ -224,7 +224,7 @@ struct TextToolData { impl TextToolData { /// Set the editing state of the currently modifying layer - fn set_editing(&self, editable: bool, render_data: &RenderData, document: &DocumentMessageHandler, responses: &mut VecDeque) { + fn set_editing(&self, editable: bool, font_cache: &FontCache, document: &DocumentMessageHandler, responses: &mut VecDeque) { if let Some(node_id) = graph_modification_utils::get_fill_id(self.layer, &document.document_legacy) { responses.add(NodeGraphMessage::SetHidden { node_id, hidden: editable }); } @@ -235,7 +235,7 @@ impl TextToolData { line_width: None, font_size: editing_text.font_size, color: editing_text.color.unwrap_or(Color::BLACK), - url: render_data.font_cache.get_preview_url(&editing_text.font).cloned().unwrap_or_default(), + url: font_cache.get_preview_url(&editing_text.font).cloned().unwrap_or_default(), transform: editing_text.transform.to_cols_array(), }); } else { @@ -258,9 +258,9 @@ impl TextToolData { Some(()) } - fn start_editing_layer(&mut self, layer: LayerNodeIdentifier, tool_state: TextToolFsmState, document: &DocumentMessageHandler, render_data: &RenderData, responses: &mut VecDeque) { + fn start_editing_layer(&mut self, layer: LayerNodeIdentifier, tool_state: TextToolFsmState, document: &DocumentMessageHandler, font_cache: &FontCache, responses: &mut VecDeque) { if tool_state == TextToolFsmState::Editing { - self.set_editing(false, render_data, document, responses); + self.set_editing(false, font_cache, document, responses); } self.layer = layer; @@ -268,19 +268,19 @@ impl TextToolData { responses.add(DocumentMessage::StartTransaction); - self.set_editing(true, render_data, document, responses); + self.set_editing(true, font_cache, document, responses); responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![self.layer.to_node()] }); } - fn interact(&mut self, state: TextToolFsmState, mouse: DVec2, document: &DocumentMessageHandler, render_data: &RenderData, responses: &mut VecDeque) -> TextToolFsmState { + fn interact(&mut self, state: TextToolFsmState, mouse: DVec2, document: &DocumentMessageHandler, font_cache: &FontCache, responses: &mut VecDeque) -> TextToolFsmState { // Check if the user has selected an existing text layer if let Some(clicked_text_layer_path) = document .document_legacy .click(mouse, document.network()) .filter(|&layer| is_layer_fed_by_node_of_name(layer, &document.document_legacy, "Text")) { - self.start_editing_layer(clicked_text_layer_path, state, document, render_data, responses); + self.start_editing_layer(clicked_text_layer_path, state, document, font_cache, responses); TextToolFsmState::Editing } @@ -309,32 +309,32 @@ impl TextToolData { skip_rerender: true, }); - self.set_editing(true, render_data, document, responses); + self.set_editing(true, font_cache, document, responses); responses.add(NodeGraphMessage::SelectedNodesSet { nodes: self.layer.to_path() }); TextToolFsmState::Editing } else { // Removing old text as editable - self.set_editing(false, render_data, document, responses); + self.set_editing(false, font_cache, document, responses); TextToolFsmState::Ready } } - fn get_bounds(&self, text: &str, render_data: &RenderData) -> Option<[DVec2; 2]> { + fn get_bounds(&self, text: &str, font_cache: &FontCache) -> Option<[DVec2; 2]> { let editing_text = self.editing_text.as_ref()?; - let buzz_face = render_data.font_cache.get(&editing_text.font).map(|data| load_face(data)); + let buzz_face = font_cache.get(&editing_text.font).map(|data| load_face(data)); let subpaths = graphene_core::text::to_path(text, buzz_face, editing_text.font_size, None); let bounds = subpaths.iter().filter_map(|subpath| subpath.bounding_box()); let combined_bounds = bounds.reduce(|a, b| [a[0].min(b[0]), a[1].max(b[1])]).unwrap_or_default(); Some(combined_bounds) } - fn fix_text_bounds(&self, new_text: &str, _document: &DocumentMessageHandler, render_data: &RenderData, responses: &mut VecDeque) -> Option<()> { + fn fix_text_bounds(&self, new_text: &str, _document: &DocumentMessageHandler, font_cache: &FontCache, responses: &mut VecDeque) -> Option<()> { let layer = self.layer.to_path(); - let old_bounds = self.get_bounds(&self.editing_text.as_ref()?.text, render_data)?; - let new_bounds = self.get_bounds(new_text, render_data)?; + let old_bounds = self.get_bounds(&self.editing_text.as_ref()?.text, font_cache)?; + let new_bounds = self.get_bounds(new_text, font_cache)?; responses.add(GraphOperationMessage::UpdateBounds { layer, old_bounds, new_bounds }); Some(()) @@ -366,7 +366,7 @@ impl Fsm for TextToolFsmState { document, global_tool_data, input, - render_data, + font_cache, .. } = transition_data; let ToolMessage::Text(event) = event else { @@ -378,7 +378,7 @@ impl Fsm for TextToolFsmState { transform: document.metadata().transform_to_viewport(tool_data.layer).to_cols_array(), }); if let Some(editing_text) = tool_data.editing_text.as_ref() { - let buzz_face = render_data.font_cache.get(&editing_text.font).map(|data| load_face(data)); + let buzz_face = font_cache.get(&editing_text.font).map(|data| load_face(data)); let far = graphene_core::text::bounding_box(&tool_data.new_text, buzz_face, editing_text.font_size, None); if far.x != 0. && far.y != 0. { let quad = Quad::from_box([DVec2::ZERO, far]); @@ -394,7 +394,7 @@ impl Fsm for TextToolFsmState { let Some((text, font, font_size)) = graph_modification_utils::get_text(layer, &document.document_legacy) else { continue; }; - let buzz_face = render_data.font_cache.get(font).map(|data| load_face(data)); + let buzz_face = font_cache.get(font).map(|data| load_face(data)); let far = graphene_core::text::bounding_box(text, buzz_face, font_size, None); let quad = Quad::from_box([DVec2::ZERO, far]); let multiplied = document.metadata().transform_to_viewport(layer) * quad; @@ -413,11 +413,11 @@ impl Fsm for TextToolFsmState { }); tool_data.new_text = String::new(); - tool_data.interact(state, input.mouse.position, document, render_data, responses) + tool_data.interact(state, input.mouse.position, document, font_cache, responses) } (state, TextToolMessage::EditSelected) => { if let Some(layer) = can_edit_selected(document) { - tool_data.start_editing_layer(layer, state, document, render_data, responses); + tool_data.start_editing_layer(layer, state, document, font_cache, responses); return TextToolFsmState::Editing; } @@ -425,7 +425,7 @@ impl Fsm for TextToolFsmState { } (state, TextToolMessage::Abort) => { if state == TextToolFsmState::Editing { - tool_data.set_editing(false, render_data, document, responses); + tool_data.set_editing(false, font_cache, document, responses); } TextToolFsmState::Ready @@ -436,7 +436,7 @@ impl Fsm for TextToolFsmState { TextToolFsmState::Editing } (TextToolFsmState::Editing, TextToolMessage::TextChange { new_text }) => { - tool_data.fix_text_bounds(&new_text, document, render_data, responses); + tool_data.fix_text_bounds(&new_text, document, font_cache, responses); responses.add(NodeGraphMessage::SetQualifiedInputValue { layer_path: Vec::new(), node_path: vec![graph_modification_utils::get_text_id(tool_data.layer, &document.document_legacy).unwrap()], @@ -444,7 +444,7 @@ impl Fsm for TextToolFsmState { value: TaggedValue::String(new_text), }); - tool_data.set_editing(false, render_data, document, responses); + tool_data.set_editing(false, font_cache, document, responses); TextToolFsmState::Ready } diff --git a/editor/src/messages/tool/utility_types.rs b/editor/src/messages/tool/utility_types.rs index 5dcb2a9f30..4d1347055c 100644 --- a/editor/src/messages/tool/utility_types.rs +++ b/editor/src/messages/tool/utility_types.rs @@ -12,8 +12,8 @@ use crate::messages::portfolio::document::overlays::utility_types::OverlayProvid use crate::messages::prelude::*; use crate::node_graph_executor::NodeGraphExecutor; -use document_legacy::layers::style::RenderData; use graphene_core::raster::color::Color; +use graphene_std::text::FontCache; use serde::{Deserialize, Serialize}; use std::fmt::{self, Debug}; @@ -23,7 +23,7 @@ pub struct ToolActionHandlerData<'a> { pub document_id: u64, pub global_tool_data: &'a DocumentToolData, pub input: &'a InputPreprocessorMessageHandler, - pub render_data: &'a RenderData<'a>, + pub font_cache: &'a FontCache, pub shape_editor: &'a mut ShapeState, pub node_graph: &'a NodeGraphExecutor, } @@ -33,7 +33,7 @@ impl<'a> ToolActionHandlerData<'a> { document_id: u64, global_tool_data: &'a DocumentToolData, input: &'a InputPreprocessorMessageHandler, - render_data: &'a RenderData<'a>, + font_cache: &'a FontCache, shape_editor: &'a mut ShapeState, node_graph: &'a NodeGraphExecutor, ) -> Self { @@ -42,7 +42,7 @@ impl<'a> ToolActionHandlerData<'a> { document_id, global_tool_data, input, - render_data, + font_cache, shape_editor, node_graph, } diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index da0473c086..2b4a3bd299 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -6,9 +6,9 @@ use crate::messages::portfolio::document::utility_types::misc::{LayerMetadata, L use crate::messages::prelude::*; use document_legacy::document::Document as DocumentLegacy; +use document_legacy::document::LayerId; use document_legacy::document_metadata::LayerNodeIdentifier; use document_legacy::layers::layer_info::{LayerDataTypeDiscriminant, LegacyLayerType}; -use document_legacy::{LayerId, Operation}; use graph_craft::document::value::TaggedValue; use graph_craft::document::{generate_uuid, DocumentNodeImplementation, NodeId, NodeNetwork}; use graph_craft::graphene_compiler::Compiler; @@ -626,9 +626,7 @@ impl NodeGraphExecutor { name: document.document_network.nodes.get(&node_id).map(|node| node.alias.clone()).unwrap_or_default(), tooltip: if cfg!(debug_assertions) { format!("Layer ID: {node_id}") } else { "".into() }, visible: !document.document_network.disabled.contains(&layer.to_node()), - layer_type: if document.metadata.is_artboard(layer) { - LayerDataTypeDiscriminant::Artboard - } else if document.metadata.is_folder(layer) { + layer_type: if document.metadata.is_folder(layer) { LayerDataTypeDiscriminant::Folder } else { LayerDataTypeDiscriminant::Layer @@ -653,12 +651,9 @@ impl NodeGraphExecutor { responses.add(DocumentMessage::RenderDocument); responses.add(DocumentMessage::DocumentStructureChanged); responses.add(BroadcastEvent::DocumentIsDirty); - responses.add(DocumentMessage::DirtyRenderDocument); responses.add(OverlaysMessage::Draw); } - NodeGraphUpdate::NodeGraphUpdateMessage(NodeGraphUpdateMessage::ImaginateStatusUpdate) => { - responses.add(DocumentMessage::PropertiesPanel(PropertiesPanelMessage::ResendActiveProperties)) - } + NodeGraphUpdate::NodeGraphUpdateMessage(NodeGraphUpdateMessage::ImaginateStatusUpdate) => responses.add(DocumentMessage::PropertiesPanel(PropertiesPanelMessage::Refresh)), } } Ok(()) @@ -685,10 +680,8 @@ impl NodeGraphExecutor { fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, layer_path: Vec, transform: DAffine2, responses: &mut VecDeque) -> Result<(), String> { self.last_output_type.insert(layer_path.clone(), Some(node_graph_output.ty())); match node_graph_output { - TaggedValue::SurfaceFrame(SurfaceFrame { surface_id, transform }) => { - let transform = transform.to_cols_array(); - responses.add(Operation::SetLayerTransform { path: layer_path.clone(), transform }); - responses.add(Operation::SetSurface { path: layer_path, surface_id }); + TaggedValue::SurfaceFrame(SurfaceFrame { surface_id: _, transform: _ }) => { + // TODO: Reimplement this now that document-legacy is gone } TaggedValue::RenderOutput(graphene_std::wasm_application_io::RenderOutput::Svg(svg)) => { // Send to frontend diff --git a/frontend/src/state-providers/portfolio.ts b/frontend/src/state-providers/portfolio.ts index 5efdff7c55..46c14da07e 100644 --- a/frontend/src/state-providers/portfolio.ts +++ b/frontend/src/state-providers/portfolio.ts @@ -112,7 +112,7 @@ export function createPortfolioState(editor: Editor) { image.src = blobURL; await image.decode(); - editor.instance.setImageBlobURL(updateImageData.documentId, element.path, element.nodeId, blobURL, image.naturalWidth, image.naturalHeight, element.transform); + // editor.instance.setImageBlobURL(updateImageData.documentId, element.path, element.nodeId, blobURL, image.naturalWidth, image.naturalHeight, element.transform); }); }); editor.subscriptions.subscribeJsMessage(TriggerRevokeBlobUrl, async (triggerRevokeBlobUrl) => { diff --git a/frontend/src/wasm-communication/editor.ts b/frontend/src/wasm-communication/editor.ts index 6709491b46..b802ec107e 100644 --- a/frontend/src/wasm-communication/editor.ts +++ b/frontend/src/wasm-communication/editor.ts @@ -10,7 +10,7 @@ export type Editor = Readonly>; // `wasmImport` starts uninitialized because its initialization needs to occur asynchronously, and thus needs to occur by manually calling and awaiting `initWasm()` let wasmImport: WebAssembly.Memory | undefined; -export async function updateImage(path: BigUint64Array, nodeId: bigint, mime: string, imageData: Uint8Array, transform: Float64Array, documentId: bigint) { +export async function updateImage(path: BigUint64Array, nodeId: bigint, mime: string, imageData: Uint8Array, _transform: Float64Array, _documentId: bigint) { const blob = new Blob([imageData], { type: mime }); const blobURL = URL.createObjectURL(blob); @@ -21,10 +21,10 @@ export async function updateImage(path: BigUint64Array, nodeId: bigint, mime: st await image.decode(); // eslint-disable-next-line @typescript-eslint/no-explicit-any - (window as any).editorInstance?.setImageBlobURL(documentId, path, nodeId, blobURL, image.naturalWidth, image.naturalHeight, transform); + // (window as any).editorInstance?.setImageBlobURL(documentId, path, nodeId, blobURL, image.naturalWidth, image.naturalHeight, transform); } -export async function fetchImage(path: BigUint64Array, nodeId: bigint, mime: string, documentId: bigint, url: string) { +export async function fetchImage(_path: BigUint64Array, _nodeId: bigint, _mime: string, _documentId: bigint, url: string) { const data = await fetch(url); const blob = await data.blob(); @@ -36,7 +36,7 @@ export async function fetchImage(path: BigUint64Array, nodeId: bigint, mime: str await image.decode(); // eslint-disable-next-line @typescript-eslint/no-explicit-any - (window as any).editorInstance?.setImageBlobURL(documentId, path, nodeId, blobURL, image.naturalWidth, image.naturalHeight, undefined); + // (window as any).editorInstance?.setImageBlobURL(documentId, path, nodeId, blobURL, image.naturalWidth, image.naturalHeight, undefined); } const tauri = "__TAURI_METADATA__" in window && import("@tauri-apps/api"); diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index 4e9dc3a1a6..c576cc71da 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -1,12 +1,13 @@ -//! This file is where functions are defined to be called directly from JS. -//! It serves as a thin wrapper over the editor backend API that relies -//! on the dispatcher messaging system and more complex Rust data types. - +#[allow(clippy::too_many_arguments)] +#[allow(clippy::non_snake_case)] +// This file is where functions are defined to be called directly from JS. +// It serves as a thin wrapper over the editor backend API that relies +// on the dispatcher messaging system and more complex Rust data types. use crate::helpers::translate_key; use crate::{Error, EDITOR_HAS_CRASHED, EDITOR_INSTANCES, JS_EDITOR_HANDLES}; +use document_legacy::document::LayerId; use document_legacy::document_metadata::LayerNodeIdentifier; -use document_legacy::LayerId; use editor::application::generate_uuid; use editor::application::Editor; use editor::consts::{FILE_SAVE_SUFFIX, GRAPHITE_DOCUMENT_VERSION}; @@ -103,7 +104,6 @@ async fn poll_node_graph_evaluation() { } #[wasm_bindgen] -#[allow(clippy::too_many_arguments)] impl JsEditorHandle { #[wasm_bindgen(constructor)] pub fn new(frontend_message_handler_callback: js_sys::Function) -> Self { @@ -575,26 +575,19 @@ impl JsEditorHandle { self.dispatch(message); } - /// Sends the blob URL generated by JS to the Image layer - #[wasm_bindgen(js_name = setImageBlobURL)] - pub fn set_image_blob_url(&self, document_id: u64, layer_path: Vec, node_id: Option, blob_url: String, width: f64, height: f64, transform: Option) { - let resolution = (width, height); - let message = PortfolioMessage::SetImageBlobUrl { - document_id, - layer_path: layer_path.clone(), - node_id, - blob_url, - resolution, - }; - self.dispatch(message); - - if let Some(array) = transform.filter(|array| array.length() == 6) { - let mut transform: [f64; 6] = [0.; 6]; - array.copy_to(&mut transform); - let message = document_legacy::Operation::SetLayerTransform { path: layer_path, transform }; - self.dispatch(message); - } - } + // /// Sends the blob URL generated by JS to the Image layer + // #[wasm_bindgen(js_name = setImageBlobURL)] + // pub fn set_image_blob_url(&self, document_id: u64, layer_path: Vec, node_id: Option, blob_url: String, width: f64, height: f64, _transform: Option) { + // let resolution = (width, height); + // let message = PortfolioMessage::SetImageBlobUrl { + // document_id, + // layer_path: layer_path.clone(), + // node_id, + // blob_url, + // resolution, + // }; + // self.dispatch(message); + // } /// Notifies the backend that the user connected a node's primary output to one of another node's inputs #[wasm_bindgen(js_name = connectNodesByLink)] diff --git a/node-graph/graphene-cli/src/main.rs b/node-graph/graphene-cli/src/main.rs index b1ead7463f..544ae69a03 100644 --- a/node-graph/graphene-cli/src/main.rs +++ b/node-graph/graphene-cli/src/main.rs @@ -89,7 +89,7 @@ fn create_executor(document_string: String) -> Result(document["document_legacy"].clone()).expect("Failed to parse document"); let Some(LegacyLayerType::Layer(ref node_graph)) = document.root.iter().find(|layer| matches!(layer.data, LegacyLayerType::Layer(_))).map(|x| &x.data) else { - panic!("failed to extract node graph from docmuent") + panic!("Failed to extract node graph from document") }; let network = &node_graph.network; let wrapped_network = wrap_network_in_scope(network.clone());