Skip to content

Commit 347e6ac

Browse files
authored
Switch vector data to the new pool storage throughout the editor and nodes (#4733)
* Swap Vector storage to the point pool and rebuild the vector-types crate on it * Port graphic types, rendering, and the outer node crates to pool storage * Migrate legacy document vector data onto pool storage * Bridge the editor onto pool storage with the VectorGeometry bundle * Port the editor tools to the vector geometry bundle * Show the point pool and paths directly in the Data panel * Port the vector nodes to pool storage and bring the workspace green * Cover the stored modification format with the demo artwork that leans on it
1 parent 46f48a5 commit 347e6ac

56 files changed

Lines changed: 4140 additions & 2804 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎Cargo.lock‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎editor/src/messages/clipboard/clipboard_message_handler.rs‎

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ use crate::messages::tool::utility_types::ToolType;
1212
use graph_craft::application_io::resource::{DataSource, ResourceHash};
1313
use graph_craft::document::NodeId;
1414
use graphene_std::Color;
15+
use graphene_std::list::List;
1516
use graphene_std::vector::misc::{BezierHandles, HandleId, point_to_dvec2, segment_to_handles};
16-
use graphene_std::vector::{PointId, SegmentId, VectorModificationType};
17+
use graphene_std::vector::{PointId, SegmentId, Vector, VectorModificationType};
1718
use graphite_proc_macros::{ExtractField, message_handler_data};
1819
use kurbo::ParamCurve;
1920
use std::sync::Arc;
@@ -434,18 +435,26 @@ impl MessageHandler<ClipboardMessage, ClipboardMessageContext<'_>> for Clipboard
434435
let stroke = graphene_std::vector::style::Stroke::new(DEFAULT_STROKE_WIDTH);
435436
responses.add(GraphOperationMessage::StrokeSet { layer, color, stroke });
436437

437-
// Create new point ids and add those into the existing Vector path
438+
// Materialize the copied snapshot so its points and segments can replay with fresh IDs
439+
let (mut pasted_vector, mut pasted_paths) = (Vector::default(), List::new());
440+
new_vector.apply(&mut pasted_vector, &mut pasted_paths);
441+
442+
// Create new point IDs and add those into the existing Vector path
438443
let mut points_map = HashMap::new();
439-
for (point, position) in new_vector.point_domain.iter() {
444+
for index in pasted_vector.anchor_indices(&pasted_paths) {
445+
let (Some(point), Some(position)) = (pasted_vector.point_id_at(index as u32), pasted_vector.position(index)) else {
446+
continue;
447+
};
448+
440449
let new_point_id = PointId::generate();
441450
points_map.insert(point, new_point_id);
442451
let modification_type = VectorModificationType::InsertPoint { id: new_point_id, position };
443452
responses.add(GraphOperationMessage::Vector { layer, modification_type });
444453
}
445454

446-
// Create new segment ids and add the segments into the existing Vector path
455+
// Create new segment IDs and add the segments into the existing Vector path
447456
let mut segments_map = HashMap::new();
448-
for (segment_id, segment, start, end) in new_vector.segment_iter() {
457+
for (segment_id, segment, start, end) in pasted_vector.segment_iter(&pasted_paths) {
449458
let (Some(&start_point), Some(&end_point)) = (points_map.get(&start), points_map.get(&end)) else {
450459
warn!("Skipping pasted vector segment with an unknown endpoint");
451460
continue;
@@ -468,7 +477,7 @@ impl MessageHandler<ClipboardMessage, ClipboardMessageContext<'_>> for Clipboard
468477
}
469478

470479
// Set G1 continuity
471-
for handles in new_vector.colinear_manipulators {
480+
for handles in pasted_vector.colinear_pairs(&pasted_paths) {
472481
let to_new_handle = |handle: HandleId| -> Option<HandleId> {
473482
Some(HandleId {
474483
ty: handle.ty,

‎editor/src/messages/clipboard/utility_types.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use base64::engine::general_purpose::STANDARD as BASE64;
55
use glam::DAffine2;
66
use graph_craft::application_io::resource::{DataSource, ResourceHash, ResourceId};
77
use graph_craft::document::NodeId;
8-
use graphene_std::vector::Vector;
8+
use graphene_std::vector::VectorModification;
99

1010
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
1111
pub enum ClipboardContentRaw {
@@ -20,7 +20,7 @@ pub enum ClipboardContent {
2020
Image { data: Vec<u8>, width: u32, height: u32 },
2121
}
2222

23-
pub type ClipboardVectorEntry = (LayerNodeIdentifier, Vector, DAffine2);
23+
pub type ClipboardVectorEntry = (LayerNodeIdentifier, VectorModification, DAffine2);
2424

2525
/// An entry in the `graphite:` clipboard payload.
2626
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]

‎editor/src/messages/portfolio/document/data_panel/data_panel_message.rs‎

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,6 @@ pub enum DataPanelMessage {
2020
TruncateElementPath {
2121
len: usize,
2222
},
23-
24-
ViewVectorTableTab {
25-
tab: VectorTableTab,
26-
},
2723
}
2824

2925
/// One hop in the breadcrumb path through nested data the Data panel is displaying.
@@ -33,11 +29,3 @@ pub enum PathStep {
3329
Element(usize),
3430
Attribute { row: usize, key: String },
3531
}
36-
37-
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize)]
38-
pub enum VectorTableTab {
39-
#[default]
40-
Points,
41-
Segments,
42-
Handles,
43-
}

‎editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs‎

Lines changed: 43 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use super::VectorTableTab;
21
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, LayoutTarget};
32
use crate::messages::portfolio::document::data_panel::{DataPanelMessage, PathStep};
43
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
@@ -25,14 +24,14 @@ use graphene_std::text_nodes::{StringCapitalization, TextDenomination};
2524
use graphene_std::transfer_curve::TransferCurve;
2625
use graphene_std::transform::{ReferencePoint, ScaleType};
2726
use graphene_std::vector::misc::{
28-
ArcType, BezierHandles, BooleanOperation, BoxCorners, CentroidType, ExtrudeJoiningAlgorithm, GridType, InterpolationDistribution, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns,
29-
SpiralType,
27+
ArcType, BooleanOperation, BoxCorners, CentroidType, ExtrudeJoiningAlgorithm, GridType, InterpolationDistribution, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns, SpiralType,
3028
};
3129
use graphene_std::vector::style::{
3230
DashPattern, FillChoice, GradientForm, GradientHueDirection, GradientInterpolation, GradientRamp, GradientSettings, GradientSpace, GradientSpread, StrokeAlign, StrokeCap, StrokeJoin,
3331
};
34-
use graphene_std::vector::{QRCodeErrorCorrectionLevel, Vector};
32+
use graphene_std::vector::{CurveType, Path, PointId, QRCodeErrorCorrectionLevel, SegmentId, Vector};
3533
use graphene_std::{Appearance, Artboard, Color, Context, Cover, Coverage, Graphic};
34+
use kurbo::BezPath;
3635
use std::any::Any;
3736
use std::sync::Arc;
3837

@@ -51,7 +50,6 @@ pub struct DataPanelMessageHandler {
5150
introspected_node_path: Vec<NodeId>,
5251
introspected_data: Option<Arc<dyn Any + Send + Sync>>,
5352
element_path: Vec<PathStep>,
54-
active_vector_table_tab: VectorTableTab,
5553
}
5654

5755
#[message_handler_data]
@@ -67,7 +65,6 @@ impl MessageHandler<DataPanelMessage, DataPanelMessageContext<'_>> for DataPanel
6765
self.introspected_node_path.clear();
6866
self.introspected_data = None;
6967
self.element_path.clear();
70-
self.active_vector_table_tab = VectorTableTab::default();
7168
self.update_layout(responses, context);
7269
}
7370
DataPanelMessage::Refresh => {
@@ -85,11 +82,6 @@ impl MessageHandler<DataPanelMessage, DataPanelMessageContext<'_>> for DataPanel
8582
self.element_path.truncate(len);
8683
self.update_layout(responses, context);
8784
}
88-
89-
DataPanelMessage::ViewVectorTableTab { tab } => {
90-
self.active_vector_table_tab = tab;
91-
self.update_layout(responses, context);
92-
}
9385
}
9486
}
9587

@@ -114,7 +106,6 @@ impl DataPanelMessageHandler {
114106
node_lookup_network_path: Vec::new(),
115107
gradient_settings: GradientSettings::default(),
116108
breadcrumbs: Vec::new(),
117-
vector_table_tab: self.active_vector_table_tab,
118109
};
119110

120111
// Main data visualization
@@ -172,7 +163,6 @@ struct LayoutData<'a> {
172163
/// rather than in the stop list itself. The enclosing `Item`/`List` sets it to the owning row's attributes.
173164
gradient_settings: GradientSettings,
174165
breadcrumbs: Vec<String>,
175-
vector_table_tab: VectorTableTab,
176166
}
177167

178168
macro_rules! generate_layout_downcast {
@@ -655,70 +645,42 @@ impl TableItemLayout for Graphic {
655645
}
656646
}
657647

648+
// The pool is a plain point list with its IDs riding its attributes, so the generic list layout shows it as-is
658649
impl TableItemLayout for Vector {
659650
fn type_name() -> &'static str {
660651
"Vector"
661652
}
662653
fn identifier(&self) -> String {
663-
format!(
664-
"Vector ({} point{}, {} segment{})",
665-
self.point_domain.ids().len(),
666-
if self.point_domain.ids().len() == 1 { "" } else { "s" },
667-
self.segment_domain.ids().len(),
668-
if self.segment_domain.ids().len() == 1 { "" } else { "s" }
669-
)
654+
self.0.identifier()
670655
}
671656
fn value_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
672-
let table_tab_entries = [VectorTableTab::Points, VectorTableTab::Segments, VectorTableTab::Handles]
673-
.into_iter()
674-
.map(|tab| {
675-
RadioEntryData::new(format!("{tab:?}"))
676-
.label(format!("{tab:?}"))
677-
.on_update(move |_| DataPanelMessage::ViewVectorTableTab { tab }.into())
678-
})
679-
.collect();
680-
let table_tabs = vec![RadioInput::new(table_tab_entries).selected_index(Some(data.vector_table_tab as u32)).widget_instance()];
681-
682-
let mut table_rows = Vec::new();
683-
match data.vector_table_tab {
684-
VectorTableTab::Handles => {
685-
table_rows.push(column_headings(&["", "colinear_manipulators[0]", "colinear_manipulators[1]"]));
686-
table_rows.extend(self.colinear_manipulators.iter().enumerate().map(|(index, [a, b])| {
687-
vec![
688-
TextLabel::new(format!("{index}")).narrow(true).widget_instance(),
689-
TextLabel::new(format!("{a}")).narrow(true).widget_instance(),
690-
TextLabel::new(format!("{b}")).narrow(true).widget_instance(),
691-
]
692-
}));
693-
}
694-
VectorTableTab::Points => {
695-
table_rows.push(column_headings(&["", "position"]));
696-
table_rows.extend(self.point_domain.iter().map(|(id, position)| {
697-
vec![
698-
TextLabel::new(format!("{}", id.inner())).narrow(true).widget_instance(),
699-
TextLabel::new(format_dvec2(position)).narrow(true).widget_instance(),
700-
]
701-
}));
702-
}
703-
VectorTableTab::Segments => {
704-
table_rows.push(column_headings(&["", "start_point", "end_point", "handles"]));
705-
table_rows.extend(self.segment_domain.iter().map(|(id, start, end, handles)| {
706-
let handles = match handles {
707-
BezierHandles::Linear => "Linear".to_string(),
708-
BezierHandles::Quadratic { handle } => format!("Quadratic — {}", format_dvec2(handle)),
709-
BezierHandles::Cubic { handle_start, handle_end } => format!("Cubic — start: {}, end: {}", format_dvec2(handle_start), format_dvec2(handle_end)),
710-
};
711-
vec![
712-
TextLabel::new(format!("{}", id.inner())).narrow(true).widget_instance(),
713-
TextLabel::new(format!("Point {start}")).narrow(true).widget_instance(),
714-
TextLabel::new(format!("Point {end}")).narrow(true).widget_instance(),
715-
TextLabel::new(handles).narrow(true).widget_instance(),
716-
]
717-
}));
718-
}
719-
}
657+
self.0.value_page(data)
658+
}
659+
}
720660

721-
vec![LayoutGroup::row(table_tabs), LayoutGroup::table(table_rows, false)]
661+
// A path is its list of point references, with segment IDs and smooth flags riding its attributes
662+
impl TableItemLayout for Path {
663+
fn type_name() -> &'static str {
664+
"Path"
665+
}
666+
fn identifier(&self) -> String {
667+
self.0.identifier()
668+
}
669+
fn value_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
670+
self.0.value_page(data)
671+
}
672+
}
673+
674+
// Only the click target attribute holds a bezpath, so it's named for what it does
675+
impl TableItemLayout for BezPath {
676+
fn type_name() -> &'static str {
677+
"Click Target"
678+
}
679+
fn identifier(&self) -> String {
680+
Self::type_name().to_string()
681+
}
682+
fn value_widgets(&self, _target: PathStep, _data: &LayoutData) -> Vec<WidgetInstance> {
683+
vec![TextLabel::new(self.identifier()).narrow(true).widget_instance()]
722684
}
723685
}
724686

@@ -1151,6 +1113,7 @@ macro_rules! known_item_types {
11511113
List<Artboard>,
11521114
List<Graphic>,
11531115
List<Vector>,
1116+
List<Path>,
11541117
List<Raster<CPU>>,
11551118
List<Raster<GPU>>,
11561119
List<Color>,
@@ -1170,6 +1133,8 @@ macro_rules! known_item_types {
11701133
bool,
11711134
String,
11721135
Vector,
1136+
Path,
1137+
BezPath,
11731138
Raster<CPU>,
11741139
Raster<GPU>,
11751140
Graphic,
@@ -1230,6 +1195,15 @@ fn display_value_override(any: &dyn Any) -> Option<String> {
12301195
if let Some(value) = any.downcast_ref::<BlendMode>() {
12311196
return Some(value.to_string());
12321197
}
1198+
if let Some(value) = any.downcast_ref::<PointId>() {
1199+
return Some(if *value == PointId::UNNAMED { String::new() } else { value.inner().to_string() });
1200+
}
1201+
if let Some(value) = any.downcast_ref::<SegmentId>() {
1202+
return Some(if *value == SegmentId::ZERO { String::new() } else { value.inner().to_string() });
1203+
}
1204+
if let Some(value) = any.downcast_ref::<CurveType>() {
1205+
return Some(value.to_string());
1206+
}
12331207
None
12341208
}
12351209

‎editor/src/messages/portfolio/document/document_message.rs‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use crate::messages::prelude::*;
1313
use glam::{DAffine2, IVec2, UVec2};
1414
use graph_craft::document::NodeId;
1515
use graphene_std::Appearance;
16+
use graphene_std::list::Item;
1617
use graphene_std::raster::BlendMode;
1718
use graphene_std::transform::Footprint;
1819
use graphene_std::vector::Vector;
@@ -295,11 +296,12 @@ pub enum DocumentMessage {
295296
UpdateClipTargets {
296297
clip_targets: HashSet<NodeId>,
297298
},
299+
// `Message` is only serialized at `editor_wrapper.rs`, and only inputs from JS pass through it.
300+
// `UpdateVectorData` and `UpdateAppearanceAttributes` are produced inside `editor.handle_message` by `node_graph_executor.rs` and consumed in the same dispatch loop, so they never reach that serialization point.
301+
#[serde(skip)]
298302
UpdateVectorData {
299-
vector_data: HashMap<NodeId, Arc<Vector>>,
303+
vector_data: HashMap<NodeId, Arc<Item<Vector>>>,
300304
},
301-
// `Message` is only serialized at `editor_wrapper.rs`, and only inputs from JS pass through it.
302-
// `UpdateAppearanceAttributes` is produced inside `editor.handle_message` by `node_graph_executor.rs` and consumed in the same dispatch loop, so it never reaches that serialization point.
303305
#[serde(skip)]
304306
UpdateAppearanceAttributes {
305307
appearance_attributes: HashMap<NodeId, Arc<Appearance>>,

‎editor/src/messages/portfolio/document/graph_operation/utility_types.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,8 @@ impl<'a> ModifyInputsContext<'a> {
147147

148148
pub fn insert_vector(&mut self, bezpath: BezPath, layer: LayerNodeIdentifier, include_transform: bool, include_fill: bool, include_stroke: bool) {
149149
// Build a VectorModification that reproduces the geometry (same format the Pen tool uses)
150-
let vector = Vector::from_bezpath(bezpath);
151-
let modification = Box::new(VectorModification::create_from_vector(&vector));
150+
let (vector, paths) = Vector::from_bezpath(&bezpath);
151+
let modification = Box::new(VectorModification::create_from_vector(&vector, &paths));
152152

153153
let shape = resolve_network_node_type("Path")
154154
.expect("Path node does not exist")

‎editor/src/messages/portfolio/document/overlays/utility_functions.rs‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::messages::tool::common_functionality::utility_functions::closest_open
88
use crate::messages::tool::tool_messages::tool_prelude::DocumentMessageHandler;
99
use glam::{DAffine2, DVec2};
1010
use graphene_std::vector::misc::{BezierHandles, ManipulatorPointId, point_to_dvec2, segment_to_handles};
11-
use graphene_std::vector::{PointId, SegmentId, Vector};
11+
use graphene_std::vector::{PointId, SegmentId, VectorGeometry};
1212
use kurbo::{Affine, ParamCurve, PathSeg};
1313
use std::collections::HashMap;
1414
#[cfg(target_family = "wasm")]
@@ -44,7 +44,7 @@ pub fn selected_segments(network_interface: &NodeNetworkInterface, shape_editor:
4444
map
4545
}
4646

47-
pub fn selected_segments_for_layer(vector: &Vector, state: &SelectedLayerState) -> Vec<SegmentId> {
47+
pub fn selected_segments_for_layer(vector: &VectorGeometry, state: &SelectedLayerState) -> Vec<SegmentId> {
4848
let selected_anchors = state
4949
.selected_points()
5050
.filter_map(|point| if let ManipulatorPointId::Anchor(p) = point { Some(p) } else { None })
@@ -195,7 +195,7 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle
195195
}
196196

197197
if display_anchors {
198-
for (&id, &position) in vector.point_domain.ids().iter().zip(vector.point_domain.positions()) {
198+
for (id, position) in vector.anchors() {
199199
overlay_context.manipulator_anchor(transform.transform_point2(position), is_selected(ManipulatorPointId::Anchor(id)), None);
200200
}
201201
}
@@ -226,7 +226,7 @@ pub fn open_path_endpoint_overlays(document: &DocumentMessageHandler, shape_edit
226226
if hovered.is_some_and(|(hovered_layer, hovered_id, _)| hovered_layer == layer && hovered_id == id) {
227227
continue;
228228
}
229-
let Some(position) = vector.point_domain.position_from_id(id) else { continue };
229+
let Some(position) = vector.position_from_id(id) else { continue };
230230

231231
overlay_context.manipulator_anchor(transform.transform_point2(position), is_selected(layer, id), None);
232232
}

0 commit comments

Comments
 (0)