Skip to content
Merged
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 15 additions & 6 deletions editor/src/messages/clipboard/clipboard_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ use crate::messages::tool::utility_types::ToolType;
use graph_craft::application_io::resource::{DataSource, ResourceHash};
use graph_craft::document::NodeId;
use graphene_std::Color;
use graphene_std::list::List;
use graphene_std::vector::misc::{BezierHandles, HandleId, point_to_dvec2, segment_to_handles};
use graphene_std::vector::{PointId, SegmentId, VectorModificationType};
use graphene_std::vector::{PointId, SegmentId, Vector, VectorModificationType};
use graphite_proc_macros::{ExtractField, message_handler_data};
use kurbo::ParamCurve;
use std::sync::Arc;
Expand Down Expand Up @@ -434,18 +435,26 @@ impl MessageHandler<ClipboardMessage, ClipboardMessageContext<'_>> for Clipboard
let stroke = graphene_std::vector::style::Stroke::new(DEFAULT_STROKE_WIDTH);
responses.add(GraphOperationMessage::StrokeSet { layer, color, stroke });

// Create new point ids and add those into the existing Vector path
// Materialize the copied snapshot so its points and segments can replay with fresh IDs
let (mut pasted_vector, mut pasted_paths) = (Vector::default(), List::new());
new_vector.apply(&mut pasted_vector, &mut pasted_paths);

// Create new point IDs and add those into the existing Vector path
let mut points_map = HashMap::new();
for (point, position) in new_vector.point_domain.iter() {
for index in pasted_vector.anchor_indices(&pasted_paths) {
let (Some(point), Some(position)) = (pasted_vector.point_id_at(index as u32), pasted_vector.position(index)) else {
continue;
};

let new_point_id = PointId::generate();
points_map.insert(point, new_point_id);
let modification_type = VectorModificationType::InsertPoint { id: new_point_id, position };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}

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

// Set G1 continuity
for handles in new_vector.colinear_manipulators {
for handles in pasted_vector.colinear_pairs(&pasted_paths) {
let to_new_handle = |handle: HandleId| -> Option<HandleId> {
Some(HandleId {
ty: handle.ty,
Expand Down
4 changes: 2 additions & 2 deletions editor/src/messages/clipboard/utility_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use base64::engine::general_purpose::STANDARD as BASE64;
use glam::DAffine2;
use graph_craft::application_io::resource::{DataSource, ResourceHash, ResourceId};
use graph_craft::document::NodeId;
use graphene_std::vector::Vector;
use graphene_std::vector::VectorModification;

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

pub type ClipboardVectorEntry = (LayerNodeIdentifier, Vector, DAffine2);
pub type ClipboardVectorEntry = (LayerNodeIdentifier, VectorModification, DAffine2);
Comment thread
Keavon marked this conversation as resolved.

/// An entry in the `graphite:` clipboard payload.
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ pub enum DataPanelMessage {
TruncateElementPath {
len: usize,
},

ViewVectorTableTab {
tab: VectorTableTab,
},
}

/// One hop in the breadcrumb path through nested data the Data panel is displaying.
Expand All @@ -33,11 +29,3 @@ pub enum PathStep {
Element(usize),
Attribute { row: usize, key: String },
}

#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize)]
pub enum VectorTableTab {
#[default]
Points,
Segments,
Handles,
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use super::VectorTableTab;
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, LayoutTarget};
use crate::messages::portfolio::document::data_panel::{DataPanelMessage, PathStep};
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
Expand All @@ -25,14 +24,14 @@ use graphene_std::text_nodes::{StringCapitalization, TextDenomination};
use graphene_std::transfer_curve::TransferCurve;
use graphene_std::transform::{ReferencePoint, ScaleType};
use graphene_std::vector::misc::{
ArcType, BezierHandles, BooleanOperation, BoxCorners, CentroidType, ExtrudeJoiningAlgorithm, GridType, InterpolationDistribution, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns,
SpiralType,
ArcType, BooleanOperation, BoxCorners, CentroidType, ExtrudeJoiningAlgorithm, GridType, InterpolationDistribution, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns, SpiralType,
};
use graphene_std::vector::style::{
DashPattern, FillChoice, GradientForm, GradientHueDirection, GradientInterpolation, GradientRamp, GradientSettings, GradientSpace, GradientSpread, StrokeAlign, StrokeCap, StrokeJoin,
};
use graphene_std::vector::{QRCodeErrorCorrectionLevel, Vector};
use graphene_std::vector::{CurveType, Path, PointId, QRCodeErrorCorrectionLevel, SegmentId, Vector};
use graphene_std::{Appearance, Artboard, Color, Context, Cover, Coverage, Graphic};
use kurbo::BezPath;
use std::any::Any;
use std::sync::Arc;

Expand All @@ -51,7 +50,6 @@ pub struct DataPanelMessageHandler {
introspected_node_path: Vec<NodeId>,
introspected_data: Option<Arc<dyn Any + Send + Sync>>,
element_path: Vec<PathStep>,
active_vector_table_tab: VectorTableTab,
}

#[message_handler_data]
Expand All @@ -67,7 +65,6 @@ impl MessageHandler<DataPanelMessage, DataPanelMessageContext<'_>> for DataPanel
self.introspected_node_path.clear();
self.introspected_data = None;
self.element_path.clear();
self.active_vector_table_tab = VectorTableTab::default();
self.update_layout(responses, context);
}
DataPanelMessage::Refresh => {
Expand All @@ -85,11 +82,6 @@ impl MessageHandler<DataPanelMessage, DataPanelMessageContext<'_>> for DataPanel
self.element_path.truncate(len);
self.update_layout(responses, context);
}

DataPanelMessage::ViewVectorTableTab { tab } => {
self.active_vector_table_tab = tab;
self.update_layout(responses, context);
}
}
}

Expand All @@ -114,7 +106,6 @@ impl DataPanelMessageHandler {
node_lookup_network_path: Vec::new(),
gradient_settings: GradientSettings::default(),
breadcrumbs: Vec::new(),
vector_table_tab: self.active_vector_table_tab,
};

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

macro_rules! generate_layout_downcast {
Expand Down Expand Up @@ -655,70 +645,42 @@ impl TableItemLayout for Graphic {
}
}

// The pool is a plain point list with its IDs riding its attributes, so the generic list layout shows it as-is
impl TableItemLayout for Vector {
fn type_name() -> &'static str {
"Vector"
}
fn identifier(&self) -> String {
format!(
"Vector ({} point{}, {} segment{})",
self.point_domain.ids().len(),
if self.point_domain.ids().len() == 1 { "" } else { "s" },
self.segment_domain.ids().len(),
if self.segment_domain.ids().len() == 1 { "" } else { "s" }
)
self.0.identifier()
Comment thread
Keavon marked this conversation as resolved.
}
fn value_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
let table_tab_entries = [VectorTableTab::Points, VectorTableTab::Segments, VectorTableTab::Handles]
.into_iter()
.map(|tab| {
RadioEntryData::new(format!("{tab:?}"))
.label(format!("{tab:?}"))
.on_update(move |_| DataPanelMessage::ViewVectorTableTab { tab }.into())
})
.collect();
let table_tabs = vec![RadioInput::new(table_tab_entries).selected_index(Some(data.vector_table_tab as u32)).widget_instance()];

let mut table_rows = Vec::new();
match data.vector_table_tab {
VectorTableTab::Handles => {
table_rows.push(column_headings(&["", "colinear_manipulators[0]", "colinear_manipulators[1]"]));
table_rows.extend(self.colinear_manipulators.iter().enumerate().map(|(index, [a, b])| {
vec![
TextLabel::new(format!("{index}")).narrow(true).widget_instance(),
TextLabel::new(format!("{a}")).narrow(true).widget_instance(),
TextLabel::new(format!("{b}")).narrow(true).widget_instance(),
]
}));
}
VectorTableTab::Points => {
table_rows.push(column_headings(&["", "position"]));
table_rows.extend(self.point_domain.iter().map(|(id, position)| {
vec![
TextLabel::new(format!("{}", id.inner())).narrow(true).widget_instance(),
TextLabel::new(format_dvec2(position)).narrow(true).widget_instance(),
]
}));
}
VectorTableTab::Segments => {
table_rows.push(column_headings(&["", "start_point", "end_point", "handles"]));
table_rows.extend(self.segment_domain.iter().map(|(id, start, end, handles)| {
let handles = match handles {
BezierHandles::Linear => "Linear".to_string(),
BezierHandles::Quadratic { handle } => format!("Quadratic — {}", format_dvec2(handle)),
BezierHandles::Cubic { handle_start, handle_end } => format!("Cubic — start: {}, end: {}", format_dvec2(handle_start), format_dvec2(handle_end)),
};
vec![
TextLabel::new(format!("{}", id.inner())).narrow(true).widget_instance(),
TextLabel::new(format!("Point {start}")).narrow(true).widget_instance(),
TextLabel::new(format!("Point {end}")).narrow(true).widget_instance(),
TextLabel::new(handles).narrow(true).widget_instance(),
]
}));
}
}
self.0.value_page(data)
}
}

vec![LayoutGroup::row(table_tabs), LayoutGroup::table(table_rows, false)]
// A path is its list of point references, with segment IDs and smooth flags riding its attributes
impl TableItemLayout for Path {
fn type_name() -> &'static str {
"Path"
}
fn identifier(&self) -> String {
self.0.identifier()
}
fn value_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
self.0.value_page(data)
}
}

// Only the click target attribute holds a bezpath, so it's named for what it does
impl TableItemLayout for BezPath {
fn type_name() -> &'static str {
"Click Target"
}
fn identifier(&self) -> String {
Self::type_name().to_string()
}
fn value_widgets(&self, _target: PathStep, _data: &LayoutData) -> Vec<WidgetInstance> {
vec![TextLabel::new(self.identifier()).narrow(true).widget_instance()]
}
}

Expand Down Expand Up @@ -1151,6 +1113,7 @@ macro_rules! known_item_types {
List<Artboard>,
List<Graphic>,
List<Vector>,
List<Path>,
List<Raster<CPU>>,
List<Raster<GPU>>,
List<Color>,
Expand All @@ -1170,6 +1133,8 @@ macro_rules! known_item_types {
bool,
String,
Vector,
Path,
BezPath,
Raster<CPU>,
Raster<GPU>,
Graphic,
Expand Down Expand Up @@ -1230,6 +1195,15 @@ fn display_value_override(any: &dyn Any) -> Option<String> {
if let Some(value) = any.downcast_ref::<BlendMode>() {
return Some(value.to_string());
}
if let Some(value) = any.downcast_ref::<PointId>() {
return Some(if *value == PointId::UNNAMED { String::new() } else { value.inner().to_string() });
}
if let Some(value) = any.downcast_ref::<SegmentId>() {
return Some(if *value == SegmentId::ZERO { String::new() } else { value.inner().to_string() });
}
if let Some(value) = any.downcast_ref::<CurveType>() {
return Some(value.to_string());
}
None
}

Expand Down
8 changes: 5 additions & 3 deletions editor/src/messages/portfolio/document/document_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::messages::prelude::*;
use glam::{DAffine2, IVec2, UVec2};
use graph_craft::document::NodeId;
use graphene_std::Appearance;
use graphene_std::list::Item;
use graphene_std::raster::BlendMode;
use graphene_std::transform::Footprint;
use graphene_std::vector::Vector;
Expand Down Expand Up @@ -295,11 +296,12 @@ pub enum DocumentMessage {
UpdateClipTargets {
clip_targets: HashSet<NodeId>,
},
// `Message` is only serialized at `editor_wrapper.rs`, and only inputs from JS pass through it.
// `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.
#[serde(skip)]
UpdateVectorData {
vector_data: HashMap<NodeId, Arc<Vector>>,
vector_data: HashMap<NodeId, Arc<Item<Vector>>>,
},
// `Message` is only serialized at `editor_wrapper.rs`, and only inputs from JS pass through it.
// `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.
#[serde(skip)]
UpdateAppearanceAttributes {
appearance_attributes: HashMap<NodeId, Arc<Appearance>>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,8 @@ impl<'a> ModifyInputsContext<'a> {

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

let shape = resolve_network_node_type("Path")
.expect("Path node does not exist")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::messages::tool::common_functionality::utility_functions::closest_open
use crate::messages::tool::tool_messages::tool_prelude::DocumentMessageHandler;
use glam::{DAffine2, DVec2};
use graphene_std::vector::misc::{BezierHandles, ManipulatorPointId, point_to_dvec2, segment_to_handles};
use graphene_std::vector::{PointId, SegmentId, Vector};
use graphene_std::vector::{PointId, SegmentId, VectorGeometry};
use kurbo::{Affine, ParamCurve, PathSeg};
use std::collections::HashMap;
#[cfg(target_family = "wasm")]
Expand Down Expand Up @@ -44,7 +44,7 @@ pub fn selected_segments(network_interface: &NodeNetworkInterface, shape_editor:
map
}

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

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

overlay_context.manipulator_anchor(transform.transform_point2(position), is_selected(layer, id), None);
}
Expand Down
Loading
Loading