Merge branch 'master' into ir/ref-centroid

This commit is contained in:
indierusty
2025-08-04 11:50:47 +05:30
102 changed files with 2803 additions and 2580 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -153,7 +153,7 @@ pub const COLOR_OVERLAY_BLACK_75: &str = "#000000bf";
pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document"; pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document";
pub const FILE_SAVE_SUFFIX: &str = ".graphite"; pub const FILE_SAVE_SUFFIX: &str = ".graphite";
pub const MAX_UNDO_HISTORY_LEN: usize = 100; // TODO: Add this to user preferences pub const MAX_UNDO_HISTORY_LEN: usize = 100; // TODO: Add this to user preferences
pub const AUTO_SAVE_TIMEOUT_SECONDS: u64 = 15; pub const AUTO_SAVE_TIMEOUT_SECONDS: u64 = 1;
// INPUT // INPUT
pub const DOUBLE_CLICK_MILLISECONDS: u64 = 500; pub const DOUBLE_CLICK_MILLISECONDS: u64 = 500;
@@ -145,14 +145,14 @@ impl LayoutHolder for ExportDialogMessageHandler {
DropdownInput::new(entries).selected_index(Some(index as u32)).widget_holder(), DropdownInput::new(entries).selected_index(Some(index as u32)).widget_holder(),
]; ];
let mut checkbox_id = CheckboxId::default(); let checkbox_id = CheckboxId::new();
let transparent_background = vec![ let transparent_background = vec![
TextLabel::new("Transparency").table_align(true).min_width(100).for_checkbox(&mut checkbox_id).widget_holder(), TextLabel::new("Transparency").table_align(true).min_width(100).for_checkbox(checkbox_id).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder(),
CheckboxInput::new(self.transparent_background) CheckboxInput::new(self.transparent_background)
.disabled(self.file_type == FileType::Jpg) .disabled(self.file_type == FileType::Jpg)
.on_update(move |value: &CheckboxInput| ExportDialogMessage::TransparentBackground(value.checked).into()) .on_update(move |value: &CheckboxInput| ExportDialogMessage::TransparentBackground(value.checked).into())
.for_label(checkbox_id.clone()) .for_label(checkbox_id)
.widget_holder(), .widget_holder(),
]; ];
@@ -80,13 +80,13 @@ impl LayoutHolder for NewDocumentDialogMessageHandler {
.widget_holder(), .widget_holder(),
]; ];
let mut checkbox_id = CheckboxId::default(); let checkbox_id = CheckboxId::new();
let infinite = vec![ let infinite = vec![
TextLabel::new("Infinite Canvas").table_align(true).min_width(90).for_checkbox(&mut checkbox_id).widget_holder(), TextLabel::new("Infinite Canvas").table_align(true).min_width(90).for_checkbox(checkbox_id).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder(),
CheckboxInput::new(self.infinite) CheckboxInput::new(self.infinite)
.on_update(|checkbox_input: &CheckboxInput| NewDocumentDialogMessage::Infinite(checkbox_input.checked).into()) .on_update(|checkbox_input: &CheckboxInput| NewDocumentDialogMessage::Infinite(checkbox_input.checked).into())
.for_label(checkbox_id.clone()) .for_label(checkbox_id)
.widget_holder(), .widget_holder(),
]; ];
@@ -68,7 +68,7 @@ impl PreferencesDialogMessageHandler {
.widget_holder(), .widget_holder(),
]; ];
let mut checkbox_id = CheckboxId::default(); let checkbox_id = CheckboxId::new();
let zoom_with_scroll_tooltip = "Use the scroll wheel for zooming instead of vertically panning (not recommended for trackpads)"; let zoom_with_scroll_tooltip = "Use the scroll wheel for zooming instead of vertically panning (not recommended for trackpads)";
let zoom_with_scroll = vec![ let zoom_with_scroll = vec![
Separator::new(SeparatorType::Unrelated).widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder(),
@@ -81,12 +81,12 @@ impl PreferencesDialogMessageHandler {
} }
.into() .into()
}) })
.for_label(checkbox_id.clone()) .for_label(checkbox_id)
.widget_holder(), .widget_holder(),
TextLabel::new("Zoom with Scroll") TextLabel::new("Zoom with Scroll")
.table_align(true) .table_align(true)
.tooltip(zoom_with_scroll_tooltip) .tooltip(zoom_with_scroll_tooltip)
.for_checkbox(&mut checkbox_id) .for_checkbox(checkbox_id)
.widget_holder(), .widget_holder(),
]; ];
@@ -169,7 +169,7 @@ impl PreferencesDialogMessageHandler {
graph_wire_style, graph_wire_style,
]; ];
let mut checkbox_id = CheckboxId::default(); let checkbox_id = CheckboxId::new();
let vello_tooltip = "Use the experimental Vello renderer (your browser must support WebGPU)"; let vello_tooltip = "Use the experimental Vello renderer (your browser must support WebGPU)";
let use_vello = vec![ let use_vello = vec![
Separator::new(SeparatorType::Unrelated).widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder(),
@@ -178,17 +178,17 @@ impl PreferencesDialogMessageHandler {
.tooltip(vello_tooltip) .tooltip(vello_tooltip)
.disabled(!preferences.supports_wgpu()) .disabled(!preferences.supports_wgpu())
.on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::UseVello { use_vello: checkbox_input.checked }.into()) .on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::UseVello { use_vello: checkbox_input.checked }.into())
.for_label(checkbox_id.clone()) .for_label(checkbox_id)
.widget_holder(), .widget_holder(),
TextLabel::new("Vello Renderer") TextLabel::new("Vello Renderer")
.table_align(true) .table_align(true)
.tooltip(vello_tooltip) .tooltip(vello_tooltip)
.disabled(!preferences.supports_wgpu()) .disabled(!preferences.supports_wgpu())
.for_checkbox(&mut checkbox_id) .for_checkbox(checkbox_id)
.widget_holder(), .widget_holder(),
]; ];
let mut checkbox_id = CheckboxId::default(); let checkbox_id = CheckboxId::new();
let vector_mesh_tooltip = let vector_mesh_tooltip =
"Allow tools to produce vector meshes, where more than two segments can connect to an anchor point.\n\nCurrently this does not properly handle stroke joins and fills."; "Allow tools to produce vector meshes, where more than two segments can connect to an anchor point.\n\nCurrently this does not properly handle stroke joins and fills.";
let vector_meshes = vec![ let vector_meshes = vec![
@@ -197,13 +197,9 @@ impl PreferencesDialogMessageHandler {
CheckboxInput::new(preferences.vector_meshes) CheckboxInput::new(preferences.vector_meshes)
.tooltip(vector_mesh_tooltip) .tooltip(vector_mesh_tooltip)
.on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::VectorMeshes { enabled: checkbox_input.checked }.into()) .on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::VectorMeshes { enabled: checkbox_input.checked }.into())
.for_label(checkbox_id.clone()) .for_label(checkbox_id)
.widget_holder(),
TextLabel::new("Vector Meshes")
.table_align(true)
.tooltip(vector_mesh_tooltip)
.for_checkbox(&mut checkbox_id)
.widget_holder(), .widget_holder(),
TextLabel::new("Vector Meshes").table_align(true).tooltip(vector_mesh_tooltip).for_checkbox(checkbox_id).widget_holder(),
]; ];
Layout::WidgetLayout(WidgetLayout::new(vec![ Layout::WidgetLayout(WidgetLayout::new(vec![
@@ -13,7 +13,7 @@ use graphene_std::raster::Image;
use graphene_std::raster::color::Color; use graphene_std::raster::color::Color;
use graphene_std::text::{Font, TextAlign}; use graphene_std::text::{Font, TextAlign};
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
#[impl_message(Message, Frontend)] #[impl_message(Message, Frontend)]
@@ -322,7 +322,7 @@ pub enum FrontendMessage {
UpdateViewportHolePunch { UpdateViewportHolePunch {
active: bool, active: bool,
}, },
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
RenderOverlays( RenderOverlays(
#[serde(skip, default = "OverlayContext::default")] #[serde(skip, default = "OverlayContext::default")]
#[derivative(Debug = "ignore", PartialEq = "ignore")] #[derivative(Debug = "ignore", PartialEq = "ignore")]
@@ -211,6 +211,9 @@ pub fn input_mappings() -> Mapping {
entry!(KeyDown(Backspace); modifiers=[Accel], action_dispatch=PathToolMessage::DeleteAndBreakPath), entry!(KeyDown(Backspace); modifiers=[Accel], action_dispatch=PathToolMessage::DeleteAndBreakPath),
entry!(KeyDown(Delete); modifiers=[Shift], action_dispatch=PathToolMessage::BreakPath), entry!(KeyDown(Delete); modifiers=[Shift], action_dispatch=PathToolMessage::BreakPath),
entry!(KeyDown(Backspace); modifiers=[Shift], action_dispatch=PathToolMessage::BreakPath), entry!(KeyDown(Backspace); modifiers=[Shift], action_dispatch=PathToolMessage::BreakPath),
entry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=PathToolMessage::Cut { clipboard: Clipboard::Device }),
entry!(KeyDown(KeyC); modifiers=[Accel], action_dispatch=PathToolMessage::Copy { clipboard: Clipboard::Device }),
entry!(KeyDown(KeyD); modifiers=[Accel], action_dispatch=PathToolMessage::Duplicate),
entry!(KeyDownNoRepeat(Tab); action_dispatch=PathToolMessage::SwapSelectedHandles), entry!(KeyDownNoRepeat(Tab); action_dispatch=PathToolMessage::SwapSelectedHandles),
entry!(KeyDown(MouseLeft); action_dispatch=PathToolMessage::MouseDown { extend_selection: Shift, lasso_select: Control, handle_drag_from_anchor: Alt, drag_restore_handle: Control, segment_editing_modifier: Control }), entry!(KeyDown(MouseLeft); action_dispatch=PathToolMessage::MouseDown { extend_selection: Shift, lasso_select: Control, handle_drag_from_anchor: Alt, drag_restore_handle: Control, segment_editing_modifier: Control }),
entry!(KeyDown(MouseRight); action_dispatch=PathToolMessage::RightClick), entry!(KeyDown(MouseRight); action_dispatch=PathToolMessage::RightClick),
@@ -35,6 +35,15 @@ impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageContext> f
responses.add(NavigationMessage::CanvasPan { delta: DVec2::ZERO }); responses.add(NavigationMessage::CanvasPan { delta: DVec2::ZERO });
responses.add(NodeGraphMessage::SetGridAlignedEdges); responses.add(NodeGraphMessage::SetGridAlignedEdges);
// We have to wait until a node node graph has run and all messages from that execution have been processed.
responses.add(DeferMessage::AfterGraphRun {
messages: vec![
DeferMessage::AfterGraphRun {
messages: vec![DeferMessage::TriggerNavigationReady.into()],
}
.into(),
],
});
} }
} }
InputPreprocessorMessage::DoubleClick { editor_mouse_state, modifier_keys } => { InputPreprocessorMessage::DoubleClick { editor_mouse_state, modifier_keys } => {
@@ -5,8 +5,6 @@ use graphene_std::Color;
use graphene_std::raster::curve::Curve; use graphene_std::raster::curve::Curve;
use graphene_std::transform::ReferencePoint; use graphene_std::transform::ReferencePoint;
use graphite_proc_macros::WidgetBuilder; use graphite_proc_macros::WidgetBuilder;
use once_cell::sync::OnceCell;
use std::sync::Arc;
#[derive(Clone, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder, specta::Type)] #[derive(Clone, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq)] #[derivative(Debug, PartialEq)]
@@ -20,7 +18,7 @@ pub struct CheckboxInput {
pub tooltip: String, pub tooltip: String,
#[serde(rename = "forLabel", skip_serializing_if = "checkbox_id_is_empty")] #[serde(rename = "forLabel")]
pub for_label: CheckboxId, pub for_label: CheckboxId,
#[serde(skip)] #[serde(skip)]
@@ -44,19 +42,24 @@ impl Default for CheckboxInput {
icon: "Checkmark".into(), icon: "Checkmark".into(),
tooltip: Default::default(), tooltip: Default::default(),
tooltip_shortcut: Default::default(), tooltip_shortcut: Default::default(),
for_label: CheckboxId::default(), for_label: CheckboxId::new(),
on_update: Default::default(), on_update: Default::default(),
on_commit: Default::default(), on_commit: Default::default(),
} }
} }
} }
#[derive(Clone, Default, Debug, Eq, PartialEq)] #[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct CheckboxId(Arc<OnceCell<u64>>); pub struct CheckboxId(u64);
impl CheckboxId { impl CheckboxId {
pub fn fill(&mut self) { pub fn new() -> Self {
let _ = self.0.set(graphene_std::uuid::generate_uuid()); Self(graphene_std::uuid::generate_uuid())
}
}
impl Default for CheckboxId {
fn default() -> Self {
Self::new()
} }
} }
impl specta::Type for CheckboxId { impl specta::Type for CheckboxId {
@@ -65,31 +68,6 @@ impl specta::Type for CheckboxId {
specta::datatype::DataType::Primitive(specta::datatype::PrimitiveType::u64) specta::datatype::DataType::Primitive(specta::datatype::PrimitiveType::u64)
} }
} }
impl serde::Serialize for CheckboxId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.0.get().copied().serialize(serializer)
}
}
impl<'a> serde::Deserialize<'a> for CheckboxId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'a>,
{
let optional_id: Option<u64> = Option::deserialize(deserializer)?;
// TODO: This is potentially weird because after deserialization the two labels will be decoupled if the value not existent
let id = optional_id.unwrap_or(0);
let checkbox_id = CheckboxId(OnceCell::new().into());
checkbox_id.0.set(id).map_err(serde::de::Error::custom)?;
Ok(checkbox_id)
}
}
fn checkbox_id_is_empty(id: &CheckboxId) -> bool {
id.0.get().is_none()
}
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)] #[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq, Default)] #[derivative(Debug, PartialEq, Default)]
@@ -57,21 +57,12 @@ pub struct TextLabel {
pub tooltip: String, pub tooltip: String,
#[serde(rename = "checkboxId")] #[serde(rename = "forCheckbox")]
#[widget_builder(skip)] pub for_checkbox: CheckboxId,
pub checkbox_id: CheckboxId,
// Body // Body
#[widget_builder(constructor)] #[widget_builder(constructor)]
pub value: String, pub value: String,
} }
impl TextLabel {
pub fn for_checkbox(mut self, id: &mut CheckboxId) -> Self {
id.fill();
self.checkbox_id = id.clone();
self
}
}
// TODO: Add UserInputLabel // TODO: Add UserInputLabel
@@ -182,7 +182,7 @@ pub enum DocumentMessage {
UpdateUpstreamTransforms { UpdateUpstreamTransforms {
upstream_footprints: HashMap<NodeId, Footprint>, upstream_footprints: HashMap<NodeId, Footprint>,
local_transforms: HashMap<NodeId, DAffine2>, local_transforms: HashMap<NodeId, DAffine2>,
first_instance_source_id: HashMap<NodeId, Option<NodeId>>, first_element_source_id: HashMap<NodeId, Option<NodeId>>,
}, },
UpdateClickTargets { UpdateClickTargets {
click_targets: HashMap<NodeId, Vec<ClickTarget>>, click_targets: HashMap<NodeId, Vec<ClickTarget>>,
@@ -32,7 +32,8 @@ use graph_craft::document::{NodeId, NodeInput, NodeNetwork, OldNodeNetwork};
use graphene_std::math::quad::Quad; use graphene_std::math::quad::Quad;
use graphene_std::path_bool::{boolean_intersect, path_bool_lib}; use graphene_std::path_bool::{boolean_intersect, path_bool_lib};
use graphene_std::raster::BlendMode; use graphene_std::raster::BlendMode;
use graphene_std::raster_types::{Raster, RasterDataTable}; use graphene_std::raster_types::Raster;
use graphene_std::table::Table;
use graphene_std::vector::PointId; use graphene_std::vector::PointId;
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType}; use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
use graphene_std::vector::style::ViewMode; use graphene_std::vector::style::ViewMode;
@@ -837,7 +838,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
responses.add(DocumentMessage::AddTransaction); responses.add(DocumentMessage::AddTransaction);
let layer = graph_modification_utils::new_image_layer(RasterDataTable::new(Raster::new_cpu(image)), layer_node_id, self.new_layer_parent(true), responses); let layer = graph_modification_utils::new_image_layer(Table::new_from_element(Raster::new_cpu(image)), layer_node_id, self.new_layer_parent(true), responses);
if let Some(name) = name { if let Some(name) = name {
responses.add(NodeGraphMessage::SetDisplayName { responses.add(NodeGraphMessage::SetDisplayName {
@@ -1302,10 +1303,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
DocumentMessage::UpdateUpstreamTransforms { DocumentMessage::UpdateUpstreamTransforms {
upstream_footprints, upstream_footprints,
local_transforms, local_transforms,
first_instance_source_id, first_element_source_id,
} => { } => {
self.network_interface.update_transforms(upstream_footprints, local_transforms); self.network_interface.update_transforms(upstream_footprints, local_transforms);
self.network_interface.update_first_instance_source_id(first_instance_source_id); self.network_interface.update_first_element_source_id(first_element_source_id);
} }
DocumentMessage::UpdateClickTargets { click_targets } => { DocumentMessage::UpdateClickTargets { click_targets } => {
// TODO: Allow non layer nodes to have click targets // TODO: Allow non layer nodes to have click targets
@@ -1435,20 +1436,6 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
}, },
}) })
} }
// Some parts of the editior (e.g. navigation messages) depend on these bounds to be present
let bounds = if self.graph_view_overlay_open {
self.network_interface.all_nodes_bounding_box(&self.breadcrumb_network_path).cloned()
} else {
self.network_interface.document_bounds_document_space(true)
};
if bounds.is_some() {
responses.add(DeferMessage::TriggerNavigationReady);
} else {
// If we don't have bounds yet, we need wait until the node graph has run once more
responses.add(DeferMessage::AfterGraphRun {
messages: vec![DocumentMessage::PTZUpdate.into()],
});
}
} }
DocumentMessage::SelectionStepBack => { DocumentMessage::SelectionStepBack => {
self.network_interface.selection_step_back(&self.selection_network_path); self.network_interface.selection_step_back(&self.selection_network_path);
@@ -2160,7 +2147,7 @@ impl DocumentMessageHandler {
}, },
LayoutGroup::Row { LayoutGroup::Row {
widgets: { widgets: {
let mut checkbox_id = CheckboxId::default(); let checkbox_id = CheckboxId::new();
vec![ vec![
CheckboxInput::new(self.overlays_visibility_settings.artboard_name) CheckboxInput::new(self.overlays_visibility_settings.artboard_name)
.on_update(|optional_input: &CheckboxInput| { .on_update(|optional_input: &CheckboxInput| {
@@ -2170,15 +2157,15 @@ impl DocumentMessageHandler {
} }
.into() .into()
}) })
.for_label(checkbox_id.clone()) .for_label(checkbox_id)
.widget_holder(), .widget_holder(),
TextLabel::new("Artboard Name".to_string()).for_checkbox(&mut checkbox_id).widget_holder(), TextLabel::new("Artboard Name".to_string()).for_checkbox(checkbox_id).widget_holder(),
] ]
}, },
}, },
LayoutGroup::Row { LayoutGroup::Row {
widgets: { widgets: {
let mut checkbox_id = CheckboxId::default(); let checkbox_id = CheckboxId::new();
vec![ vec![
CheckboxInput::new(self.overlays_visibility_settings.transform_measurement) CheckboxInput::new(self.overlays_visibility_settings.transform_measurement)
.on_update(|optional_input: &CheckboxInput| { .on_update(|optional_input: &CheckboxInput| {
@@ -2188,9 +2175,9 @@ impl DocumentMessageHandler {
} }
.into() .into()
}) })
.for_label(checkbox_id.clone()) .for_label(checkbox_id)
.widget_holder(), .widget_holder(),
TextLabel::new("G/R/S Measurement".to_string()).for_checkbox(&mut checkbox_id).widget_holder(), TextLabel::new("G/R/S Measurement".to_string()).for_checkbox(checkbox_id).widget_holder(),
] ]
}, },
}, },
@@ -2199,7 +2186,7 @@ impl DocumentMessageHandler {
}, },
LayoutGroup::Row { LayoutGroup::Row {
widgets: { widgets: {
let mut checkbox_id = CheckboxId::default(); let checkbox_id = CheckboxId::new();
vec![ vec![
CheckboxInput::new(self.overlays_visibility_settings.quick_measurement) CheckboxInput::new(self.overlays_visibility_settings.quick_measurement)
.on_update(|optional_input: &CheckboxInput| { .on_update(|optional_input: &CheckboxInput| {
@@ -2209,15 +2196,15 @@ impl DocumentMessageHandler {
} }
.into() .into()
}) })
.for_label(checkbox_id.clone()) .for_label(checkbox_id)
.widget_holder(), .widget_holder(),
TextLabel::new("Quick Measurement".to_string()).for_checkbox(&mut checkbox_id).widget_holder(), TextLabel::new("Quick Measurement".to_string()).for_checkbox(checkbox_id).widget_holder(),
] ]
}, },
}, },
LayoutGroup::Row { LayoutGroup::Row {
widgets: { widgets: {
let mut checkbox_id = CheckboxId::default(); let checkbox_id = CheckboxId::new();
vec![ vec![
CheckboxInput::new(self.overlays_visibility_settings.transform_cage) CheckboxInput::new(self.overlays_visibility_settings.transform_cage)
.on_update(|optional_input: &CheckboxInput| { .on_update(|optional_input: &CheckboxInput| {
@@ -2227,15 +2214,15 @@ impl DocumentMessageHandler {
} }
.into() .into()
}) })
.for_label(checkbox_id.clone()) .for_label(checkbox_id)
.widget_holder(), .widget_holder(),
TextLabel::new("Transform Cage".to_string()).for_checkbox(&mut checkbox_id).widget_holder(), TextLabel::new("Transform Cage".to_string()).for_checkbox(checkbox_id).widget_holder(),
] ]
}, },
}, },
LayoutGroup::Row { LayoutGroup::Row {
widgets: { widgets: {
let mut checkbox_id = CheckboxId::default(); let checkbox_id = CheckboxId::new();
vec![ vec![
CheckboxInput::new(self.overlays_visibility_settings.compass_rose) CheckboxInput::new(self.overlays_visibility_settings.compass_rose)
.on_update(|optional_input: &CheckboxInput| { .on_update(|optional_input: &CheckboxInput| {
@@ -2245,15 +2232,15 @@ impl DocumentMessageHandler {
} }
.into() .into()
}) })
.for_label(checkbox_id.clone()) .for_label(checkbox_id)
.widget_holder(), .widget_holder(),
TextLabel::new("Transform Dial".to_string()).for_checkbox(&mut checkbox_id).widget_holder(), TextLabel::new("Transform Dial".to_string()).for_checkbox(checkbox_id).widget_holder(),
] ]
}, },
}, },
LayoutGroup::Row { LayoutGroup::Row {
widgets: { widgets: {
let mut checkbox_id = CheckboxId::default(); let checkbox_id = CheckboxId::new();
vec![ vec![
CheckboxInput::new(self.overlays_visibility_settings.pivot) CheckboxInput::new(self.overlays_visibility_settings.pivot)
.on_update(|optional_input: &CheckboxInput| { .on_update(|optional_input: &CheckboxInput| {
@@ -2263,15 +2250,15 @@ impl DocumentMessageHandler {
} }
.into() .into()
}) })
.for_label(checkbox_id.clone()) .for_label(checkbox_id)
.widget_holder(), .widget_holder(),
TextLabel::new("Transform Pivot".to_string()).for_checkbox(&mut checkbox_id).widget_holder(), TextLabel::new("Transform Pivot".to_string()).for_checkbox(checkbox_id).widget_holder(),
] ]
}, },
}, },
LayoutGroup::Row { LayoutGroup::Row {
widgets: { widgets: {
let mut checkbox_id = CheckboxId::default(); let checkbox_id = CheckboxId::new();
vec![ vec![
CheckboxInput::new(self.overlays_visibility_settings.pivot) CheckboxInput::new(self.overlays_visibility_settings.pivot)
.on_update(|optional_input: &CheckboxInput| { .on_update(|optional_input: &CheckboxInput| {
@@ -2281,15 +2268,15 @@ impl DocumentMessageHandler {
} }
.into() .into()
}) })
.for_label(checkbox_id.clone()) .for_label(checkbox_id)
.widget_holder(), .widget_holder(),
TextLabel::new("Transform Origin".to_string()).for_checkbox(&mut checkbox_id).widget_holder(), TextLabel::new("Transform Origin".to_string()).for_checkbox(checkbox_id).widget_holder(),
] ]
}, },
}, },
LayoutGroup::Row { LayoutGroup::Row {
widgets: { widgets: {
let mut checkbox_id = CheckboxId::default(); let checkbox_id = CheckboxId::new();
vec![ vec![
CheckboxInput::new(self.overlays_visibility_settings.hover_outline) CheckboxInput::new(self.overlays_visibility_settings.hover_outline)
.on_update(|optional_input: &CheckboxInput| { .on_update(|optional_input: &CheckboxInput| {
@@ -2299,15 +2286,15 @@ impl DocumentMessageHandler {
} }
.into() .into()
}) })
.for_label(checkbox_id.clone()) .for_label(checkbox_id)
.widget_holder(), .widget_holder(),
TextLabel::new("Hover Outline".to_string()).for_checkbox(&mut checkbox_id).widget_holder(), TextLabel::new("Hover Outline".to_string()).for_checkbox(checkbox_id).widget_holder(),
] ]
}, },
}, },
LayoutGroup::Row { LayoutGroup::Row {
widgets: { widgets: {
let mut checkbox_id = CheckboxId::default(); let checkbox_id = CheckboxId::new();
vec![ vec![
CheckboxInput::new(self.overlays_visibility_settings.selection_outline) CheckboxInput::new(self.overlays_visibility_settings.selection_outline)
.on_update(|optional_input: &CheckboxInput| { .on_update(|optional_input: &CheckboxInput| {
@@ -2317,9 +2304,9 @@ impl DocumentMessageHandler {
} }
.into() .into()
}) })
.for_label(checkbox_id.clone()) .for_label(checkbox_id)
.widget_holder(), .widget_holder(),
TextLabel::new("Selection Outline".to_string()).for_checkbox(&mut checkbox_id).widget_holder(), TextLabel::new("Selection Outline".to_string()).for_checkbox(checkbox_id).widget_holder(),
] ]
}, },
}, },
@@ -2328,7 +2315,7 @@ impl DocumentMessageHandler {
}, },
LayoutGroup::Row { LayoutGroup::Row {
widgets: { widgets: {
let mut checkbox_id = CheckboxId::default(); let checkbox_id = CheckboxId::new();
vec![ vec![
CheckboxInput::new(self.overlays_visibility_settings.path) CheckboxInput::new(self.overlays_visibility_settings.path)
.on_update(|optional_input: &CheckboxInput| { .on_update(|optional_input: &CheckboxInput| {
@@ -2338,15 +2325,15 @@ impl DocumentMessageHandler {
} }
.into() .into()
}) })
.for_label(checkbox_id.clone()) .for_label(checkbox_id)
.widget_holder(), .widget_holder(),
TextLabel::new("Path".to_string()).for_checkbox(&mut checkbox_id).widget_holder(), TextLabel::new("Path".to_string()).for_checkbox(checkbox_id).widget_holder(),
] ]
}, },
}, },
LayoutGroup::Row { LayoutGroup::Row {
widgets: { widgets: {
let mut checkbox_id = CheckboxId::default(); let checkbox_id = CheckboxId::new();
vec![ vec![
CheckboxInput::new(self.overlays_visibility_settings.anchors) CheckboxInput::new(self.overlays_visibility_settings.anchors)
.on_update(|optional_input: &CheckboxInput| { .on_update(|optional_input: &CheckboxInput| {
@@ -2356,15 +2343,15 @@ impl DocumentMessageHandler {
} }
.into() .into()
}) })
.for_label(checkbox_id.clone()) .for_label(checkbox_id)
.widget_holder(), .widget_holder(),
TextLabel::new("Anchors".to_string()).for_checkbox(&mut checkbox_id).widget_holder(), TextLabel::new("Anchors".to_string()).for_checkbox(checkbox_id).widget_holder(),
] ]
}, },
}, },
LayoutGroup::Row { LayoutGroup::Row {
widgets: { widgets: {
let mut checkbox_id = CheckboxId::default(); let checkbox_id = CheckboxId::new();
vec![ vec![
CheckboxInput::new(self.overlays_visibility_settings.handles) CheckboxInput::new(self.overlays_visibility_settings.handles)
.disabled(!self.overlays_visibility_settings.anchors) .disabled(!self.overlays_visibility_settings.anchors)
@@ -2375,11 +2362,11 @@ impl DocumentMessageHandler {
} }
.into() .into()
}) })
.for_label(checkbox_id.clone()) .for_label(checkbox_id)
.widget_holder(), .widget_holder(),
TextLabel::new("Handles".to_string()) TextLabel::new("Handles".to_string())
.disabled(!self.overlays_visibility_settings.anchors) .disabled(!self.overlays_visibility_settings.anchors)
.for_checkbox(&mut checkbox_id) .for_checkbox(checkbox_id)
.widget_holder(), .widget_holder(),
] ]
}, },
@@ -2412,7 +2399,7 @@ impl DocumentMessageHandler {
.into_iter() .into_iter()
.chain(SNAP_FUNCTIONS_FOR_BOUNDING_BOXES.into_iter().map(|(name, closure, tooltip)| LayoutGroup::Row { .chain(SNAP_FUNCTIONS_FOR_BOUNDING_BOXES.into_iter().map(|(name, closure, tooltip)| LayoutGroup::Row {
widgets: { widgets: {
let mut checkbox_id = CheckboxId::default(); let checkbox_id = CheckboxId::new();
vec![ vec![
CheckboxInput::new(*closure(&mut snapping_state)) CheckboxInput::new(*closure(&mut snapping_state))
.on_update(move |input: &CheckboxInput| { .on_update(move |input: &CheckboxInput| {
@@ -2423,9 +2410,9 @@ impl DocumentMessageHandler {
.into() .into()
}) })
.tooltip(tooltip) .tooltip(tooltip)
.for_label(checkbox_id.clone()) .for_label(checkbox_id)
.widget_holder(), .widget_holder(),
TextLabel::new(name).tooltip(tooltip).for_checkbox(&mut checkbox_id).widget_holder(), TextLabel::new(name).tooltip(tooltip).for_checkbox(checkbox_id).widget_holder(),
] ]
}, },
})) }))
@@ -2434,7 +2421,7 @@ impl DocumentMessageHandler {
}]) }])
.chain(SNAP_FUNCTIONS_FOR_PATHS.into_iter().map(|(name, closure, tooltip)| LayoutGroup::Row { .chain(SNAP_FUNCTIONS_FOR_PATHS.into_iter().map(|(name, closure, tooltip)| LayoutGroup::Row {
widgets: { widgets: {
let mut checkbox_id = CheckboxId::default(); let checkbox_id = CheckboxId::new();
vec![ vec![
CheckboxInput::new(*closure(&mut snapping_state2)) CheckboxInput::new(*closure(&mut snapping_state2))
.on_update(move |input: &CheckboxInput| { .on_update(move |input: &CheckboxInput| {
@@ -2445,9 +2432,9 @@ impl DocumentMessageHandler {
.into() .into()
}) })
.tooltip(tooltip) .tooltip(tooltip)
.for_label(checkbox_id.clone()) .for_label(checkbox_id)
.widget_holder(), .widget_holder(),
TextLabel::new(name).tooltip(tooltip).for_checkbox(&mut checkbox_id).widget_holder(), TextLabel::new(name).tooltip(tooltip).for_checkbox(checkbox_id).widget_holder(),
] ]
}, },
})) }))
@@ -2920,7 +2907,7 @@ impl DocumentMessageHandler {
/// Create a network interface with a single export /// Create a network interface with a single export
fn default_document_network_interface() -> NodeNetworkInterface { fn default_document_network_interface() -> NodeNetworkInterface {
let mut network_interface = NodeNetworkInterface::default(); let mut network_interface = NodeNetworkInterface::default();
network_interface.add_export(TaggedValue::ArtboardGroup(graphene_std::ArtboardGroupTable::default()), -1, "", &[]); network_interface.add_export(TaggedValue::ArtboardGroup(Default::default()), -1, "", &[]);
network_interface network_interface
} }
@@ -8,7 +8,8 @@ use graph_craft::document::NodeId;
use graphene_std::Artboard; use graphene_std::Artboard;
use graphene_std::brush::brush_stroke::BrushStroke; use graphene_std::brush::brush_stroke::BrushStroke;
use graphene_std::raster::BlendMode; use graphene_std::raster::BlendMode;
use graphene_std::raster_types::{CPU, RasterDataTable}; use graphene_std::raster_types::{CPU, Raster};
use graphene_std::table::Table;
use graphene_std::text::{Font, TypesettingConfig}; use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::vector::PointId; use graphene_std::vector::PointId;
use graphene_std::vector::VectorModificationType; use graphene_std::vector::VectorModificationType;
@@ -69,7 +70,7 @@ pub enum GraphOperationMessage {
}, },
NewBitmapLayer { NewBitmapLayer {
id: NodeId, id: NodeId,
image_frame: RasterDataTable<CPU>, image_frame: Table<Raster<CPU>>,
parent: LayerNodeIdentifier, parent: LayerNodeIdentifier,
insert_index: usize, insert_index: usize,
}, },
@@ -11,12 +11,13 @@ use graph_craft::document::{NodeId, NodeInput};
use graphene_std::Artboard; use graphene_std::Artboard;
use graphene_std::brush::brush_stroke::BrushStroke; use graphene_std::brush::brush_stroke::BrushStroke;
use graphene_std::raster::BlendMode; use graphene_std::raster::BlendMode;
use graphene_std::raster_types::{CPU, RasterDataTable}; use graphene_std::raster_types::{CPU, Raster};
use graphene_std::table::Table;
use graphene_std::text::{Font, TypesettingConfig}; use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::vector::VectorData;
use graphene_std::vector::style::{Fill, Stroke}; use graphene_std::vector::style::{Fill, Stroke};
use graphene_std::vector::{PointId, VectorModificationType}; use graphene_std::vector::{PointId, VectorModificationType};
use graphene_std::vector::{VectorData, VectorDataTable}; use graphene_std::{Graphic, NodeInputDecleration};
use graphene_std::{GraphicGroupTable, NodeInputDecleration};
#[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)] #[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
pub enum TransformIn { pub enum TransformIn {
@@ -130,8 +131,8 @@ impl<'a> ModifyInputsContext<'a> {
/// Creates an artboard as the primary export for the document network /// Creates an artboard as the primary export for the document network
pub fn create_artboard(&mut self, new_id: NodeId, artboard: Artboard) -> LayerNodeIdentifier { pub fn create_artboard(&mut self, new_id: NodeId, artboard: Artboard) -> LayerNodeIdentifier {
let artboard_node_template = resolve_document_node_type("Artboard").expect("Node").node_template_input_override([ let artboard_node_template = resolve_document_node_type("Artboard").expect("Node").node_template_input_override([
Some(NodeInput::value(TaggedValue::ArtboardGroup(graphene_std::ArtboardGroupTable::default()), true)), Some(NodeInput::value(TaggedValue::ArtboardGroup(Default::default()), true)),
Some(NodeInput::value(TaggedValue::GraphicGroup(graphene_std::GraphicGroupTable::default()), true)), Some(NodeInput::value(TaggedValue::GraphicGroup(Default::default()), true)),
Some(NodeInput::value(TaggedValue::DVec2(artboard.location.into()), false)), Some(NodeInput::value(TaggedValue::DVec2(artboard.location.into()), false)),
Some(NodeInput::value(TaggedValue::DVec2(artboard.dimensions.into()), false)), Some(NodeInput::value(TaggedValue::DVec2(artboard.dimensions.into()), false)),
Some(NodeInput::value(TaggedValue::Color(artboard.background), false)), Some(NodeInput::value(TaggedValue::Color(artboard.background), false)),
@@ -143,7 +144,7 @@ impl<'a> ModifyInputsContext<'a> {
pub fn insert_boolean_data(&mut self, operation: graphene_std::path_bool::BooleanOperation, layer: LayerNodeIdentifier) { pub fn insert_boolean_data(&mut self, operation: graphene_std::path_bool::BooleanOperation, layer: LayerNodeIdentifier) {
let boolean = resolve_document_node_type("Boolean Operation").expect("Boolean node does not exist").node_template_input_override([ let boolean = resolve_document_node_type("Boolean Operation").expect("Boolean node does not exist").node_template_input_override([
Some(NodeInput::value(TaggedValue::GraphicGroup(graphene_std::GraphicGroupTable::default()), true)), Some(NodeInput::value(TaggedValue::GraphicGroup(Default::default()), true)),
Some(NodeInput::value(TaggedValue::BooleanOperation(operation), false)), Some(NodeInput::value(TaggedValue::BooleanOperation(operation), false)),
]); ]);
@@ -153,7 +154,7 @@ impl<'a> ModifyInputsContext<'a> {
} }
pub fn insert_vector_data(&mut self, subpaths: Vec<Subpath<PointId>>, layer: LayerNodeIdentifier, include_transform: bool, include_fill: bool, include_stroke: bool) { pub fn insert_vector_data(&mut self, subpaths: Vec<Subpath<PointId>>, layer: LayerNodeIdentifier, include_transform: bool, include_fill: bool, include_stroke: bool) {
let vector_data = VectorDataTable::new(VectorData::from_subpaths(subpaths, true)); let vector_data = Table::new_from_element(VectorData::from_subpaths(subpaths, true));
let shape = resolve_document_node_type("Path") let shape = resolve_document_node_type("Path")
.expect("Path node does not exist") .expect("Path node does not exist")
@@ -218,7 +219,7 @@ impl<'a> ModifyInputsContext<'a> {
self.network_interface.move_node_to_chain_start(&stroke_id, layer, &[]); self.network_interface.move_node_to_chain_start(&stroke_id, layer, &[]);
} }
pub fn insert_image_data(&mut self, image_frame: RasterDataTable<CPU>, layer: LayerNodeIdentifier) { pub fn insert_image_data(&mut self, image_frame: Table<Raster<CPU>>, layer: LayerNodeIdentifier) {
let transform = resolve_document_node_type("Transform").expect("Transform node does not exist").default_node_template(); let transform = resolve_document_node_type("Transform").expect("Transform node does not exist").default_node_template();
let image = resolve_document_node_type("Image Value") let image = resolve_document_node_type("Image Value")
.expect("ImageValue node does not exist") .expect("ImageValue node does not exist")
@@ -302,7 +303,7 @@ impl<'a> ModifyInputsContext<'a> {
// TODO: Allow the path node to operate on Graphic Group data by utilizing the reference for each vector data in a group. // TODO: Allow the path node to operate on Graphic Group data by utilizing the reference for each vector data in a group.
if node_definition.identifier == "Path" { if node_definition.identifier == "Path" {
let layer_input_type = self.network_interface.input_type(&InputConnector::node(output_layer.to_node(), 1), &[]).0.nested_type().clone(); let layer_input_type = self.network_interface.input_type(&InputConnector::node(output_layer.to_node(), 1), &[]).0.nested_type().clone();
if layer_input_type == concrete!(GraphicGroupTable) { if layer_input_type == concrete!(Table<Graphic>) {
let Some(flatten_path_definition) = resolve_document_node_type("Flatten Path") else { let Some(flatten_path_definition) = resolve_document_node_type("Flatten Path") else {
log::error!("Flatten Path does not exist in ModifyInputsContext::existing_node_id"); log::error!("Flatten Path does not exist in ModifyInputsContext::existing_node_id");
return None; return None;
@@ -19,11 +19,12 @@ use graph_craft::document::*;
use graphene_std::brush::brush_cache::BrushCache; use graphene_std::brush::brush_cache::BrushCache;
use graphene_std::extract_xy::XY; use graphene_std::extract_xy::XY;
use graphene_std::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlueAlpha}; use graphene_std::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlueAlpha};
use graphene_std::raster_types::{CPU, RasterDataTable}; use graphene_std::raster_types::{CPU, Raster};
use graphene_std::table::Table;
use graphene_std::text::{Font, TypesettingConfig}; use graphene_std::text::{Font, TypesettingConfig};
#[allow(unused_imports)] #[allow(unused_imports)]
use graphene_std::transform::Footprint; use graphene_std::transform::Footprint;
use graphene_std::vector::VectorDataTable; use graphene_std::vector::VectorData;
use graphene_std::*; use graphene_std::*;
use std::collections::{HashMap, HashSet, VecDeque}; use std::collections::{HashMap, HashSet, VecDeque};
@@ -268,8 +269,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true), NodeInput::value(TaggedValue::GraphicGroup(Default::default()), true),
NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true), NodeInput::value(TaggedValue::GraphicGroup(Default::default()), true),
], ],
..Default::default() ..Default::default()
}, },
@@ -338,7 +339,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
// Ensure this ID is kept in sync with the ID in set_alias so that the name input is kept in sync with the alias // Ensure this ID is kept in sync with the ID in set_alias so that the name input is kept in sync with the alias
DocumentNode { DocumentNode {
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
implementation: DocumentNodeImplementation::ProtoNode(graphic_element::to_artboard::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(artboard::to_artboard::IDENTIFIER),
inputs: vec![ inputs: vec![
NodeInput::network(concrete!(TaggedValue), 1), NodeInput::network(concrete!(TaggedValue), 1),
NodeInput::value(TaggedValue::String(String::from("Artboard")), false), NodeInput::value(TaggedValue::String(String::from("Artboard")), false),
@@ -361,11 +362,11 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode { DocumentNode {
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
inputs: vec![ inputs: vec![
NodeInput::network(graphene_std::Type::Fn(Box::new(concrete!(Context)), Box::new(concrete!(ArtboardGroupTable))), 0), NodeInput::network(graphene_std::Type::Fn(Box::new(concrete!(Context)), Box::new(concrete!(Table<Artboard>))), 0),
NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(1), 0),
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
], ],
implementation: DocumentNodeImplementation::ProtoNode(graphic_element::append_artboard::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(artboard::append_artboard::IDENTIFIER),
..Default::default() ..Default::default()
}, },
] ]
@@ -376,8 +377,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::ArtboardGroup(ArtboardGroupTable::default()), true), NodeInput::value(TaggedValue::ArtboardGroup(Default::default()), true),
NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true), NodeInput::value(TaggedValue::GraphicGroup(Default::default()), true),
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false), NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
NodeInput::value(TaggedValue::DVec2(DVec2::new(1920., 1080.)), false), NodeInput::value(TaggedValue::DVec2(DVec2::new(1920., 1080.)), false),
NodeInput::value(TaggedValue::Color(Color::WHITE), false), NodeInput::value(TaggedValue::Color(Color::WHITE), false),
@@ -591,7 +592,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
description: Cow::Borrowed("Creates a new canvas object."), description: Cow::Borrowed("Creates a new canvas object."),
properties: None, properties: None,
}, },
#[cfg(all(feature = "gpu", target_arch = "wasm32"))] #[cfg(all(feature = "gpu", target_family = "wasm"))]
DocumentNodeDefinition { DocumentNodeDefinition {
identifier: "Rasterize", identifier: "Rasterize",
category: "Raster", category: "Raster",
@@ -627,7 +628,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::VectorData(VectorDataTable::default()), true), NodeInput::value(TaggedValue::VectorData(Default::default()), true),
NodeInput::value( NodeInput::value(
TaggedValue::Footprint(Footprint { TaggedValue::Footprint(Footprint {
transform: DAffine2::from_scale_angle_translation(DVec2::new(1000., 1000.), 0., DVec2::new(0., 0.)), transform: DAffine2::from_scale_angle_translation(DVec2::new(1000., 1000.), 0., DVec2::new(0., 0.)),
@@ -752,7 +753,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: [ nodes: [
DocumentNode { DocumentNode {
inputs: vec![ inputs: vec![
NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::network(concrete!(Table<Raster<CPU>>), 0),
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Red), false), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Red), false),
], ],
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
@@ -761,7 +762,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
}, },
DocumentNode { DocumentNode {
inputs: vec![ inputs: vec![
NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::network(concrete!(Table<Raster<CPU>>), 0),
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Green), false), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Green), false),
], ],
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
@@ -770,7 +771,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
}, },
DocumentNode { DocumentNode {
inputs: vec![ inputs: vec![
NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::network(concrete!(Table<Raster<CPU>>), 0),
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Blue), false), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Blue), false),
], ],
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
@@ -779,7 +780,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
}, },
DocumentNode { DocumentNode {
inputs: vec![ inputs: vec![
NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::network(concrete!(Table<Raster<CPU>>), 0),
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Alpha), false), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Alpha), false),
], ],
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
@@ -793,7 +794,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
.collect(), .collect(),
..Default::default() ..Default::default()
}), }),
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)], inputs: vec![NodeInput::value(TaggedValue::RasterData(Default::default()), true)],
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
@@ -859,13 +860,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
exports: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(1), 0)], exports: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(1), 0)],
nodes: [ nodes: [
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::value(TaggedValue::XY(XY::X), false)], inputs: vec![NodeInput::network(concrete!(Table<Raster<CPU>>), 0), NodeInput::value(TaggedValue::XY(XY::X), false)],
implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::value(TaggedValue::XY(XY::Y), false)], inputs: vec![NodeInput::network(concrete!(Table<Raster<CPU>>), 0), NodeInput::value(TaggedValue::XY(XY::Y), false)],
implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
@@ -878,7 +879,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)], inputs: vec![NodeInput::value(TaggedValue::RasterData(Default::default()), true)],
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
@@ -931,7 +932,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
exports: vec![NodeInput::node(NodeId(0), 0)], exports: vec![NodeInput::node(NodeId(0), 0)],
nodes: vec![DocumentNode { nodes: vec![DocumentNode {
inputs: vec![ inputs: vec![
NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::network(concrete!(Table<Raster<CPU>>), 0),
NodeInput::network(concrete!(Vec<brush::brush_stroke::BrushStroke>), 1), NodeInput::network(concrete!(Vec<brush::brush_stroke::BrushStroke>), 1),
NodeInput::network(concrete!(BrushCache), 2), NodeInput::network(concrete!(BrushCache), 2),
], ],
@@ -946,7 +947,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true), NodeInput::value(TaggedValue::RasterData(Default::default()), true),
NodeInput::value(TaggedValue::BrushStrokes(Vec::new()), false), NodeInput::value(TaggedValue::BrushStrokes(Vec::new()), false),
NodeInput::value(TaggedValue::BrushCache(BrushCache::default()), false), NodeInput::value(TaggedValue::BrushCache(BrushCache::default()), false),
], ],
@@ -985,7 +986,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)], inputs: vec![NodeInput::value(TaggedValue::RasterData(Default::default()), true)],
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
..Default::default() ..Default::default()
}, },
@@ -1004,7 +1005,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)], inputs: vec![NodeInput::value(TaggedValue::RasterData(Default::default()), true)],
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
..Default::default() ..Default::default()
}, },
@@ -1098,7 +1099,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::network(concrete!(Table<Raster<CPU>>), 0), NodeInput::node(NodeId(0), 0)],
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
implementation: DocumentNodeImplementation::ProtoNode(wgpu_executor::texture_upload::upload_texture::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(wgpu_executor::texture_upload::upload_texture::IDENTIFIER),
..Default::default() ..Default::default()
@@ -1116,7 +1117,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
.collect(), .collect(),
..Default::default() ..Default::default()
}), }),
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)], inputs: vec![NodeInput::value(TaggedValue::RasterData(Default::default()), true)],
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
@@ -1195,7 +1196,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
// document_node: DocumentNode { // document_node: DocumentNode {
// implementation: DocumentNodeImplementation::proto("graphene_core::raster::CurvesNode"), // implementation: DocumentNodeImplementation::proto("graphene_core::raster::CurvesNode"),
// inputs: vec![ // inputs: vec![
// NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true), // NodeInput::value(TaggedValue::RasterData(Default::default()), true),
// NodeInput::value(TaggedValue::Curve(Default::default()), false), // NodeInput::value(TaggedValue::Curve(Default::default()), false),
// ], // ],
// ..Default::default() // ..Default::default()
@@ -1218,7 +1219,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
exports: vec![NodeInput::node(NodeId(1), 0)], exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: vec![ nodes: vec![
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0)], inputs: vec![NodeInput::network(concrete!(Table<VectorData>), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
skip_deduplication: true, skip_deduplication: true,
@@ -1242,7 +1243,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::VectorData(VectorDataTable::default()), true), NodeInput::value(TaggedValue::VectorData(Default::default()), true),
NodeInput::value(TaggedValue::VectorModification(Default::default()), false), NodeInput::value(TaggedValue::VectorModification(Default::default()), false),
], ],
..Default::default() ..Default::default()
@@ -1373,7 +1374,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
}), }),
), ),
InputMetadata::with_name_description_override("Align", "TODO", WidgetOverride::Custom("text_align".to_string())), InputMetadata::with_name_description_override("Align", "TODO", WidgetOverride::Custom("text_align".to_string())),
("Per-Glyph Instances", "Splits each text glyph into its own instance, i.e. row in the table of vector data.").into(), ("Per-Glyph Instances", "Splits each text glyph into its own row in the table of vector data.").into(),
], ],
output_names: vec!["Vector".to_string()], output_names: vec!["Vector".to_string()],
..Default::default() ..Default::default()
@@ -1495,7 +1496,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
exports: vec![NodeInput::node(NodeId(3), 0)], exports: vec![NodeInput::node(NodeId(3), 0)],
nodes: vec![ nodes: vec![
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0), NodeInput::network(concrete!(vector::style::Fill), 1)], inputs: vec![NodeInput::network(concrete!(Table<VectorData>), 0), NodeInput::network(concrete!(vector::style::Fill), 1)],
implementation: DocumentNodeImplementation::ProtoNode(path_bool::boolean_operation::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(path_bool::boolean_operation::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
@@ -1526,7 +1527,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true), NodeInput::value(TaggedValue::GraphicGroup(Default::default()), true),
NodeInput::value(TaggedValue::BooleanOperation(path_bool::BooleanOperation::Union), false), NodeInput::value(TaggedValue::BooleanOperation(path_bool::BooleanOperation::Union), false),
], ],
..Default::default() ..Default::default()
@@ -1593,14 +1594,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
exports: vec![NodeInput::node(NodeId(4), 0)], exports: vec![NodeInput::node(NodeId(4), 0)],
nodes: [ nodes: [
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0)], inputs: vec![NodeInput::network(concrete!(Table<VectorData>), 0)],
implementation: DocumentNodeImplementation::ProtoNode(vector::subpath_segment_lengths::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(vector::subpath_segment_lengths::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![ inputs: vec![
NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0), NodeInput::network(concrete!(Table<VectorData>), 0),
NodeInput::network(concrete!(vector::misc::PointSpacingType), 1), NodeInput::network(concrete!(vector::misc::PointSpacingType), 1),
NodeInput::network(concrete!(f64), 2), NodeInput::network(concrete!(f64), 2),
NodeInput::network(concrete!(u32), 3), NodeInput::network(concrete!(u32), 3),
@@ -1639,7 +1640,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::VectorData(graphene_std::vector::VectorDataTable::default()), true), NodeInput::value(TaggedValue::VectorData(Default::default()), true),
NodeInput::value(TaggedValue::PointSpacingType(Default::default()), false), NodeInput::value(TaggedValue::PointSpacingType(Default::default()), false),
NodeInput::value(TaggedValue::F64(100.), false), NodeInput::value(TaggedValue::F64(100.), false),
NodeInput::value(TaggedValue::U32(100), false), NodeInput::value(TaggedValue::U32(100), false),
@@ -1760,7 +1761,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: [ nodes: [
DocumentNode { DocumentNode {
inputs: vec![ inputs: vec![
NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0), NodeInput::network(concrete!(Table<VectorData>), 0),
NodeInput::network(concrete!(f64), 1), NodeInput::network(concrete!(f64), 1),
NodeInput::network(concrete!(u32), 2), NodeInput::network(concrete!(u32), 2),
], ],
@@ -1794,7 +1795,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::VectorData(graphene_std::vector::VectorDataTable::default()), true), NodeInput::value(TaggedValue::VectorData(Default::default()), true),
NodeInput::value(TaggedValue::F64(10.), false), NodeInput::value(TaggedValue::F64(10.), false),
NodeInput::value(TaggedValue::U32(0), false), NodeInput::value(TaggedValue::U32(0), false),
], ],
@@ -17,11 +17,11 @@ use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle,
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
use crate::messages::tool::common_functionality::graph_modification_utils::{self, get_clip_mode}; use crate::messages::tool::common_functionality::graph_modification_utils::{self, get_clip_mode};
use crate::messages::tool::common_functionality::utility_functions::make_path_editable_is_allowed;
use crate::messages::tool::tool_messages::tool_prelude::{Key, MouseMotion}; use crate::messages::tool::tool_messages::tool_prelude::{Key, MouseMotion};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use bezier_rs::Subpath; use bezier_rs::Subpath;
use glam::{DAffine2, DVec2, IVec2}; use glam::{DAffine2, DVec2, IVec2};
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput}; use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput};
use graph_craft::proto::GraphErrors; use graph_craft::proto::GraphErrors;
use graphene_std::math::math_ext::QuadExt; use graphene_std::math::math_ext::QuadExt;
@@ -126,35 +126,9 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![new_layer_id] }); responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![new_layer_id] });
} }
NodeGraphMessage::AddPathNode => { NodeGraphMessage::AddPathNode => {
let selected_nodes = network_interface.selected_nodes(); if let Some(layer) = make_path_editable_is_allowed(network_interface, network_interface.document_metadata()) {
let mut selected_layers = selected_nodes.selected_layers(network_interface.document_metadata()); responses.add(NodeGraphMessage::CreateNodeInLayerWithTransaction { node_type: "Path".to_string(), layer });
let first_layer = selected_layers.next(); responses.add(BroadcastEvent::SelectionChanged);
let second_layer = selected_layers.next();
let has_single_selection = first_layer.is_some() && second_layer.is_none();
let compatible_type = first_layer.and_then(|layer| {
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &network_interface);
graph_layer.horizontal_layer_flow().nth(1).and_then(|node_id| {
let (output_type, _) = network_interface.output_type(&node_id, 0, &[]);
Some(format!("type:{}", output_type.nested_type()))
})
});
let is_compatible = compatible_type.as_deref() == Some("type:Instances<VectorData>");
if first_layer.is_some() && has_single_selection && is_compatible {
if let Some(layer) = first_layer {
let node_type = "Path".to_string();
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &network_interface);
let is_modifiable = matches!(graph_layer.find_input("Path", 1), Some(TaggedValue::VectorModification(_)));
if !is_modifiable {
responses.add(NodeGraphMessage::CreateNodeInLayerWithTransaction {
node_type: node_type.clone(),
layer: LayerNodeIdentifier::new_unchecked(layer.to_node()),
});
responses.add(BroadcastEvent::SelectionChanged);
}
}
} }
} }
NodeGraphMessage::AddImport => { NodeGraphMessage::AddImport => {
@@ -19,16 +19,14 @@ use graphene_std::raster::{
BlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute, BlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute,
SelectiveColorChoice, SelectiveColorChoice,
}; };
use graphene_std::raster_types::{CPU, GPU, RasterDataTable}; use graphene_std::raster_types::{CPU, GPU, Raster};
use graphene_std::table::Table;
use graphene_std::text::{Font, TextAlign}; use graphene_std::text::{Font, TextAlign};
use graphene_std::transform::{Footprint, ReferencePoint, Transform}; use graphene_std::transform::{Footprint, ReferencePoint, Transform};
use graphene_std::vector::VectorDataTable; use graphene_std::vector::VectorData;
use graphene_std::vector::misc::GridType; use graphene_std::vector::misc::{ArcType, CentroidType, GridType, MergeByDistanceAlgorithm, PointSpacingType};
use graphene_std::vector::misc::{ArcType, MergeByDistanceAlgorithm}; use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops, GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
use graphene_std::vector::misc::{CentroidType, PointSpacingType}; use graphene_std::{Graphic, NodeInputDecleration};
use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops};
use graphene_std::vector::style::{GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
use graphene_std::{GraphicGroupTable, NodeInputDecleration};
pub(crate) fn string_properties(text: &str) -> Vec<LayoutGroup> { pub(crate) fn string_properties(text: &str) -> Vec<LayoutGroup> {
let widget = TextLabel::new(text).widget_holder(); let widget = TextLabel::new(text).widget_holder();
@@ -185,9 +183,9 @@ pub(crate) fn property_from_type(
// ==================== // ====================
// GRAPHICAL DATA TYPES // GRAPHICAL DATA TYPES
// ==================== // ====================
Some(x) if x == TypeId::of::<VectorDataTable>() => vector_data_widget(default_info).into(), Some(x) if x == TypeId::of::<Table<VectorData>>() => vector_data_widget(default_info).into(),
Some(x) if x == TypeId::of::<RasterDataTable<CPU>>() || x == TypeId::of::<RasterDataTable<GPU>>() => raster_widget(default_info).into(), Some(x) if x == TypeId::of::<Table<Raster<CPU>>>() || x == TypeId::of::<Table<Raster<GPU>>>() => raster_widget(default_info).into(),
Some(x) if x == TypeId::of::<GraphicGroupTable>() => group_widget(default_info).into(), Some(x) if x == TypeId::of::<Table<Graphic>>() => group_widget(default_info).into(),
// ============ // ============
// STRUCT TYPES // STRUCT TYPES
// ============ // ============
@@ -18,18 +18,17 @@ pub enum FrontendGraphDataType {
impl FrontendGraphDataType { impl FrontendGraphDataType {
pub fn from_type(input: &Type) -> Self { pub fn from_type(input: &Type) -> Self {
match TaggedValue::from_type_or_none(input) { match TaggedValue::from_type_or_none(input) {
TaggedValue::Image(_) | TaggedValue::RasterData(_) => Self::Raster, TaggedValue::RasterData(_) => Self::Raster,
TaggedValue::Subpaths(_) | TaggedValue::VectorData(_) => Self::VectorData, TaggedValue::VectorData(_) => Self::VectorData,
TaggedValue::U32(_) TaggedValue::U32(_)
| TaggedValue::U64(_) | TaggedValue::U64(_)
| TaggedValue::F64(_) | TaggedValue::F64(_)
| TaggedValue::DVec2(_) | TaggedValue::DVec2(_)
| TaggedValue::OptionalDVec2(_)
| TaggedValue::F64Array4(_) | TaggedValue::F64Array4(_)
| TaggedValue::VecF64(_) | TaggedValue::VecF64(_)
| TaggedValue::VecDVec2(_) | TaggedValue::VecDVec2(_)
| TaggedValue::DAffine2(_) => Self::Number, | TaggedValue::DAffine2(_) => Self::Number,
TaggedValue::GraphicGroup(_) | TaggedValue::GraphicElement(_) => Self::Group, // TODO: Is GraphicElement supposed to be included here? TaggedValue::GraphicGroup(_) => Self::Group,
TaggedValue::ArtboardGroup(_) => Self::Artboard, TaggedValue::ArtboardGroup(_) => Self::Artboard,
_ => Self::General, _ => Self::General,
} }
@@ -2,7 +2,7 @@ pub mod grid_overlays;
mod overlays_message; mod overlays_message;
mod overlays_message_handler; mod overlays_message_handler;
pub mod utility_functions; pub mod utility_functions;
#[cfg_attr(not(target_arch = "wasm32"), path = "utility_types_vello.rs")] #[cfg_attr(not(target_family = "wasm"), path = "utility_types_vello.rs")]
pub mod utility_types; pub mod utility_types;
#[doc(inline)] #[doc(inline)]
@@ -11,20 +11,24 @@ pub struct OverlaysMessageContext<'a> {
#[derive(Debug, Clone, Default, ExtractField)] #[derive(Debug, Clone, Default, ExtractField)]
pub struct OverlaysMessageHandler { pub struct OverlaysMessageHandler {
pub overlay_providers: HashSet<OverlayProvider>, pub overlay_providers: HashSet<OverlayProvider>,
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
canvas: Option<web_sys::HtmlCanvasElement>, canvas: Option<web_sys::HtmlCanvasElement>,
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
context: Option<web_sys::CanvasRenderingContext2d>, context: Option<web_sys::CanvasRenderingContext2d>,
} }
#[message_handler_data] #[message_handler_data]
impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMessageHandler { impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMessageHandler {
fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque<Message>, context: OverlaysMessageContext) { fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque<Message>, context: OverlaysMessageContext) {
let OverlaysMessageContext { visibility_settings, ipp, .. } = context; let OverlaysMessageContext {
let device_pixel_ratio = context.device_pixel_ratio; visibility_settings,
ipp,
device_pixel_ratio,
..
} = context;
match message { match message {
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
OverlaysMessage::Draw => { OverlaysMessage::Draw => {
use super::utility_functions::overlay_canvas_element; use super::utility_functions::overlay_canvas_element;
use super::utility_types::OverlayContext; use super::utility_types::OverlayContext;
@@ -68,11 +72,10 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMes
} }
} }
} }
#[cfg(test)] #[cfg(all(not(target_family = "wasm"), not(test)))]
OverlaysMessage::Draw => {}
#[cfg(all(not(target_arch = "wasm32"), not(test)))]
OverlaysMessage::Draw => { OverlaysMessage::Draw => {
use super::utility_types::OverlayContext; use super::utility_types::OverlayContext;
let size = ipp.viewport_bounds.size(); let size = ipp.viewport_bounds.size();
let overlay_context = OverlayContext::new(size, device_pixel_ratio, visibility_settings); let overlay_context = OverlayContext::new(size, device_pixel_ratio, visibility_settings);
@@ -86,6 +89,10 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMes
} }
responses.add(FrontendMessage::RenderOverlays(overlay_context)); responses.add(FrontendMessage::RenderOverlays(overlay_context));
} }
#[cfg(all(not(target_family = "wasm"), test))]
OverlaysMessage::Draw => {
let _ = (responses, visibility_settings, ipp, device_pixel_ratio);
}
OverlaysMessage::AddProvider(message) => { OverlaysMessage::AddProvider(message) => {
self.overlay_providers.insert(message); self.overlay_providers.insert(message);
} }
@@ -22,7 +22,7 @@ use std::num::NonZeroU64;
pub struct DocumentMetadata { pub struct DocumentMetadata {
pub upstream_footprints: HashMap<NodeId, Footprint>, pub upstream_footprints: HashMap<NodeId, Footprint>,
pub local_transforms: HashMap<NodeId, DAffine2>, pub local_transforms: HashMap<NodeId, DAffine2>,
pub first_instance_source_ids: HashMap<NodeId, Option<NodeId>>, pub first_element_source_ids: HashMap<NodeId, Option<NodeId>>,
pub structure: HashMap<LayerNodeIdentifier, NodeRelations>, pub structure: HashMap<LayerNodeIdentifier, NodeRelations>,
pub click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>, pub click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
pub clip_targets: HashSet<NodeId>, pub clip_targets: HashSet<NodeId>,
@@ -90,8 +90,8 @@ impl DocumentMetadata {
let mut use_local = true; let mut use_local = true;
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, network_interface); let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, network_interface);
if let Some(path_node) = graph_layer.upstream_node_id_from_name("Path") { if let Some(path_node) = graph_layer.upstream_visible_node_id_from_name_in_layer("Path") {
if let Some(&source) = self.first_instance_source_ids.get(&layer.to_node()) { if let Some(&source) = self.first_element_source_ids.get(&layer.to_node()) {
if !network_interface if !network_interface
.upstream_flow_back_from_nodes(vec![path_node], &[], FlowType::HorizontalFlow) .upstream_flow_back_from_nodes(vec![path_node], &[], FlowType::HorizontalFlow)
.any(|upstream| Some(upstream) == source) .any(|upstream| Some(upstream) == source)
@@ -13,7 +13,9 @@ use glam::{DAffine2, DVec2, IVec2};
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork}; use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork};
use graph_craft::{Type, concrete}; use graph_craft::{Type, concrete};
use graphene_std::Artboard;
use graphene_std::math::quad::Quad; use graphene_std::math::quad::Quad;
use graphene_std::table::Table;
use graphene_std::transform::Footprint; use graphene_std::transform::Footprint;
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType}; use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
use graphene_std::vector::{PointId, VectorData, VectorModificationType}; use graphene_std::vector::{PointId, VectorData, VectorModificationType};
@@ -507,8 +509,8 @@ impl NodeNetworkInterface {
InputConnector::Node { node_id, input_index } => (node_id, input_index), InputConnector::Node { node_id, input_index } => (node_id, input_index),
InputConnector::Export(export_index) => { InputConnector::Export(export_index) => {
let Some((encapsulating_node_id, encapsulating_node_id_path)) = network_path.split_last() else { let Some((encapsulating_node_id, encapsulating_node_id_path)) = network_path.split_last() else {
// The outermost network export defaults to an ArtboardGroupTable. // The outermost network export defaults to a Table<Artboard>.
return Some((concrete!(graphene_std::ArtboardGroupTable), TypeSource::OuterMostExportDefault)); return Some((concrete!(Table<Artboard>), TypeSource::OuterMostExportDefault));
}; };
let output_type = self.output_type(encapsulating_node_id, export_index, encapsulating_node_id_path); let output_type = self.output_type(encapsulating_node_id, export_index, encapsulating_node_id_path);
@@ -1424,13 +1426,13 @@ impl NodeNetworkInterface {
.any(|id| id == potentially_upstream_node) .any(|id| id == potentially_upstream_node)
} }
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
fn text_width(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<f64> { fn text_width(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<f64> {
warn!("Failed to find width of {node_id:#?} in network_path {network_path:?} due to non-wasm arch"); warn!("Failed to find width of {node_id:#?} in network_path {network_path:?} due to non-wasm arch");
Some(0.) Some(0.)
} }
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
fn text_width(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<f64> { fn text_width(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<f64> {
let document = web_sys::window().unwrap().document().unwrap(); let document = web_sys::window().unwrap().document().unwrap();
let div = match document.create_element("div") { let div = match document.create_element("div") {
@@ -3445,12 +3447,17 @@ impl NodeNetworkInterface {
pub fn compute_modified_vector(&self, layer: LayerNodeIdentifier) -> Option<VectorData> { pub fn compute_modified_vector(&self, layer: LayerNodeIdentifier) -> Option<VectorData> {
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, self); let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, self);
if let Some(vector_data) = graph_layer.upstream_node_id_from_name("Path").and_then(|node| self.document_metadata.vector_modify.get(&node)) { if let Some(path_node) = graph_layer.upstream_visible_node_id_from_name_in_layer("Path") {
let mut modified = vector_data.clone(); if let Some(vector_data) = self.document_metadata.vector_modify.get(&path_node) {
if let Some(TaggedValue::VectorModification(modification)) = graph_layer.find_input("Path", 1) { let mut modified = vector_data.clone();
modification.apply(&mut modified);
let path_node = self.document_network().nodes.get(&path_node);
let modification_input = path_node.and_then(|node: &DocumentNode| node.inputs.get(1)).and_then(|input| input.as_value());
if let Some(TaggedValue::VectorModification(modification)) = modification_input {
modification.apply(&mut modified);
}
return Some(modified);
} }
return Some(modified);
} }
self.document_metadata self.document_metadata
@@ -3553,8 +3560,8 @@ impl NodeNetworkInterface {
} }
/// Update the cached first instance source id of the layers /// Update the cached first instance source id of the layers
pub fn update_first_instance_source_id(&mut self, new: HashMap<NodeId, Option<NodeId>>) { pub fn update_first_element_source_id(&mut self, new: HashMap<NodeId, Option<NodeId>>) {
self.document_metadata.first_instance_source_ids = new; self.document_metadata.first_element_source_ids = new;
} }
/// Update the cached click targets of the layers /// Update the cached click targets of the layers
@@ -6604,11 +6611,24 @@ struct InputTransientMetadata {
fn migrate_output_names<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Vec<String>, D::Error> { fn migrate_output_names<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Vec<String>, D::Error> {
use serde::Deserialize; use serde::Deserialize;
const REPLACEMENTS: [(&str, &str); 4] = [ const REPLACEMENTS: &[(&str, &str)] = &[
("VectorData", "Instances<VectorData>"), // Single to table data
("GraphicGroup", "Instances<GraphicGroup>"), ("VectorData", "Table<VectorData>"),
("ImageFrame", "Instances<Image>"), ("GraphicGroup", "Table<GraphicGroup>"),
("Instances<ImageFrame>", "Instances<Image>"), ("ImageFrame", "Table<Image>"),
// `ImageFrame` to `Image` rename
("Instances<ImageFrame>", "Table<Image>"),
// `Instances` to `Table` rename
("Instances<VectorData>", "Table<VectorData>"),
("Instances<GraphicGroup>", "Table<GraphicGroup>"),
("Instances<Image>", "Table<Image>"),
("Instances<GraphicElement>", "Table<Graphic>"),
("Table<GraphicElement>", "Table<Graphic>"),
("Future<Instances<VectorData>>", "Future<Table<VectorData>>"),
("Future<Instances<GraphicGroup>>", "Future<Table<GraphicGroup>>"),
("Future<Instances<Image>>", "Future<Table<Image>>"),
("Future<Instances<GraphicElement>>", "Future<Table<Graphic>>"),
("Future<Table<GraphicElement>>", "Future<Table<Graphic>>"),
]; ];
let mut names = Vec::<String>::deserialize(deserializer)?; let mut names = Vec::<String>::deserialize(deserializer)?;
@@ -10,10 +10,11 @@ use glam::IVec2;
use graph_craft::document::DocumentNode; use graph_craft::document::DocumentNode;
use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue}; use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue};
use graphene_std::ProtoNodeIdentifier; use graphene_std::ProtoNodeIdentifier;
use graphene_std::table::Table;
use graphene_std::text::{TextAlign, TypesettingConfig}; use graphene_std::text::{TextAlign, TypesettingConfig};
use graphene_std::uuid::NodeId; use graphene_std::uuid::NodeId;
use graphene_std::vector::VectorData;
use graphene_std::vector::style::{PaintOrder, StrokeAlign}; use graphene_std::vector::style::{PaintOrder, StrokeAlign};
use graphene_std::vector::{VectorData, VectorDataTable};
use std::collections::HashMap; use std::collections::HashMap;
const TEXT_REPLACEMENTS: &[(&str, &str)] = &[ const TEXT_REPLACEMENTS: &[(&str, &str)] = &[
@@ -29,16 +30,16 @@ pub struct NodeReplacement<'a> {
const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
// graphic element // graphic element
NodeReplacement { NodeReplacement {
node: graphene_std::graphic_element::append_artboard::IDENTIFIER, node: graphene_std::artboard::append_artboard::IDENTIFIER,
aliases: &["graphene_core::AddArtboardNode"], aliases: &["graphene_core::AddArtboardNode", "graphene_core::graphic_element::AppendArtboardNode"],
}, },
NodeReplacement { NodeReplacement {
node: graphene_std::graphic_element::to_artboard::IDENTIFIER, node: graphene_std::artboard::to_artboard::IDENTIFIER,
aliases: &["graphene_core::ConstructArtboardNode"], aliases: &["graphene_core::ConstructArtboardNode", "graphene_core::graphic_element::ToArtboardNode"],
}, },
NodeReplacement { NodeReplacement {
node: graphene_std::graphic_element::to_element::IDENTIFIER, node: graphene_std::graphic_element::to_element::IDENTIFIER,
aliases: &["graphene_core::ToGraphicElementNode"], aliases: &["graphene_core::ToGraphicElementNode", "graphene_core::graphic_element::ToElementNode"],
}, },
NodeReplacement { NodeReplacement {
node: graphene_std::graphic_element::to_group::IDENTIFIER, node: graphene_std::graphic_element::to_group::IDENTIFIER,
@@ -386,7 +387,7 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
}, },
NodeReplacement { NodeReplacement {
node: graphene_std::raster_nodes::std_nodes::image_value::IDENTIFIER, node: graphene_std::raster_nodes::std_nodes::image_value::IDENTIFIER,
aliases: &["graphene_std::raster::ImageValueNode"], aliases: &["graphene_std::raster::ImageValueNode", "graphene_std::raster::ImageNode"],
}, },
NodeReplacement { NodeReplacement {
node: graphene_std::raster_nodes::std_nodes::noise_pattern::IDENTIFIER, node: graphene_std::raster_nodes::std_nodes::noise_pattern::IDENTIFIER,
@@ -616,7 +617,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
return None; return None;
}; };
let path_node = path_node_type.node_template_input_override([ let path_node = path_node_type.node_template_input_override([
Some(NodeInput::value(TaggedValue::VectorData(VectorDataTable::new(vector_data)), true)), Some(NodeInput::value(TaggedValue::VectorData(Table::new_from_element(vector_data)), true)),
Some(NodeInput::value(TaggedValue::VectorModification(Default::default()), false)), Some(NodeInput::value(TaggedValue::VectorModification(Default::default()), false)),
]); ]);
@@ -19,7 +19,7 @@ pub struct MenuBarMessageHandler {
pub spreadsheet_view_open: bool, pub spreadsheet_view_open: bool,
pub message_logging_verbosity: MessageLoggingVerbosity, pub message_logging_verbosity: MessageLoggingVerbosity,
pub reset_node_definitions_on_open: bool, pub reset_node_definitions_on_open: bool,
pub single_path_node_compatible_layer_selected: bool, pub make_path_editable_is_allowed: bool,
} }
#[message_handler_data] #[message_handler_data]
@@ -46,7 +46,7 @@ impl LayoutHolder for MenuBarMessageHandler {
let message_logging_verbosity_names = self.message_logging_verbosity == MessageLoggingVerbosity::Names; let message_logging_verbosity_names = self.message_logging_verbosity == MessageLoggingVerbosity::Names;
let message_logging_verbosity_contents = self.message_logging_verbosity == MessageLoggingVerbosity::Contents; let message_logging_verbosity_contents = self.message_logging_verbosity == MessageLoggingVerbosity::Contents;
let reset_node_definitions_on_open = self.reset_node_definitions_on_open; let reset_node_definitions_on_open = self.reset_node_definitions_on_open;
let single_path_node_compatible_layer_selected = self.single_path_node_compatible_layer_selected; let make_path_editable_is_allowed = self.make_path_editable_is_allowed;
let menu_bar_entries = vec![ let menu_bar_entries = vec![
MenuBarEntry { MenuBarEntry {
@@ -442,7 +442,7 @@ impl LayoutHolder for MenuBarMessageHandler {
icon: Some("NodeShape".into()), icon: Some("NodeShape".into()),
shortcut: None, shortcut: None,
action: MenuBarEntry::create_action(|_| NodeGraphMessage::AddPathNode.into()), action: MenuBarEntry::create_action(|_| NodeGraphMessage::AddPathNode.into()),
disabled: !single_path_node_compatible_layer_selected, disabled: !make_path_editable_is_allowed,
..MenuBarEntry::default() ..MenuBarEntry::default()
}], }],
]), ]),
@@ -88,6 +88,9 @@ pub enum PortfolioMessage {
PasteSerializedData { PasteSerializedData {
data: String, data: String,
}, },
PasteSerializedVector {
data: String,
},
CenterPastedLayers { CenterPastedLayers {
layers: Vec<LayerNodeIdentifier>, layers: Vec<LayerNodeIdentifier>,
}, },
@@ -3,7 +3,7 @@ use super::document::utility_types::network_interface;
use super::spreadsheet::SpreadsheetMessageHandler; use super::spreadsheet::SpreadsheetMessageHandler;
use super::utility_types::{PanelType, PersistentData}; use super::utility_types::{PanelType, PersistentData};
use crate::application::generate_uuid; use crate::application::generate_uuid;
use crate::consts::DEFAULT_DOCUMENT_NAME; use crate::consts::{DEFAULT_DOCUMENT_NAME, DEFAULT_STROKE_WIDTH};
use crate::messages::animation::TimingInformation; use crate::messages::animation::TimingInformation;
use crate::messages::debug::utility_types::MessageLoggingVerbosity; use crate::messages::debug::utility_types::MessageLoggingVerbosity;
use crate::messages::dialog::simple_dialogs; use crate::messages::dialog::simple_dialogs;
@@ -12,6 +12,7 @@ use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::DocumentMessageContext; use crate::messages::portfolio::document::DocumentMessageContext;
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::node_graph::document_node_definitions; use crate::messages::portfolio::document::node_graph::document_node_definitions;
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT}; use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
use crate::messages::portfolio::document::utility_types::network_interface::OutputConnector; use crate::messages::portfolio::document::utility_types::network_interface::OutputConnector;
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes; use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
@@ -19,13 +20,16 @@ use crate::messages::portfolio::document_migration::*;
use crate::messages::preferences::SelectionMode; use crate::messages::preferences::SelectionMode;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::utility_functions::make_path_editable_is_allowed;
use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType}; use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType};
use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor}; use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor};
use bezier_rs::BezierHandles;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use graph_craft::document::NodeId; use graph_craft::document::NodeId;
use graph_craft::document::value::TaggedValue; use graphene_std::Color;
use graphene_std::renderer::Quad; use graphene_std::renderer::Quad;
use graphene_std::text::Font; use graphene_std::text::Font;
use graphene_std::vector::{HandleId, PointId, SegmentId, VectorData, VectorModificationType};
use std::vec; use std::vec;
#[derive(ExtractField)] #[derive(ExtractField)]
@@ -50,7 +54,7 @@ pub struct PortfolioMessageHandler {
pub persistent_data: PersistentData, pub persistent_data: PersistentData,
pub executor: NodeGraphExecutor, pub executor: NodeGraphExecutor,
pub selection_mode: SelectionMode, pub selection_mode: SelectionMode,
/// The spreadsheet UI allows for instance data to be previewed. /// The spreadsheet UI allows for graph data to be previewed.
pub spreadsheet: SpreadsheetMessageHandler, pub spreadsheet: SpreadsheetMessageHandler,
device_pixel_ratio: Option<f64>, device_pixel_ratio: Option<f64>,
pub reset_node_definitions_on_open: bool, pub reset_node_definitions_on_open: bool,
@@ -80,7 +84,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
self.menu_bar_message_handler.has_selected_nodes = false; self.menu_bar_message_handler.has_selected_nodes = false;
self.menu_bar_message_handler.has_selected_layers = false; self.menu_bar_message_handler.has_selected_layers = false;
self.menu_bar_message_handler.has_selection_history = (false, false); self.menu_bar_message_handler.has_selection_history = (false, false);
self.menu_bar_message_handler.single_path_node_compatible_layer_selected = false; self.menu_bar_message_handler.make_path_editable_is_allowed = false;
self.menu_bar_message_handler.spreadsheet_view_open = self.spreadsheet.spreadsheet_view_open; self.menu_bar_message_handler.spreadsheet_view_open = self.spreadsheet.spreadsheet_view_open;
self.menu_bar_message_handler.message_logging_verbosity = message_logging_verbosity; self.menu_bar_message_handler.message_logging_verbosity = message_logging_verbosity;
self.menu_bar_message_handler.reset_node_definitions_on_open = reset_node_definitions_on_open; self.menu_bar_message_handler.reset_node_definitions_on_open = reset_node_definitions_on_open;
@@ -98,30 +102,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
let metadata = &document.network_interface.document_network_metadata().persistent_metadata; let metadata = &document.network_interface.document_network_metadata().persistent_metadata;
(!metadata.selection_undo_history.is_empty(), !metadata.selection_redo_history.is_empty()) (!metadata.selection_undo_history.is_empty(), !metadata.selection_redo_history.is_empty())
}; };
self.menu_bar_message_handler.single_path_node_compatible_layer_selected = { self.menu_bar_message_handler.make_path_editable_is_allowed = make_path_editable_is_allowed(&document.network_interface, document.metadata()).is_some();
let selected_nodes = document.network_interface.selected_nodes();
let mut selected_layers = selected_nodes.selected_layers(document.metadata());
let first_layer = selected_layers.next();
let second_layer = selected_layers.next();
let has_single_selection = first_layer.is_some() && second_layer.is_none();
let compatible_type = first_layer.and_then(|layer| {
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &document.network_interface);
graph_layer.horizontal_layer_flow().nth(1).map(|node_id| {
let (output_type, _) = document.network_interface.output_type(&node_id, 0, &[]);
format!("type:{}", output_type.nested_type())
})
});
let is_compatible = compatible_type.as_deref() == Some("type:Instances<VectorData>");
let is_modifiable = first_layer.is_some_and(|layer| {
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &document.network_interface);
matches!(graph_layer.find_input("Path", 1), Some(TaggedValue::VectorModification(_)))
});
first_layer.is_some() && has_single_selection && is_compatible && !is_modifiable
}
} }
self.menu_bar_message_handler.process_message(message, responses, ()); self.menu_bar_message_handler.process_message(message, responses, ());
@@ -576,6 +557,99 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
} }
} }
} }
// Custom paste implementation for Path tool
PortfolioMessage::PasteSerializedVector { data } => {
// If using Path tool then send the operation to Path tool
if *current_tool == ToolType::Path {
responses.add(PathToolMessage::Paste { data });
return;
}
// If not using Path tool, create new layers and add paths into those
if let Some(document) = self.active_document() {
let Ok(data) = serde_json::from_str::<Vec<(LayerNodeIdentifier, VectorData, DAffine2)>>(&data) else {
return;
};
let mut layers = Vec::new();
for (_, new_vector, transform) in data {
let Some(node_type) = resolve_document_node_type("Path") else {
error!("Path node does not exist");
continue;
};
let nodes = vec![(NodeId(0), node_type.default_node_template())];
let parent = document.new_layer_parent(false);
let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, parent, responses);
layers.push(layer);
// Adding the transform back into the layer
responses.add(GraphOperationMessage::TransformSet {
layer,
transform,
transform_in: TransformIn::Local,
skip_rerender: false,
});
// Add default fill and stroke to the layer
let fill_color = Color::WHITE;
let stroke_color = Color::BLACK;
let fill = graphene_std::vector::style::Fill::solid(fill_color.to_gamma_srgb());
responses.add(GraphOperationMessage::FillSet { layer, fill });
let stroke = graphene_std::vector::style::Stroke::new(Some(stroke_color.to_gamma_srgb()), DEFAULT_STROKE_WIDTH);
responses.add(GraphOperationMessage::StrokeSet { layer, stroke });
// Create new point ids and add those into the existing vector data
let mut points_map = HashMap::new();
for (point, position) in new_vector.point_domain.iter() {
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 data
let mut segments_map = HashMap::new();
for (segment_id, bezier, start, end) in new_vector.segment_bezier_iter() {
let new_segment_id = SegmentId::generate();
segments_map.insert(segment_id, new_segment_id);
let handles = match bezier.handles {
BezierHandles::Linear => [None, None],
BezierHandles::Quadratic { handle } => [Some(handle - bezier.start), None],
BezierHandles::Cubic { handle_start, handle_end } => [Some(handle_start - bezier.start), Some(handle_end - bezier.end)],
};
let points = [points_map[&start], points_map[&end]];
let modification_type = VectorModificationType::InsertSegment { id: new_segment_id, points, handles };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
// Set G1 continuity
for handles in new_vector.colinear_manipulators {
let to_new_handle = |handle: HandleId| -> HandleId {
HandleId {
ty: handle.ty,
segment: segments_map[&handle.segment],
}
};
let new_handles = [to_new_handle(handles[0]), to_new_handle(handles[1])];
let modification_type = VectorModificationType::SetG1Continuous { handles: new_handles, enabled: true };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
}
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(Message::Defer(DeferMessage::AfterGraphRun {
messages: vec![PortfolioMessage::CenterPastedLayers { layers }.into()],
}));
}
}
PortfolioMessage::CenterPastedLayers { layers } => { PortfolioMessage::CenterPastedLayers { layers } => {
if let Some(document) = self.active_document_mut() { if let Some(document) = self.active_document_mut() {
let viewport_bounds_quad_pixels = Quad::from_box([DVec2::ZERO, ipp.viewport_bounds.size()]); let viewport_bounds_quad_pixels = Quad::from_box([DVec2::ZERO, ipp.viewport_bounds.size()]);
@@ -888,7 +962,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
responses.add(FrontendMessage::UpdateOpenDocumentsList { open_documents }); responses.add(FrontendMessage::UpdateOpenDocumentsList { open_documents });
} }
PortfolioMessage::UpdateVelloPreference => { PortfolioMessage::UpdateVelloPreference => {
let active = if cfg!(target_arch = "wasm32") { false } else { preferences.use_vello }; let active = if cfg!(target_family = "wasm") { false } else { preferences.use_vello };
responses.add(FrontendMessage::UpdateViewportHolePunch { active }); responses.add(FrontendMessage::UpdateViewportHolePunch { active });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(NodeGraphMessage::RunDocumentGraph);
self.persistent_data.use_vello = preferences.use_vello; self.persistent_data.use_vello = preferences.use_vello;
@@ -1,7 +1,7 @@
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::node_graph_executor::InspectResult; use crate::node_graph_executor::InspectResult;
/// The spreadsheet UI allows for instance data to be previewed. /// The spreadsheet UI allows for graph data to be previewed.
#[impl_message(Message, PortfolioMessage, Spreadsheet)] #[impl_message(Message, PortfolioMessage, Spreadsheet)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum SpreadsheetMessage { pub enum SpreadsheetMessage {
@@ -12,10 +12,10 @@ pub enum SpreadsheetMessage {
inspect_result: InspectResult, inspect_result: InspectResult,
}, },
PushToInstancePath { PushToElementPath {
index: usize, index: usize,
}, },
TruncateInstancePath { TruncateElementPath {
len: usize, len: usize,
}, },
@@ -5,23 +5,22 @@ use crate::messages::tool::tool_messages::tool_prelude::*;
use graph_craft::document::NodeId; use graph_craft::document::NodeId;
use graphene_std::Color; use graphene_std::Color;
use graphene_std::Context; use graphene_std::Context;
use graphene_std::GraphicGroupTable;
use graphene_std::instances::Instances;
use graphene_std::memo::IORecord; use graphene_std::memo::IORecord;
use graphene_std::raster::Image; use graphene_std::raster::Image;
use graphene_std::vector::{VectorData, VectorDataTable}; use graphene_std::table::Table;
use graphene_std::{Artboard, ArtboardGroupTable, GraphicElement}; use graphene_std::vector::VectorData;
use graphene_std::{Artboard, Graphic};
use std::any::Any; use std::any::Any;
use std::sync::Arc; use std::sync::Arc;
/// The spreadsheet UI allows for instance data to be previewed. /// The spreadsheet UI allows for graph data to be previewed.
#[derive(Default, Debug, Clone, ExtractField)] #[derive(Default, Debug, Clone, ExtractField)]
pub struct SpreadsheetMessageHandler { pub struct SpreadsheetMessageHandler {
/// Sets whether or not the spreadsheet is drawn. /// Sets whether or not the spreadsheet is drawn.
pub spreadsheet_view_open: bool, pub spreadsheet_view_open: bool,
inspect_node: Option<NodeId>, inspect_node: Option<NodeId>,
introspected_data: Option<Arc<dyn Any + Send + Sync>>, introspected_data: Option<Arc<dyn Any + Send + Sync>>,
instances_path: Vec<usize>, element_path: Vec<usize>,
viewing_vector_data_domain: VectorDataDomain, viewing_vector_data_domain: VectorDataDomain,
} }
@@ -46,12 +45,12 @@ impl MessageHandler<SpreadsheetMessage, ()> for SpreadsheetMessageHandler {
self.update_layout(responses) self.update_layout(responses)
} }
SpreadsheetMessage::PushToInstancePath { index } => { SpreadsheetMessage::PushToElementPath { index } => {
self.instances_path.push(index); self.element_path.push(index);
self.update_layout(responses); self.update_layout(responses);
} }
SpreadsheetMessage::TruncateInstancePath { len } => { SpreadsheetMessage::TruncateElementPath { len } => {
self.instances_path.truncate(len); self.element_path.truncate(len);
self.update_layout(responses); self.update_layout(responses);
} }
@@ -78,7 +77,7 @@ impl SpreadsheetMessageHandler {
} }
let mut layout_data = LayoutData { let mut layout_data = LayoutData {
current_depth: 0, current_depth: 0,
desired_path: &mut self.instances_path, desired_path: &mut self.element_path,
breadcrumbs: Vec::new(), breadcrumbs: Vec::new(),
vector_data_domain: self.viewing_vector_data_domain, vector_data_domain: self.viewing_vector_data_domain,
}; };
@@ -91,7 +90,7 @@ impl SpreadsheetMessageHandler {
if layout_data.breadcrumbs.len() > 1 { if layout_data.breadcrumbs.len() > 1 {
let breadcrumb = BreadcrumbTrailButtons::new(layout_data.breadcrumbs) let breadcrumb = BreadcrumbTrailButtons::new(layout_data.breadcrumbs)
.on_update(|&len| SpreadsheetMessage::TruncateInstancePath { len: len as usize }.into()) .on_update(|&len| SpreadsheetMessage::TruncateElementPath { len: len as usize }.into())
.widget_holder(); .widget_holder();
layout.insert(0, LayoutGroup::Row { widgets: vec![breadcrumb] }); layout.insert(0, LayoutGroup::Row { widgets: vec![breadcrumb] });
} }
@@ -113,17 +112,17 @@ struct LayoutData<'a> {
fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'static>, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> { fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'static>, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> {
// We simply try random types. TODO: better strategy. // We simply try random types. TODO: better strategy.
#[allow(clippy::manual_map)] #[allow(clippy::manual_map)]
if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, ArtboardGroupTable>>() { if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Artboard>>>() {
Some(io.output.layout_with_breadcrumb(data)) Some(io.output.layout_with_breadcrumb(data))
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), ArtboardGroupTable>>() { } else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), Table<Artboard>>>() {
Some(io.output.layout_with_breadcrumb(data)) Some(io.output.layout_with_breadcrumb(data))
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, VectorDataTable>>() { } else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<VectorData>>>() {
Some(io.output.layout_with_breadcrumb(data)) Some(io.output.layout_with_breadcrumb(data))
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), VectorDataTable>>() { } else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), Table<VectorData>>>() {
Some(io.output.layout_with_breadcrumb(data)) Some(io.output.layout_with_breadcrumb(data))
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, GraphicGroupTable>>() { } else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Graphic>>>() {
Some(io.output.layout_with_breadcrumb(data)) Some(io.output.layout_with_breadcrumb(data))
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), GraphicGroupTable>>() { } else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), Table<Graphic>>>() {
Some(io.output.layout_with_breadcrumb(data)) Some(io.output.layout_with_breadcrumb(data))
} else { } else {
None None
@@ -139,7 +138,7 @@ fn label(x: impl Into<String>) -> Vec<LayoutGroup> {
vec![LayoutGroup::Row { widgets: error }] vec![LayoutGroup::Row { widgets: error }]
} }
trait InstanceLayout { trait TableRowLayout {
fn type_name() -> &'static str; fn type_name() -> &'static str;
fn identifier(&self) -> String; fn identifier(&self) -> String;
fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> { fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
@@ -149,33 +148,33 @@ trait InstanceLayout {
fn compute_layout(&self, data: &mut LayoutData) -> Vec<LayoutGroup>; fn compute_layout(&self, data: &mut LayoutData) -> Vec<LayoutGroup>;
} }
impl InstanceLayout for GraphicElement { impl TableRowLayout for Graphic {
fn type_name() -> &'static str { fn type_name() -> &'static str {
"GraphicElement" "Graphic"
} }
fn identifier(&self) -> String { fn identifier(&self) -> String {
match self { match self {
Self::GraphicGroup(instances) => instances.identifier(), Self::GraphicGroup(table) => table.identifier(),
Self::VectorData(instances) => instances.identifier(), Self::VectorData(table) => table.identifier(),
Self::RasterDataCPU(_) => "RasterDataCPU".to_string(), Self::RasterDataCPU(_) => "RasterDataCPU".to_string(),
Self::RasterDataGPU(_) => "RasterDataGPU".to_string(), Self::RasterDataGPU(_) => "RasterDataGPU".to_string(),
} }
} }
// Don't put a breadcrumb for GraphicElement // Don't put a breadcrumb for Graphic
fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> { fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
self.compute_layout(data) self.compute_layout(data)
} }
fn compute_layout(&self, data: &mut LayoutData) -> Vec<LayoutGroup> { fn compute_layout(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
match self { match self {
Self::GraphicGroup(instances) => instances.layout_with_breadcrumb(data), Self::GraphicGroup(table) => table.layout_with_breadcrumb(data),
Self::VectorData(instances) => instances.layout_with_breadcrumb(data), Self::VectorData(table) => table.layout_with_breadcrumb(data),
Self::RasterDataCPU(_) => label("Raster frame not supported"), Self::RasterDataCPU(_) => label("Raster is not supported"),
Self::RasterDataGPU(_) => label("Raster frame not supported"), Self::RasterDataGPU(_) => label("Raster is not supported"),
} }
} }
} }
impl InstanceLayout for VectorData { impl TableRowLayout for VectorData {
fn type_name() -> &'static str { fn type_name() -> &'static str {
"VectorData" "VectorData"
} }
@@ -243,7 +242,7 @@ impl InstanceLayout for VectorData {
} }
} }
impl InstanceLayout for Image<Color> { impl TableRowLayout for Image<Color> {
fn type_name() -> &'static str { fn type_name() -> &'static str {
"Image" "Image"
} }
@@ -256,7 +255,7 @@ impl InstanceLayout for Image<Color> {
} }
} }
impl InstanceLayout for Artboard { impl TableRowLayout for Artboard {
fn type_name() -> &'static str { fn type_name() -> &'static str {
"Artboard" "Artboard"
} }
@@ -268,18 +267,18 @@ impl InstanceLayout for Artboard {
} }
} }
impl<T: InstanceLayout> InstanceLayout for Instances<T> { impl<T: TableRowLayout> TableRowLayout for Table<T> {
fn type_name() -> &'static str { fn type_name() -> &'static str {
"Instances" "Table"
} }
fn identifier(&self) -> String { fn identifier(&self) -> String {
format!("Instances<{}> (length={})", T::type_name(), self.len()) format!("Table<{}> (length={})", T::type_name(), self.len())
} }
fn compute_layout(&self, data: &mut LayoutData) -> Vec<LayoutGroup> { fn compute_layout(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
if let Some(index) = data.desired_path.get(data.current_depth).copied() { if let Some(index) = data.desired_path.get(data.current_depth).copied() {
if let Some(instance) = self.get(index) { if let Some(row) = self.get(index) {
data.current_depth += 1; data.current_depth += 1;
let result = instance.instance.layout_with_breadcrumb(data); let result = row.element.layout_with_breadcrumb(data);
data.current_depth -= 1; data.current_depth -= 1;
return result; return result;
} else { } else {
@@ -289,16 +288,16 @@ impl<T: InstanceLayout> InstanceLayout for Instances<T> {
} }
let mut rows = self let mut rows = self
.instance_ref_iter() .iter_ref()
.enumerate() .enumerate()
.map(|(index, instance)| { .map(|(index, row)| {
let (scale, angle, translation) = instance.transform.to_scale_angle_translation(); let (scale, angle, translation) = row.transform.to_scale_angle_translation();
let rotation = if angle == -0. { 0. } else { angle.to_degrees() }; let rotation = if angle == -0. { 0. } else { angle.to_degrees() };
let round = |x: f64| (x * 1e3).round() / 1e3; let round = |x: f64| (x * 1e3).round() / 1e3;
vec![ vec![
TextLabel::new(format!("{}", index)).widget_holder(), TextLabel::new(format!("{}", index)).widget_holder(),
TextButton::new(instance.instance.identifier()) TextButton::new(row.element.identifier())
.on_update(move |_| SpreadsheetMessage::PushToInstancePath { index }.into()) .on_update(move |_| SpreadsheetMessage::PushToElementPath { index }.into())
.widget_holder(), .widget_holder(),
TextLabel::new(format!( TextLabel::new(format!(
"Location: ({} px, {} px) — Rotation: {rotation:2}° — Scale: ({}x, {}x)", "Location: ({} px, {} px) — Rotation: {rotation:2}° — Scale: ({}x, {}x)",
@@ -308,15 +307,15 @@ impl<T: InstanceLayout> InstanceLayout for Instances<T> {
round(scale.y) round(scale.y)
)) ))
.widget_holder(), .widget_holder(),
TextLabel::new(format!("{}", instance.alpha_blending)).widget_holder(), TextLabel::new(format!("{}", row.alpha_blending)).widget_holder(),
TextLabel::new(instance.source_node_id.map_or_else(|| "-".to_string(), |id| format!("{}", id.0))).widget_holder(), TextLabel::new(row.source_node_id.map_or_else(|| "-".to_string(), |id| format!("{}", id.0))).widget_holder(),
] ]
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
rows.insert(0, column_headings(&["", "instance", "transform", "alpha_blending", "source_node_id"])); rows.insert(0, column_headings(&["", "element", "transform", "alpha_blending", "source_node_id"]));
let instances = vec![TextLabel::new("Instances:").widget_holder()]; let table = vec![TextLabel::new("Table:").widget_holder()];
vec![LayoutGroup::Row { widgets: instances }, LayoutGroup::Table { rows }] vec![LayoutGroup::Row { widgets: table }, LayoutGroup::Table { rows }]
} }
} }
@@ -118,16 +118,19 @@ impl RadiusHandle {
let viewport = document.metadata().transform_to_viewport(layer); let viewport = document.metadata().transform_to_viewport(layer);
let center = viewport.transform_point2(DVec2::ZERO); let center = viewport.transform_point2(DVec2::ZERO);
let start_point = viewport.transform_point2(calculate_circle_point_position(0., radius)).distance(center); let x_point = viewport.transform_point2(calculate_circle_point_position(0., radius));
let end_point = viewport.transform_point2(calculate_circle_point_position(FRAC_PI_2, radius)).distance(center); let y_point = viewport.transform_point2(calculate_circle_point_position(FRAC_PI_2, radius));
let direction_x = viewport.transform_vector2(DVec2::X);
let direction_y = viewport.transform_vector2(-DVec2::Y);
if let Some(stroke_width) = get_stroke_width(layer, &document.network_interface) { if let Some(stroke_width) = get_stroke_width(layer, &document.network_interface) {
let spacing = Self::calculate_extra_spacing(viewport, radius, center, stroke_width, 15.); let spacing = Self::calculate_extra_spacing(viewport, radius, center, stroke_width, 15.);
let smaller_radius_x = (start_point - spacing).abs(); let smaller_radius_x = (x_point - direction_x * spacing).distance(center);
let smaller_radius_y = (end_point - spacing).abs(); let smaller_radius_y = (y_point - direction_y * spacing).distance(center);
let larger_radius_x = (start_point + spacing).abs(); let larger_radius_x = (x_point + direction_x * spacing).distance(center);
let larger_radius_y = (end_point + spacing).abs(); let larger_radius_y = (y_point + direction_y * spacing).distance(center);
overlay_context.dashed_ellipse(center, smaller_radius_x, smaller_radius_y, None, None, None, None, None, None, Some(4.), Some(4.), Some(0.5)); overlay_context.dashed_ellipse(center, smaller_radius_x, smaller_radius_y, None, None, None, None, None, None, Some(4.), Some(4.), Some(0.5));
overlay_context.dashed_ellipse(center, larger_radius_x, larger_radius_y, None, None, None, None, None, None, Some(4.), Some(4.), Some(0.5)); overlay_context.dashed_ellipse(center, larger_radius_x, larger_radius_y, None, None, None, None, None, None, Some(4.), Some(4.), Some(0.5));
@@ -135,7 +138,9 @@ impl RadiusHandle {
return; return;
} }
overlay_context.dashed_ellipse(center, start_point, end_point, None, None, None, None, None, None, Some(4.), Some(4.), Some(0.5)); let radius_x = x_point.distance(center);
let radius_y = y_point.distance(center);
overlay_context.dashed_ellipse(center, radius_x, radius_y, None, None, None, None, None, None, Some(4.), Some(4.), Some(0.5));
} }
} }
} }
@@ -153,7 +158,7 @@ impl RadiusHandle {
let center = viewport_transform.transform_point2(DVec2::ZERO); let center = viewport_transform.transform_point2(DVec2::ZERO);
let delta_vector = viewport_transform.inverse().transform_point2(input.mouse.position) - viewport_transform.inverse().transform_point2(self.previous_mouse_position); let delta_vector = viewport_transform.inverse().transform_point2(input.mouse.position) - viewport_transform.inverse().transform_point2(self.previous_mouse_position);
let radius = document.metadata().document_to_viewport.transform_point2(drag_start) - center; let radius = drag_start - center;
let sign = radius.dot(delta_vector).signum(); let sign = radius.dot(delta_vector).signum();
let net_delta = delta_vector.length() * sign * self.initial_radius.signum(); let net_delta = delta_vector.length() * sign * self.initial_radius.signum();
@@ -189,8 +189,8 @@ impl NumberOfPointsDial {
} }
pub fn update_number_of_sides(&self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>, drag_start: DVec2) { pub fn update_number_of_sides(&self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>, drag_start: DVec2) {
let delta = input.mouse.position - document.metadata().document_to_viewport.transform_point2(drag_start); let delta = input.mouse.position - drag_start;
let sign = (input.mouse.position.x - document.metadata().document_to_viewport.transform_point2(drag_start).x).signum(); let sign = (input.mouse.position.x - drag_start.x).signum();
let net_delta = (delta.length() / 25.).round() * sign; let net_delta = (delta.length() / 25.).round() * sign;
let Some(layer) = self.layer else { return }; let Some(layer) = self.layer else { return };
@@ -142,14 +142,7 @@ impl PointRadiusHandle {
} }
} }
pub fn overlays( pub fn overlays(&self, selected_star_layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, overlay_context: &mut OverlayContext) {
&self,
selected_star_layer: Option<LayerNodeIdentifier>,
document: &DocumentMessageHandler,
input: &InputPreprocessorMessageHandler,
mouse_position: DVec2,
overlay_context: &mut OverlayContext,
) {
match &self.handle_state { match &self.handle_state {
PointRadiusHandleState::Inactive => { PointRadiusHandleState::Inactive => {
let Some(layer) = selected_star_layer else { return }; let Some(layer) = selected_star_layer else { return };
@@ -161,25 +154,12 @@ impl PointRadiusHandle {
for i in 0..(2 * sides) { for i in 0..(2 * sides) {
let point = star_vertex_position(viewport, i as i32, sides, radius1, radius2); let point = star_vertex_position(viewport, i as i32, sides, radius1, radius2);
let center = viewport.transform_point2(DVec2::ZERO); let center = viewport.transform_point2(DVec2::ZERO);
let viewport_diagonal = input.viewport_bounds.size().length();
// If the user zooms out such that shape is very small hide the gizmo // If the user zooms out such that shape is very small hide the gizmo
if point.distance(center) < GIZMO_HIDE_THRESHOLD { if point.distance(center) < GIZMO_HIDE_THRESHOLD {
return; return;
} }
if point.distance(mouse_position) < 5. {
let Some(direction) = (point - center).try_normalize() else { continue };
overlay_context.manipulator_handle(point, true, None);
let angle = ((i as f64) * PI) / (sides as f64);
overlay_context.line(center, center + direction * viewport_diagonal, None, None);
draw_snapping_ticks(&self.snap_radii, direction, viewport, angle, overlay_context);
return;
}
overlay_context.manipulator_handle(point, false, None); overlay_context.manipulator_handle(point, false, None);
} }
} }
@@ -191,22 +171,12 @@ impl PointRadiusHandle {
for i in 0..sides { for i in 0..sides {
let point = polygon_vertex_position(viewport, i as i32, sides, radius); let point = polygon_vertex_position(viewport, i as i32, sides, radius);
let center = viewport.transform_point2(DVec2::ZERO); let center = viewport.transform_point2(DVec2::ZERO);
let viewport_diagonal = input.viewport_bounds.size().length();
// If the user zooms out such that shape is very small hide the gizmo // If the user zooms out such that shape is very small hide the gizmo
if point.distance(center) < GIZMO_HIDE_THRESHOLD { if point.distance(center) < GIZMO_HIDE_THRESHOLD {
return; return;
} }
if point.distance(mouse_position) < 5. {
let Some(direction) = (point - center).try_normalize() else { continue };
overlay_context.manipulator_handle(point, true, None);
overlay_context.line(center, center + direction * viewport_diagonal, None, None);
return;
}
overlay_context.manipulator_handle(point, false, None); overlay_context.manipulator_handle(point, false, None);
} }
} }
@@ -232,12 +202,9 @@ impl PointRadiusHandle {
star_outline(Some(layer), document, overlay_context); star_outline(Some(layer), document, overlay_context);
// Make the ticks for snapping // Make the ticks for snapping
if (radius1.signum() * radius2.signum()).is_sign_positive() {
// If dragging to make radius negative don't show the draw_snapping_ticks(&self.snap_radii, direction, viewport, angle, overlay_context);
if (mouse_position - center).dot(direction) < 0. {
return;
} }
draw_snapping_ticks(&self.snap_radii, direction, viewport, angle, overlay_context);
return; return;
} }
@@ -368,25 +335,36 @@ impl PointRadiusHandle {
return snap_radii; return snap_radii;
}; };
let other_index = if radius_index == 3 { 2 } else { 3 }; let (Some(&TaggedValue::F64(radius_1)), Some(&TaggedValue::F64(radius_2))) = (node_inputs[2].as_value(), node_inputs[3].as_value()) else {
let Some(&TaggedValue::F64(other_radius)) = node_inputs[other_index].as_value() else {
return snap_radii; return snap_radii;
}; };
let other_radius = if radius_index == 3 { radius_1 } else { radius_2 };
let Some(&TaggedValue::U32(sides)) = node_inputs[1].as_value() else { let Some(&TaggedValue::U32(sides)) = node_inputs[1].as_value() else {
return snap_radii; return snap_radii;
}; };
let both_radii_negative = radius_1.is_sign_negative() && radius_2.is_sign_negative();
let both_radii_same_sign = (radius_1.signum() * radius_2.signum()).is_sign_positive();
// When only one of the radii is negative, no need for snapping
if !both_radii_same_sign {
return snap_radii;
}
let sign = if both_radii_negative { -1. } else { 1. };
// Inner radius for 90° // Inner radius for 90°
let b = FRAC_PI_4 * 3. - PI / (sides as f64); let b = FRAC_PI_4 * 3. - PI / (sides as f64);
let angle = b.sin(); let angle = b.sin();
let required_radius = (other_radius / angle) * FRAC_1_SQRT_2; let required_radius = (other_radius.abs() * sign / angle) * FRAC_1_SQRT_2;
snap_radii.push(required_radius); snap_radii.push(required_radius);
// Also push the case when the when it length increases more than the other // Also push the case when the when it length increases more than the other
let flipped = other_radius * angle * SQRT_2; let flipped = other_radius.abs() * sign * angle * SQRT_2;
snap_radii.push(flipped); snap_radii.push(flipped);
@@ -401,11 +379,11 @@ impl PointRadiusHandle {
break; break;
} }
if other_radius * factor > 1e-6 { if other_radius.abs() * factor > 1e-6 {
snap_radii.push(other_radius * factor); snap_radii.push(other_radius.abs() * sign * factor);
} }
snap_radii.push((other_radius * 1.) / factor); snap_radii.push((other_radius.abs() * sign) / factor);
} }
snap_radii snap_radii
@@ -441,21 +419,23 @@ impl PointRadiusHandle {
}; };
let viewport_transform = document.network_interface.document_metadata().transform_to_viewport(layer); let viewport_transform = document.network_interface.document_metadata().transform_to_viewport(layer);
let document_transform = document.network_interface.document_metadata().transform_to_document(layer);
let center = viewport_transform.transform_point2(DVec2::ZERO); let center = viewport_transform.transform_point2(DVec2::ZERO);
let radius_index = self.radius_index; let radius_index = self.radius_index;
let original_radius = self.initial_radius; let original_radius = self.initial_radius;
let delta = viewport_transform.inverse().transform_point2(input.mouse.position) - document_transform.inverse().transform_point2(drag_start); let delta = viewport_transform.inverse().transform_point2(input.mouse.position) - viewport_transform.inverse().transform_point2(drag_start);
let radius = document.metadata().document_to_viewport.transform_point2(drag_start) - center; let radius = drag_start - center;
let projection = delta.project_onto(radius); let projection = delta.project_onto(radius);
let sign = radius.dot(delta).signum(); let sign = radius.dot(delta).signum();
let mut net_delta = projection.length() * sign; let mut net_delta = projection.length() * sign * original_radius.signum();
let new_radius = original_radius + net_delta; let new_radius = original_radius + net_delta;
self.update_state(PointRadiusHandleState::Dragging); self.update_state(PointRadiusHandleState::Dragging);
self.check_if_radius_flipped(original_radius, new_radius, document, layer, radius_index);
if let Some((index, snapped_delta)) = self.check_snapping(new_radius, original_radius) { if let Some((index, snapped_delta)) = self.check_snapping(new_radius, original_radius) {
net_delta = snapped_delta; net_delta = snapped_delta;
self.update_state(PointRadiusHandleState::Snapped(index)); self.update_state(PointRadiusHandleState::Snapped(index));
@@ -467,4 +447,23 @@ impl PointRadiusHandle {
}); });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(NodeGraphMessage::RunDocumentGraph);
} }
fn check_if_radius_flipped(&mut self, original_radius: f64, new_radius: f64, document: &DocumentMessageHandler, layer: LayerNodeIdentifier, radius_index: usize) {
let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star") else {
return;
};
let (Some(&TaggedValue::F64(radius_1)), Some(&TaggedValue::F64(radius_2))) = (node_inputs[2].as_value(), node_inputs[3].as_value()) else {
return;
};
let other_radius = if radius_index == 3 { radius_1 } else { radius_2 };
let flipped = (other_radius.is_sign_positive() && original_radius.is_sign_negative() && new_radius.is_sign_positive())
|| (other_radius.is_sign_negative() && original_radius.is_sign_positive() && new_radius.is_sign_negative());
if flipped {
self.snap_radii = Self::calculate_snap_radii(document, layer, radius_index);
}
}
} }
@@ -11,7 +11,8 @@ use graph_craft::{ProtoNodeIdentifier, concrete};
use graphene_std::Color; use graphene_std::Color;
use graphene_std::NodeInputDecleration; use graphene_std::NodeInputDecleration;
use graphene_std::raster::BlendMode; use graphene_std::raster::BlendMode;
use graphene_std::raster_types::{CPU, GPU, RasterDataTable}; use graphene_std::raster_types::{CPU, GPU, Raster};
use graphene_std::table::Table;
use graphene_std::text::{Font, TypesettingConfig}; use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::vector::style::Gradient; use graphene_std::vector::style::Gradient;
use graphene_std::vector::{ManipulatorPointId, PointId, SegmentId, VectorModificationType}; use graphene_std::vector::{ManipulatorPointId, PointId, SegmentId, VectorModificationType};
@@ -211,7 +212,7 @@ pub fn new_vector_layer(subpaths: Vec<Subpath<PointId>>, id: NodeId, parent: Lay
} }
/// Create a new bitmap layer. /// Create a new bitmap layer.
pub fn new_image_layer(image_frame: RasterDataTable<CPU>, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier { pub fn new_image_layer(image_frame: Table<Raster<CPU>>, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {
let insert_index = 0; let insert_index = 0;
responses.add(GraphOperationMessage::NewBitmapLayer { responses.add(GraphOperationMessage::NewBitmapLayer {
id, id,
@@ -433,6 +434,16 @@ impl<'a> NodeGraphLayer<'a> {
.find(|node_id| self.network_interface.reference(node_id, &[]).is_some_and(|reference| *reference == Some(node_name.to_string()))) .find(|node_id| self.network_interface.reference(node_id, &[]).is_some_and(|reference| *reference == Some(node_name.to_string())))
} }
/// Node id of a visible node if it exists in the layer's primary flow until another layer
pub fn upstream_visible_node_id_from_name_in_layer(&self, node_name: &str) -> Option<NodeId> {
// `.skip(1)` is used to skip self
self.horizontal_layer_flow()
.skip(1)
.take_while(|node_id| !self.network_interface.is_layer(node_id, &[]))
.filter(|node_id| self.network_interface.is_visible(node_id, &[]))
.find(|node_id| self.network_interface.reference(node_id, &[]).is_some_and(|reference| *reference == Some(node_name.to_string())))
}
/// Node id of a protonode if it exists in the layer's primary flow /// Node id of a protonode if it exists in the layer's primary flow
pub fn upstream_node_id_from_protonode(&self, protonode_identifier: ProtoNodeIdentifier) -> Option<NodeId> { pub fn upstream_node_id_from_protonode(&self, protonode_identifier: ProtoNodeIdentifier) -> Option<NodeId> {
self.horizontal_layer_flow() self.horizontal_layer_flow()
@@ -447,10 +458,11 @@ impl<'a> NodeGraphLayer<'a> {
/// Find all of the inputs of a specific node within the layer's primary flow, up until the next layer is reached. /// Find all of the inputs of a specific node within the layer's primary flow, up until the next layer is reached.
pub fn find_node_inputs(&self, node_name: &str) -> Option<&'a Vec<NodeInput>> { pub fn find_node_inputs(&self, node_name: &str) -> Option<&'a Vec<NodeInput>> {
// `.skip(1)` is used to skip self
self.horizontal_layer_flow() self.horizontal_layer_flow()
.skip(1)// Skip self .skip(1)
.take_while(|node_id| !self.network_interface.is_layer(node_id,&[])) .take_while(|node_id| !self.network_interface.is_layer(node_id, &[]))
.find(|node_id| self.network_interface.reference(node_id,&[]).is_some_and(|reference| *reference == Some(node_name.to_string()))) .find(|node_id| self.network_interface.reference(node_id, &[]).is_some_and(|reference| *reference == Some(node_name.to_string())))
.and_then(|node_id| self.network_interface.document_network().nodes.get(&node_id).map(|node| &node.inputs)) .and_then(|node_id| self.network_interface.document_network().nodes.get(&node_id).map(|node| &node.inputs))
} }
@@ -464,6 +476,6 @@ impl<'a> NodeGraphLayer<'a> {
pub fn is_raster_layer(layer: LayerNodeIdentifier, network_interface: &mut NodeNetworkInterface) -> bool { pub fn is_raster_layer(layer: LayerNodeIdentifier, network_interface: &mut NodeNetworkInterface) -> bool {
let layer_input_type = network_interface.input_type(&InputConnector::node(layer.to_node(), 1), &[]).0.nested_type().clone(); let layer_input_type = network_interface.input_type(&InputConnector::node(layer.to_node(), 1), &[]).0.nested_type().clone();
layer_input_type == concrete!(RasterDataTable<CPU>) || layer_input_type == concrete!(RasterDataTable<GPU>) layer_input_type == concrete!(Table<Raster<CPU>>) || layer_input_type == concrete!(Table<Raster<GPU>>)
} }
} }
@@ -57,6 +57,10 @@ pub struct SelectedLayerState {
} }
impl SelectedLayerState { impl SelectedLayerState {
pub fn is_empty(&self) -> bool {
self.selected_points.is_empty() && self.selected_segments.is_empty()
}
pub fn selected_points(&self) -> impl Iterator<Item = ManipulatorPointId> + '_ { pub fn selected_points(&self) -> impl Iterator<Item = ManipulatorPointId> + '_ {
self.selected_points.iter().copied() self.selected_points.iter().copied()
} }
@@ -67,7 +67,7 @@ impl ShapeGizmoHandler for PolygonGizmoHandler {
overlay_context: &mut OverlayContext, overlay_context: &mut OverlayContext,
) { ) {
self.number_of_points_dial.overlays(document, selected_polygon_layer, shape_editor, mouse_position, overlay_context); self.number_of_points_dial.overlays(document, selected_polygon_layer, shape_editor, mouse_position, overlay_context);
self.point_radius_handle.overlays(selected_polygon_layer, document, input, mouse_position, overlay_context); self.point_radius_handle.overlays(selected_polygon_layer, document, input, overlay_context);
polygon_outline(selected_polygon_layer, document, overlay_context); polygon_outline(selected_polygon_layer, document, overlay_context);
} }
@@ -85,7 +85,7 @@ impl ShapeGizmoHandler for PolygonGizmoHandler {
} }
if self.point_radius_handle.is_dragging_or_snapped() { if self.point_radius_handle.is_dragging_or_snapped() {
self.point_radius_handle.overlays(None, document, input, mouse_position, overlay_context); self.point_radius_handle.overlays(None, document, input, overlay_context);
} }
} }
@@ -64,7 +64,7 @@ impl ShapeGizmoHandler for StarGizmoHandler {
overlay_context: &mut OverlayContext, overlay_context: &mut OverlayContext,
) { ) {
self.number_of_points_dial.overlays(document, selected_star_layer, shape_editor, mouse_position, overlay_context); self.number_of_points_dial.overlays(document, selected_star_layer, shape_editor, mouse_position, overlay_context);
self.point_radius_handle.overlays(selected_star_layer, document, input, mouse_position, overlay_context); self.point_radius_handle.overlays(selected_star_layer, document, input, overlay_context);
star_outline(selected_star_layer, document, overlay_context); star_outline(selected_star_layer, document, overlay_context);
} }
@@ -82,7 +82,7 @@ impl ShapeGizmoHandler for StarGizmoHandler {
} }
if self.point_radius_handle.is_dragging_or_snapped() { if self.point_radius_handle.is_dragging_or_snapped() {
self.point_radius_handle.overlays(None, document, input, mouse_position, overlay_context); self.point_radius_handle.overlays(None, document, input, overlay_context);
} }
} }
@@ -1,18 +1,22 @@
use super::snapping::{SnapCandidatePoint, SnapData, SnapManager}; use super::snapping::{SnapCandidatePoint, SnapData, SnapManager};
use super::transformation_cage::{BoundingBoxManager, SizeSnapData}; use super::transformation_cage::{BoundingBoxManager, SizeSnapData};
use crate::consts::ROTATE_INCREMENT; use crate::consts::ROTATE_INCREMENT;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
use crate::messages::portfolio::document::utility_types::transformation::Selected; use crate::messages::portfolio::document::utility_types::transformation::Selected;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils::get_text; use crate::messages::tool::common_functionality::graph_modification_utils::{NodeGraphLayer, get_text};
use crate::messages::tool::common_functionality::transformation_cage::SelectedEdges; use crate::messages::tool::common_functionality::transformation_cage::SelectedEdges;
use crate::messages::tool::tool_messages::path_tool::PathOverlayMode; use crate::messages::tool::tool_messages::path_tool::PathOverlayMode;
use crate::messages::tool::utility_types::ToolType; use crate::messages::tool::utility_types::ToolType;
use bezier_rs::{Bezier, BezierHandles}; use bezier_rs::{Bezier, BezierHandles};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use graph_craft::concrete;
use graph_craft::document::value::TaggedValue;
use graphene_std::renderer::Quad; use graphene_std::renderer::Quad;
use graphene_std::table::Table;
use graphene_std::text::{FontCache, load_font}; use graphene_std::text::{FontCache, load_font};
use graphene_std::vector::{HandleExt, HandleId, ManipulatorPointId, PointId, SegmentId, VectorData, VectorModificationType}; use graphene_std::vector::{HandleExt, HandleId, ManipulatorPointId, PointId, SegmentId, VectorData, VectorModification, VectorModificationType};
use kurbo::{CubicBez, Line, ParamCurveExtrema, PathSeg, Point, QuadBez}; use kurbo::{CubicBez, Line, ParamCurveExtrema, PathSeg, Point, QuadBez};
/// Determines if a path should be extended. Goal in viewport space. Returns the path and if it is extending from the start, if applicable. /// Determines if a path should be extended. Goal in viewport space. Returns the path and if it is extending from the start, if applicable.
@@ -73,7 +77,7 @@ pub fn text_bounding_box(layer: LayerNodeIdentifier, document: &DocumentMessageH
let font_data = font_cache.get(font).map(|data| load_font(data)); let font_data = font_cache.get(font).map(|data| load_font(data));
let far = graphene_std::text::bounding_box(text, font_data, typesetting, false); let far = graphene_std::text::bounding_box(text, font_data, typesetting, false);
// TODO: Once the instances refactor is complete and per_glyph_instances can be removed (since it'll be the default), // TODO: Once the instance tables refactor is complete and per_glyph_instances can be removed (since it'll be the default),
// TODO: remove this because the top of the dashed bounding overlay should no longer be based on the first line's baseline. // TODO: remove this because the top of the dashed bounding overlay should no longer be based on the first line's baseline.
let vertical_offset = if per_glyph_instances { let vertical_offset = if per_glyph_instances {
DVec2::NEG_Y * typesetting.font_size * (1. + (typesetting.line_height_ratio - 1.) / 2.) DVec2::NEG_Y * typesetting.font_size * (1. + (typesetting.line_height_ratio - 1.) / 2.)
@@ -586,3 +590,36 @@ pub fn find_two_param_best_approximate(p1: DVec2, p3: DVec2, d1: DVec2, d2: DVec
(d1 * len1, d2 * len2) (d1 * len1, d2 * len2)
} }
pub fn make_path_editable_is_allowed(network_interface: &NodeNetworkInterface, metadata: &DocumentMetadata) -> Option<LayerNodeIdentifier> {
// Must have exactly one layer selected
let selected_nodes = network_interface.selected_nodes();
let mut selected_layers = selected_nodes.selected_layers(metadata);
let first_layer = selected_layers.next()?;
if selected_layers.next().is_some() {
return None;
}
// Must be a layer of type Table<VectorData>
let compatible_type = NodeGraphLayer::new(first_layer, network_interface)
.horizontal_layer_flow()
.nth(1)
.map(|node_id| {
let (output_type, _) = network_interface.output_type(&node_id, 0, &[]);
output_type.nested_type() == concrete!(Table<VectorData>).nested_type()
})
.unwrap_or_default();
if !compatible_type {
return None;
}
// Must not already have an existing Path node, in the right-most part of the layer chain, which has an empty set of modifications
// (otherwise users could repeatedly keep running this command and stacking up empty Path nodes)
if let Some(TaggedValue::VectorModification(modifications)) = NodeGraphLayer::new(first_layer, network_interface).find_input("Path", 1) {
if modifications.as_ref() == &VectorModification::default() {
return None;
}
}
Some(first_layer)
}
@@ -12,6 +12,7 @@ use crate::messages::tool::common_functionality::snapping::SnapManager;
use crate::messages::tool::common_functionality::transformation_cage::*; use crate::messages::tool::common_functionality::transformation_cage::*;
use graph_craft::document::NodeId; use graph_craft::document::NodeId;
use graphene_std::renderer::Quad; use graphene_std::renderer::Quad;
use graphene_std::table::Table;
#[derive(Default, ExtractField)] #[derive(Default, ExtractField)]
pub struct ArtboardTool { pub struct ArtboardTool {
@@ -337,7 +338,7 @@ impl Fsm for ArtboardToolFsmState {
responses.add(GraphOperationMessage::NewArtboard { responses.add(GraphOperationMessage::NewArtboard {
id, id,
artboard: graphene_std::Artboard { artboard: graphene_std::Artboard {
graphic_group: graphene_std::GraphicGroupTable::default(), graphic_group: Table::new(),
label: String::from("Artboard"), label: String::from("Artboard"),
location: start.min(end).round().as_ivec2(), location: start.min(end).round().as_ivec2(),
dimensions: (start.round() - end.round()).abs().as_ivec2(), dimensions: (start.round() - end.round()).abs().as_ivec2(),
@@ -568,7 +569,7 @@ mod test_artboard {
Ok(instrumented) => instrumented, Ok(instrumented) => instrumented,
Err(e) => panic!("Failed to evaluate graph: {}", e), Err(e) => panic!("Failed to evaluate graph: {}", e),
}; };
instrumented.grab_all_input::<graphene_std::graphic_element::append_artboard::ArtboardInput>(&editor.runtime).collect() instrumented.grab_all_input::<graphene_std::artboard::append_artboard::ArtboardInput>(&editor.runtime).collect()
} }
#[tokio::test] #[tokio::test]
@@ -356,7 +356,7 @@ fn extend_path_with_next_segment(tool_data: &mut FreehandToolData, position: DVe
mod test_freehand { mod test_freehand {
use crate::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, MouseKeys, ScrollDelta}; use crate::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, MouseKeys, ScrollDelta};
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::tool::common_functionality::graph_modification_utils::get_stroke_width; use crate::messages::tool::common_functionality::graph_modification_utils::{NodeGraphLayer, get_stroke_width};
use crate::messages::tool::tool_messages::freehand_tool::FreehandOptionsUpdate; use crate::messages::tool::tool_messages::freehand_tool::FreehandOptionsUpdate;
use crate::test_utils::test_prelude::*; use crate::test_utils::test_prelude::*;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
@@ -368,6 +368,10 @@ mod test_freehand {
layers layers
.filter_map(|layer| { .filter_map(|layer| {
let graph_layer = NodeGraphLayer::new(layer, &document.network_interface);
// Only get layers with path nodes
let _ = graph_layer.upstream_visible_node_id_from_name_in_layer("Path")?;
let vector_data = document.network_interface.compute_modified_vector(layer)?; let vector_data = document.network_interface.compute_modified_vector(layer)?;
let transform = document.metadata().transform_to_viewport(layer); let transform = document.metadata().transform_to_viewport(layer);
Some((vector_data, transform)) Some((vector_data, transform))
@@ -376,9 +380,7 @@ mod test_freehand {
} }
fn verify_path_points(vector_data_list: &[(VectorData, DAffine2)], expected_captured_points: &[DVec2], tolerance: f64) -> Result<(), String> { fn verify_path_points(vector_data_list: &[(VectorData, DAffine2)], expected_captured_points: &[DVec2], tolerance: f64) -> Result<(), String> {
if vector_data_list.len() == 0 { assert_eq!(vector_data_list.len(), 1, "there should be one vector data");
return Err("No vector data found after drawing".to_string());
}
let path_data = vector_data_list.iter().find(|(data, _)| data.point_domain.ids().len() > 0).ok_or("Could not find path data")?; let path_data = vector_data_list.iter().find(|(data, _)| data.point_domain.ids().len() > 0).ok_or("Could not find path data")?;
@@ -386,15 +388,7 @@ mod test_freehand {
let point_count = vector_data.point_domain.ids().len(); let point_count = vector_data.point_domain.ids().len();
let segment_count = vector_data.segment_domain.ids().len(); let segment_count = vector_data.segment_domain.ids().len();
let actual_positions: Vec<DVec2> = vector_data let actual_positions: Vec<DVec2> = vector_data.point_domain.positions().iter().map(|&position| transform.transform_point2(position)).collect();
.point_domain
.ids()
.iter()
.filter_map(|&point_id| {
let position = vector_data.point_domain.position_from_id(point_id)?;
Some(transform.transform_point2(position))
})
.collect();
if segment_count != point_count - 1 { if segment_count != point_count - 1 {
return Err(format!("Expected segments to be one less than points, got {} segments for {} points", segment_count, point_count)); return Err(format!("Expected segments to be one less than points, got {} segments for {} points", segment_count, point_count));
@@ -1,11 +1,14 @@
use super::select_tool::extend_lasso; use super::select_tool::extend_lasso;
use super::tool_prelude::*; use super::tool_prelude::*;
use crate::consts::{ use crate::consts::{
COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GRAY, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, DOUBLE_CLICK_MILLISECONDS, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, DRAG_THRESHOLD, DRILL_THROUGH_THRESHOLD, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GRAY, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, DEFAULT_STROKE_WIDTH, DOUBLE_CLICK_MILLISECONDS, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, DRAG_THRESHOLD,
HANDLE_ROTATE_SNAP_ANGLE, SEGMENT_INSERTION_DISTANCE, SEGMENT_OVERLAY_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE, DRILL_THROUGH_THRESHOLD, HANDLE_ROTATE_SNAP_ANGLE, SEGMENT_INSERTION_DISTANCE, SEGMENT_OVERLAY_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE,
}; };
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
use crate::messages::portfolio::document::overlays::utility_functions::{path_overlays, selected_segments}; use crate::messages::portfolio::document::overlays::utility_functions::{path_overlays, selected_segments};
use crate::messages::portfolio::document::overlays::utility_types::{DrawHandles, OverlayContext}; use crate::messages::portfolio::document::overlays::utility_types::{DrawHandles, OverlayContext};
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier}; use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface; use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
use crate::messages::portfolio::document::utility_types::transformation::Axis; use crate::messages::portfolio::document::utility_types::transformation::Axis;
@@ -17,11 +20,12 @@ use crate::messages::tool::common_functionality::shape_editor::{
ClosestSegment, ManipulatorAngle, OpposingHandleLengths, SelectedLayerState, SelectedPointsInfo, SelectionChange, SelectionShape, SelectionShapeType, ShapeState, ClosestSegment, ManipulatorAngle, OpposingHandleLengths, SelectedLayerState, SelectedPointsInfo, SelectionChange, SelectionShape, SelectionShapeType, ShapeState,
}; };
use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager}; use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager};
use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, find_two_param_best_approximate}; use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, find_two_param_best_approximate, make_path_editable_is_allowed};
use bezier_rs::{Bezier, BezierHandles, TValue}; use bezier_rs::{Bezier, BezierHandles, TValue};
use graph_craft::document::value::TaggedValue; use graphene_std::Color;
use graphene_std::renderer::Quad; use graphene_std::renderer::Quad;
use graphene_std::transform::ReferencePoint; use graphene_std::transform::ReferencePoint;
use graphene_std::uuid::NodeId;
use graphene_std::vector::click_target::ClickTargetType; use graphene_std::vector::click_target::ClickTargetType;
use graphene_std::vector::{HandleExt, HandleId, NoHashBuilder, SegmentId, VectorData}; use graphene_std::vector::{HandleExt, HandleId, NoHashBuilder, SegmentId, VectorData};
use graphene_std::vector::{ManipulatorPointId, PointId, VectorModificationType}; use graphene_std::vector::{ManipulatorPointId, PointId, VectorModificationType};
@@ -121,6 +125,17 @@ pub enum PathToolMessage {
UpdateSelectedPointsStatus { UpdateSelectedPointsStatus {
overlay_context: OverlayContext, overlay_context: OverlayContext,
}, },
Copy {
clipboard: Clipboard,
},
Cut {
clipboard: Clipboard,
},
Paste {
data: String,
},
DeleteSelected,
Duplicate,
TogglePointEditing, TogglePointEditing,
ToggleSegmentEditing, ToggleSegmentEditing,
} }
@@ -221,7 +236,7 @@ impl LayoutHolder for PathTool {
}) })
// TODO: Remove `unwrap_or_default` once checkboxes are capable of displaying a mixed state // TODO: Remove `unwrap_or_default` once checkboxes are capable of displaying a mixed state
.unwrap_or_default(); .unwrap_or_default();
let mut checkbox_id = CheckboxId::default(); let checkbox_id = CheckboxId::new();
let colinear_handle_checkbox = CheckboxInput::new(colinear_handles_state) let colinear_handle_checkbox = CheckboxInput::new(colinear_handles_state)
.disabled(!self.tool_data.can_toggle_colinearity) .disabled(!self.tool_data.can_toggle_colinearity)
.on_update(|&CheckboxInput { checked, .. }| { .on_update(|&CheckboxInput { checked, .. }| {
@@ -232,12 +247,12 @@ impl LayoutHolder for PathTool {
} }
}) })
.tooltip(colinear_handles_tooltip) .tooltip(colinear_handles_tooltip)
.for_label(checkbox_id.clone()) .for_label(checkbox_id)
.widget_holder(); .widget_holder();
let colinear_handles_label = TextLabel::new("Colinear Handles") let colinear_handles_label = TextLabel::new("Colinear Handles")
.disabled(!self.tool_data.can_toggle_colinearity) .disabled(!self.tool_data.can_toggle_colinearity)
.tooltip(colinear_handles_tooltip) .tooltip(colinear_handles_tooltip)
.for_checkbox(&mut checkbox_id) .for_checkbox(checkbox_id)
.widget_holder(); .widget_holder();
let point_editing_mode = CheckboxInput::new(self.options.path_editing_mode.point_editing_mode) let point_editing_mode = CheckboxInput::new(self.options.path_editing_mode.point_editing_mode)
@@ -275,7 +290,7 @@ impl LayoutHolder for PathTool {
.icon(Some("NodeShape".into())) .icon(Some("NodeShape".into()))
.tooltip("Make Path Editable") .tooltip("Make Path Editable")
.on_update(|_| NodeGraphMessage::AddPathNode.into()) .on_update(|_| NodeGraphMessage::AddPathNode.into())
.disabled(!self.tool_data.single_path_node_compatible_layer_selected) .disabled(!self.tool_data.make_path_editable_is_allowed)
.widget_holder(); .widget_holder();
let [_checkbox, _dropdown] = { let [_checkbox, _dropdown] = {
@@ -403,6 +418,11 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Path
DeleteAndBreakPath, DeleteAndBreakPath,
ClosePath, ClosePath,
PointerMove, PointerMove,
Copy,
Cut,
DeleteSelected,
Paste,
Duplicate,
TogglePointEditing, TogglePointEditing,
ToggleSegmentEditing ToggleSegmentEditing
), ),
@@ -416,6 +436,11 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Path
BreakPath, BreakPath,
DeleteAndBreakPath, DeleteAndBreakPath,
SwapSelectedHandles, SwapSelectedHandles,
Copy,
Cut,
DeleteSelected,
Paste,
Duplicate,
TogglePointEditing, TogglePointEditing,
ToggleSegmentEditing ToggleSegmentEditing
), ),
@@ -545,7 +570,7 @@ struct PathToolData {
drill_through_cycle_count: usize, drill_through_cycle_count: usize,
hovered_layers: Vec<LayerNodeIdentifier>, hovered_layers: Vec<LayerNodeIdentifier>,
ghost_outline: Vec<(Vec<ClickTargetType>, LayerNodeIdentifier)>, ghost_outline: Vec<(Vec<ClickTargetType>, LayerNodeIdentifier)>,
single_path_node_compatible_layer_selected: bool, make_path_editable_is_allowed: bool,
} }
impl PathToolData { impl PathToolData {
@@ -2255,7 +2280,7 @@ impl Fsm for PathToolFsmState {
shape_editor.deselect_all_segments(); shape_editor.deselect_all_segments();
for (layer, (selected_points, selected_segments)) in &tool_data.saved_selection_before_handle_drag { for (layer, (selected_points, selected_segments)) in &tool_data.saved_selection_before_handle_drag {
let Some(state) = shape_editor.selected_shape_state.get_mut(&layer) else { continue }; let Some(state) = shape_editor.selected_shape_state.get_mut(layer) else { continue };
selected_points.iter().for_each(|point| state.select_point(*point)); selected_points.iter().for_each(|point| state.select_point(*point));
selected_segments.iter().for_each(|segment| state.select_segment(*segment)); selected_segments.iter().for_each(|segment| state.select_segment(*segment));
} }
@@ -2374,7 +2399,7 @@ impl Fsm for PathToolFsmState {
let is_segment_selected = shape_editor let is_segment_selected = shape_editor
.selected_shape_state .selected_shape_state
.get(&segment.layer()) .get(&segment.layer())
.map_or(false, |state| state.is_segment_selected(segment.segment())); .is_some_and(|state| state.is_segment_selected(segment.segment()));
segment.adjusted_insert_and_select(shape_editor, responses, extend_selection, point_mode, is_segment_selected); segment.adjusted_insert_and_select(shape_editor, responses, extend_selection, point_mode, is_segment_selected);
tool_data.segment = None; tool_data.segment = None;
@@ -2485,7 +2510,7 @@ impl Fsm for PathToolFsmState {
shape_editor.deselect_all_segments(); shape_editor.deselect_all_segments();
for (layer, (selected_points, selected_segments)) in &tool_data.saved_selection_before_handle_drag { for (layer, (selected_points, selected_segments)) in &tool_data.saved_selection_before_handle_drag {
let Some(state) = shape_editor.selected_shape_state.get_mut(&layer) else { continue }; let Some(state) = shape_editor.selected_shape_state.get_mut(layer) else { continue };
selected_points.iter().for_each(|point| state.select_point(*point)); selected_points.iter().for_each(|point| state.select_point(*point));
selected_segments.iter().for_each(|segment| state.select_segment(*segment)); selected_segments.iter().for_each(|segment| state.select_segment(*segment));
} }
@@ -2545,6 +2570,300 @@ impl Fsm for PathToolFsmState {
shape_editor.delete_point_and_break_path(document, responses); shape_editor.delete_point_and_break_path(document, responses);
PathToolFsmState::Ready PathToolFsmState::Ready
} }
(_, PathToolMessage::Copy { clipboard }) => {
// TODO: Add support for selected segments
let mut buffer = Vec::new();
for (&layer, layer_selection_state) in &shape_editor.selected_shape_state {
if layer_selection_state.is_empty() {
continue;
}
let Some(old_vector_data) = document.network_interface.compute_modified_vector(layer) else {
continue;
};
// Also get the transform node that is applied on the layer if it exists
let transform = document.metadata().transform_to_document(layer);
let mut new_vector_data = VectorData::default();
let mut selected_points_by_segment = HashSet::new();
old_vector_data
.segment_bezier_iter()
.filter(|(segment, _, _, _)| layer_selection_state.is_segment_selected(*segment))
.for_each(|(_, _, start, end)| {
selected_points_by_segment.insert(start);
selected_points_by_segment.insert(end);
});
// Add all the selected points
for (point, position) in old_vector_data.point_domain.iter() {
if layer_selection_state.is_point_selected(ManipulatorPointId::Anchor(point)) || selected_points_by_segment.contains(&point) {
new_vector_data.point_domain.push(point, position);
}
}
let find_index = |id: PointId| new_vector_data.point_domain.iter().enumerate().find(|(_, (point_id, _))| *point_id == id).map(|(index, _)| index);
// Add segments which have selected ends
for ((segment_id, bezier, start, end), stroke) in old_vector_data.segment_bezier_iter().zip(old_vector_data.segment_domain.stroke().iter()) {
let both_ends_selected = layer_selection_state.is_point_selected(ManipulatorPointId::Anchor(start)) && layer_selection_state.is_point_selected(ManipulatorPointId::Anchor(end));
let segment_selected = layer_selection_state.is_segment_selected(segment_id);
if both_ends_selected || segment_selected {
let Some((start_index, end_index)) = find_index(start).zip(find_index(end)) else {
error!("Point does not exist in point domain");
return PathToolFsmState::Ready;
};
new_vector_data.segment_domain.push(segment_id, start_index, end_index, bezier.handles, *stroke);
}
}
for handles in old_vector_data.colinear_manipulators {
if new_vector_data.segment_domain.ids().contains(&handles[0].segment) && new_vector_data.segment_domain.ids().contains(&handles[1].segment) {
new_vector_data.colinear_manipulators.push(handles);
}
}
buffer.push((layer, new_vector_data, transform));
}
if clipboard == Clipboard::Device {
let mut copy_text = String::from("graphite/vector: ");
copy_text += &serde_json::to_string(&buffer).expect("Could not serialize paste");
responses.add(FrontendMessage::TriggerTextCopy { copy_text });
}
// TODO: Add implementation for internal clipboard
PathToolFsmState::Ready
}
(_, PathToolMessage::Cut { clipboard }) => {
responses.add(PathToolMessage::Copy { clipboard });
// Delete the selected points/segments
responses.add(PathToolMessage::DeleteSelected);
PathToolFsmState::Ready
}
(_, PathToolMessage::Paste { data }) => {
// Deserialize the data
if let Ok(data) = serde_json::from_str::<Vec<(LayerNodeIdentifier, VectorData, DAffine2)>>(&data) {
shape_editor.deselect_all_points();
responses.add(DocumentMessage::AddTransaction);
let mut new_layers = Vec::new();
for (layer, new_vector, transform) in data {
// If layer is not selected then create a new selected layer
let layer = if shape_editor.selected_shape_state.contains_key(&layer) {
layer
} else {
let Some(node_type) = resolve_document_node_type("Path") else {
error!("Could not resolve node type for Path");
continue;
};
let nodes = vec![(NodeId(0), node_type.default_node_template())];
let parent = document.new_layer_parent(false);
let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, parent, responses);
let fill_color = Color::WHITE;
let stroke_color = Color::BLACK;
let fill = graphene_std::vector::style::Fill::solid(fill_color.to_gamma_srgb());
responses.add(GraphOperationMessage::FillSet { layer, fill });
let stroke = graphene_std::vector::style::Stroke::new(Some(stroke_color.to_gamma_srgb()), DEFAULT_STROKE_WIDTH);
responses.add(GraphOperationMessage::StrokeSet { layer, stroke });
new_layers.push(layer);
responses.add(GraphOperationMessage::TransformSet {
layer,
transform,
transform_in: TransformIn::Local,
skip_rerender: false,
});
layer
};
// Create new point ids and add those into the existing vector data
let mut points_map = HashMap::new();
for (point, position) in new_vector.point_domain.iter() {
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 data
let mut segments_map = HashMap::new();
for (segment_id, bezier, start, end) in new_vector.segment_bezier_iter() {
let new_segment_id = SegmentId::generate();
segments_map.insert(segment_id, new_segment_id);
let handles = match bezier.handles {
BezierHandles::Linear => [None, None],
BezierHandles::Quadratic { handle } => [Some(handle - bezier.start), None],
BezierHandles::Cubic { handle_start, handle_end } => [Some(handle_start - bezier.start), Some(handle_end - bezier.end)],
};
let points = [points_map[&start], points_map[&end]];
let modification_type = VectorModificationType::InsertSegment { id: new_segment_id, points, handles };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
// Set G1 continuity
for handles in new_vector.colinear_manipulators {
let to_new_handle = |handle: HandleId| -> HandleId {
HandleId {
ty: handle.ty,
segment: segments_map[&handle.segment],
}
};
let new_handles = [to_new_handle(handles[0]), to_new_handle(handles[1])];
let modification_type = VectorModificationType::SetG1Continuous { handles: new_handles, enabled: true };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
shape_editor.selected_shape_state.entry(layer).or_insert(Default::default());
// Set selection to newly inserted points
let Some(state) = shape_editor.selected_shape_state.get_mut(&layer) else {
error!("No state for layer: {layer:?}");
continue;
};
// If point editing mode is enabled, select all the pasted points
if tool_options.path_editing_mode.point_editing_mode {
points_map.values().for_each(|point| state.select_point(ManipulatorPointId::Anchor(*point)));
}
// If segment editing mode is enabled, select all the pasted segments
if tool_options.path_editing_mode.segment_editing_mode {
segments_map.values().for_each(|segment| state.select_segment(*segment));
}
}
// If there are new layers created, we need to center them in the viewport
if !new_layers.is_empty() {
responses.add(Message::Defer(DeferMessage::AfterGraphRun {
messages: vec![PortfolioMessage::CenterPastedLayers { layers: new_layers }.into()],
}));
}
}
PathToolFsmState::Ready
}
(_, PathToolMessage::DeleteSelected) => {
// Delete the selected points and segments
shape_editor.delete_point_and_break_path(document, responses);
shape_editor.delete_selected_segments(document, responses);
PathToolFsmState::Ready
}
(_, PathToolMessage::Duplicate) => {
responses.add(DocumentMessage::AddTransaction);
// Copy the existing selected geometry and paste it in the existing layers
for (layer, layer_selection_state) in shape_editor.selected_shape_state.clone() {
if layer_selection_state.is_empty() {
continue;
}
let Some(old_vector_data) = document.network_interface.compute_modified_vector(layer) else {
continue;
};
// Add all the selected points
let mut selected_points_by_segment = HashSet::new();
old_vector_data
.segment_bezier_iter()
.filter(|(segment, _, _, _)| layer_selection_state.is_segment_selected(*segment))
.for_each(|(_, _, start, end)| {
selected_points_by_segment.insert(start);
selected_points_by_segment.insert(end);
});
let mut points_map = HashMap::new();
for (point, position) in old_vector_data.point_domain.iter() {
// TODO: Either the point is selected or it is an endpoint of a selected segment
if layer_selection_state.is_point_selected(ManipulatorPointId::Anchor(point)) || selected_points_by_segment.contains(&point) {
// Insert the same point with a new id
let new_id = PointId::generate();
points_map.insert(point, new_id);
let modification_type = VectorModificationType::InsertPoint { id: new_id, position };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
}
let mut segments_map = HashMap::new();
for (segment_id, bezier, start, end) in old_vector_data.segment_bezier_iter() {
let both_ends_selected = layer_selection_state.is_point_selected(ManipulatorPointId::Anchor(start)) && layer_selection_state.is_point_selected(ManipulatorPointId::Anchor(end));
let segment_selected = layer_selection_state.is_segment_selected(segment_id);
if both_ends_selected || segment_selected {
let new_id = SegmentId::generate();
segments_map.insert(segment_id, new_id);
let handles = match bezier.handles {
BezierHandles::Linear => [None, None],
BezierHandles::Quadratic { handle } => [Some(handle - bezier.start), None],
BezierHandles::Cubic { handle_start, handle_end } => [Some(handle_start - bezier.start), Some(handle_end - bezier.end)],
};
let points = [points_map[&start], points_map[&end]];
let modification_type = VectorModificationType::InsertSegment { id: new_id, points, handles };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
}
for handles in old_vector_data.colinear_manipulators {
let to_new_handle = |handle: HandleId| -> HandleId {
HandleId {
ty: handle.ty,
segment: segments_map[&handle.segment],
}
};
if segments_map.contains_key(&handles[0].segment) && segments_map.contains_key(&handles[1].segment) {
let new_handles = [to_new_handle(handles[0]), to_new_handle(handles[1])];
let modification_type = VectorModificationType::SetG1Continuous { handles: new_handles, enabled: true };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
}
shape_editor.deselect_all_points();
shape_editor.deselect_all_segments();
// Set selection to newly inserted points and segments
let Some(state) = shape_editor.selected_shape_state.get_mut(&layer) else {
error!("No state for layer: {layer:?}");
continue;
};
if tool_options.path_editing_mode.point_editing_mode {
points_map.values().for_each(|point| state.select_point(ManipulatorPointId::Anchor(*point)));
}
if tool_options.path_editing_mode.segment_editing_mode {
segments_map.values().for_each(|segment| state.select_segment(*segment));
}
}
PathToolFsmState::Ready
}
(_, PathToolMessage::DoubleClick { extend_selection, shrink_selection }) => { (_, PathToolMessage::DoubleClick { extend_selection, shrink_selection }) => {
// Double-clicked on a point (flip smooth/sharp behavior) // Double-clicked on a point (flip smooth/sharp behavior)
let nearest_point = shape_editor.find_nearest_point_indices(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD); let nearest_point = shape_editor.find_nearest_point_indices(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD);
@@ -2694,30 +3013,7 @@ impl Fsm for PathToolFsmState {
colinear, colinear,
}; };
tool_data.single_path_node_compatible_layer_selected = { tool_data.make_path_editable_is_allowed = make_path_editable_is_allowed(&document.network_interface, document.metadata()).is_some();
let selected_nodes = document.network_interface.selected_nodes();
let mut selected_layers = selected_nodes.selected_layers(document.metadata());
let first_layer = selected_layers.next();
let second_layer = selected_layers.next();
let has_single_selection = first_layer.is_some() && second_layer.is_none();
let compatible_type = first_layer.and_then(|layer| {
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &document.network_interface);
graph_layer.horizontal_layer_flow().nth(1).map(|node_id| {
let (output_type, _) = document.network_interface.output_type(&node_id, 0, &[]);
format!("type:{}", output_type.nested_type())
})
});
let is_compatible = compatible_type.as_deref() == Some("type:Instances<VectorData>");
let is_modifiable = first_layer.is_some_and(|layer| {
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &document.network_interface);
matches!(graph_layer.find_input("Path", 1), Some(TaggedValue::VectorModification(_)))
});
first_layer.is_some() && has_single_selection && is_compatible && !is_modifiable
};
tool_data.update_selection_status(shape_editor, document); tool_data.update_selection_status(shape_editor, document);
self self
} }
@@ -743,7 +743,7 @@ impl Fsm for ShapeToolFsmState {
self self
} }
(ShapeToolFsmState::ModifyingGizmo, ShapeToolMessage::PointerMove(..)) => { (ShapeToolFsmState::ModifyingGizmo, ShapeToolMessage::PointerMove(..)) => {
tool_data.gizmo_manager.handle_update(tool_data.data.drag_start, document, input, responses); tool_data.gizmo_manager.handle_update(tool_data.data.viewport_drag_start(document), document, input, responses);
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);
@@ -385,25 +385,19 @@ impl TextToolData {
parent: document.new_layer_parent(true), parent: document.new_layer_parent(true),
insert_index: 0, insert_index: 0,
}); });
responses.add(DeferMessage::AfterGraphRun { responses.add(GraphOperationMessage::FillSet {
messages: vec![ layer: self.layer,
GraphOperationMessage::FillSet { fill: if editing_text.color.is_some() {
layer: self.layer, Fill::Solid(editing_text.color.unwrap().to_gamma_srgb())
fill: if editing_text.color.is_some() { } else {
Fill::Solid(editing_text.color.unwrap().to_gamma_srgb()) Fill::None
} else { },
Fill::None });
}, responses.add(GraphOperationMessage::TransformSet {
} layer: self.layer,
.into(), transform: editing_text.transform,
GraphOperationMessage::TransformSet { transform_in: TransformIn::Viewport,
layer: self.layer, skip_rerender: true,
transform: editing_text.transform,
transform_in: TransformIn::Viewport,
skip_rerender: true,
}
.into(),
],
}); });
self.editing_text = Some(editing_text); self.editing_text = Some(editing_text);
+4 -4
View File
@@ -9,7 +9,7 @@ use graph_craft::wasm_application_io::EditorPreferences;
use graphene_std::application_io::TimingInformation; use graphene_std::application_io::TimingInformation;
use graphene_std::application_io::{NodeGraphUpdateMessage, RenderConfig}; use graphene_std::application_io::{NodeGraphUpdateMessage, RenderConfig};
use graphene_std::renderer::RenderSvgSegmentList; use graphene_std::renderer::RenderSvgSegmentList;
use graphene_std::renderer::{GraphicElementRendered, RenderParams, SvgRender}; use graphene_std::renderer::{Render, RenderParams, SvgRender};
use graphene_std::renderer::{RenderMetadata, format_transform_matrix}; use graphene_std::renderer::{RenderMetadata, format_transform_matrix};
use graphene_std::text::FontCache; use graphene_std::text::FontCache;
use graphene_std::transform::Footprint; use graphene_std::transform::Footprint;
@@ -321,7 +321,7 @@ impl NodeGraphExecutor {
Ok(()) Ok(())
} }
fn debug_render(render_object: impl GraphicElementRendered, transform: DAffine2, responses: &mut VecDeque<Message>) { fn debug_render(render_object: impl Render, transform: DAffine2, responses: &mut VecDeque<Message>) {
// Setup rendering // Setup rendering
let mut render = SvgRender::new(); let mut render = SvgRender::new();
let render_params = RenderParams { let render_params = RenderParams {
@@ -388,7 +388,7 @@ impl NodeGraphExecutor {
let graphene_std::renderer::RenderMetadata { let graphene_std::renderer::RenderMetadata {
upstream_footprints: footprints, upstream_footprints: footprints,
local_transforms, local_transforms,
first_instance_source_id, first_element_source_id,
click_targets, click_targets,
clip_targets, clip_targets,
} = render_output_metadata; } = render_output_metadata;
@@ -397,7 +397,7 @@ impl NodeGraphExecutor {
responses.add(DocumentMessage::UpdateUpstreamTransforms { responses.add(DocumentMessage::UpdateUpstreamTransforms {
upstream_footprints: footprints, upstream_footprints: footprints,
local_transforms, local_transforms,
first_instance_source_id, first_element_source_id,
}); });
responses.add(DocumentMessage::UpdateClickTargets { click_targets }); responses.add(DocumentMessage::UpdateClickTargets { click_targets });
responses.add(DocumentMessage::UpdateClipTargets { clip_targets }); responses.add(DocumentMessage::UpdateClipTargets { clip_targets });
+14 -16
View File
@@ -9,13 +9,13 @@ use graph_craft::wasm_application_io::EditorPreferences;
use graph_craft::{ProtoNodeIdentifier, concrete}; use graph_craft::{ProtoNodeIdentifier, concrete};
use graphene_std::Context; use graphene_std::Context;
use graphene_std::application_io::{ImageTexture, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig}; use graphene_std::application_io::{ImageTexture, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
use graphene_std::instances::Instance;
use graphene_std::memo::IORecord; use graphene_std::memo::IORecord;
use graphene_std::renderer::{GraphicElementRendered, RenderParams, SvgRender}; use graphene_std::renderer::{Render, RenderParams, SvgRender};
use graphene_std::renderer::{RenderSvgSegmentList, SvgSegment}; use graphene_std::renderer::{RenderSvgSegmentList, SvgSegment};
use graphene_std::table::{Table, TableRow};
use graphene_std::text::FontCache; use graphene_std::text::FontCache;
use graphene_std::vector::VectorData;
use graphene_std::vector::style::ViewMode; use graphene_std::vector::style::ViewMode;
use graphene_std::vector::{VectorData, VectorDataTable};
use graphene_std::wasm_application_io::{RenderOutputType, WasmApplicationIo, WasmEditorApi}; use graphene_std::wasm_application_io::{RenderOutputType, WasmApplicationIo, WasmEditorApi};
use interpreted_executor::dynamic_executor::{DynamicExecutor, IntrospectError, ResolvedDocumentNodeTypesDelta}; use interpreted_executor::dynamic_executor::{DynamicExecutor, IntrospectError, ResolvedDocumentNodeTypesDelta};
use interpreted_executor::util::wrap_network_in_scope; use interpreted_executor::util::wrap_network_in_scope;
@@ -134,9 +134,9 @@ impl NodeRuntime {
pub async fn run(&mut self) -> Option<ImageTexture> { pub async fn run(&mut self) -> Option<ImageTexture> {
if self.editor_api.application_io.is_none() { if self.editor_api.application_io.is_none() {
self.editor_api = WasmEditorApi { self.editor_api = WasmEditorApi {
#[cfg(all(not(test), target_arch = "wasm32"))] #[cfg(all(not(test), target_family = "wasm"))]
application_io: Some(WasmApplicationIo::new().await.into()), application_io: Some(WasmApplicationIo::new().await.into()),
#[cfg(any(test, not(target_arch = "wasm32")))] #[cfg(any(test, not(target_family = "wasm")))]
application_io: Some(WasmApplicationIo::new_offscreen().await.into()), application_io: Some(WasmApplicationIo::new_offscreen().await.into()),
font_cache: self.editor_api.font_cache.clone(), font_cache: self.editor_api.font_cache.clone(),
node_graph_message_sender: Box::new(self.sender.clone()), node_graph_message_sender: Box::new(self.sender.clone()),
@@ -299,7 +299,7 @@ impl NodeRuntime {
continue; continue;
}; };
// Extract the monitor node's stored `GraphicElement` data. // Extract the monitor node's stored `Graphic` data.
let Ok(introspected_data) = self.executor.introspect(monitor_node_path) else { let Ok(introspected_data) = self.executor.introspect(monitor_node_path) else {
// TODO: Fix the root of the issue causing the spam of this warning (this at least temporarily disables it in release builds) // TODO: Fix the root of the issue causing the spam of this warning (this at least temporarily disables it in release builds)
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
@@ -308,29 +308,27 @@ impl NodeRuntime {
continue; continue;
}; };
if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, graphene_std::GraphicElement>>() { if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, graphene_std::Graphic>>() {
Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails) Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, graphene_std::Artboard>>() { } else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, graphene_std::Artboard>>() {
Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails) Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
// Insert the vector modify if we are dealing with vector data // Insert the vector modify if we are dealing with vector data
} else if let Some(record) = introspected_data.downcast_ref::<IORecord<Context, VectorDataTable>>() { } else if let Some(record) = introspected_data.downcast_ref::<IORecord<Context, Table<VectorData>>>() {
let default = Instance::default(); let default = TableRow::default();
self.vector_modify.insert( self.vector_modify
parent_network_node_id, .insert(parent_network_node_id, record.output.iter_ref().next().unwrap_or_else(|| default.as_ref()).element.clone());
record.output.instance_ref_iter().next().unwrap_or_else(|| default.to_instance_ref()).instance.clone(),
);
} else { } else {
log::warn!("Failed to downcast monitor node output {parent_network_node_id:?}"); log::warn!("Failed to downcast monitor node output {parent_network_node_id:?}");
} }
} }
} }
// If this is `GraphicElement` data: // If this is `Graphic` data:
// Regenerate click targets and thumbnails for the layers in the graph, modifying the state and updating the UI. // Regenerate click targets and thumbnails for the layers in the graph, modifying the state and updating the UI.
fn process_graphic_element( fn process_graphic_element(
thumbnail_renders: &mut HashMap<NodeId, Vec<SvgSegment>>, thumbnail_renders: &mut HashMap<NodeId, Vec<SvgSegment>>,
parent_network_node_id: NodeId, parent_network_node_id: NodeId,
graphic_element: &impl GraphicElementRendered, graphic_element: &impl Render,
responses: &mut VecDeque<FrontendMessage>, responses: &mut VecDeque<FrontendMessage>,
update_thumbnails: bool, update_thumbnails: bool,
) { ) {
@@ -354,7 +352,7 @@ impl NodeRuntime {
let bounds = graphic_element.bounding_box(DAffine2::IDENTITY, true); let bounds = graphic_element.bounding_box(DAffine2::IDENTITY, true);
// Render the thumbnail from a `GraphicElement` into an SVG string // Render the thumbnail from a `Graphic` into an SVG string
let render_params = RenderParams { let render_params = RenderParams {
view_mode: ViewMode::Normal, view_mode: ViewMode::Normal,
culling_bounds: bounds, culling_bounds: bounds,
@@ -13,7 +13,7 @@
export let minWidth = 0; export let minWidth = 0;
export let multiline = false; export let multiline = false;
export let tooltip: string | undefined = undefined; export let tooltip: string | undefined = undefined;
export let checkboxId: bigint | undefined = undefined; export let forCheckbox: bigint | undefined = undefined;
$: extraClasses = Object.entries(classes) $: extraClasses = Object.entries(classes)
.flatMap(([className, stateName]) => (stateName ? [className] : [])) .flatMap(([className, stateName]) => (stateName ? [className] : []))
@@ -34,7 +34,7 @@
style:min-width={minWidth > 0 ? `${minWidth}px` : undefined} style:min-width={minWidth > 0 ? `${minWidth}px` : undefined}
style={`${styleName} ${extraStyles}`.trim() || undefined} style={`${styleName} ${extraStyles}`.trim() || undefined}
title={tooltip} title={tooltip}
for={checkboxId !== undefined ? `checkbox-input-${checkboxId}` : undefined} for={forCheckbox !== undefined ? `checkbox-input-${forCheckbox}` : undefined}
> >
<slot /> <slot />
</label> </label>
+10 -4
View File
@@ -303,13 +303,19 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
if (!dataTransfer || targetIsTextField(e.target || undefined)) return; if (!dataTransfer || targetIsTextField(e.target || undefined)) return;
e.preventDefault(); e.preventDefault();
const LAYER_DATA = "graphite/layer: ";
const NODES_DATA = "graphite/nodes: ";
const VECTOR_DATA = "graphite/vector: ";
Array.from(dataTransfer.items).forEach(async (item) => { Array.from(dataTransfer.items).forEach(async (item) => {
if (item.type === "text/plain") { if (item.type === "text/plain") {
item.getAsString((text) => { item.getAsString((text) => {
if (text.startsWith("graphite/layer: ")) { if (text.startsWith(LAYER_DATA)) {
editor.handle.pasteSerializedData(text.substring(16, text.length)); editor.handle.pasteSerializedData(text.substring(LAYER_DATA.length, text.length));
} else if (text.startsWith("graphite/nodes: ")) { } else if (text.startsWith(NODES_DATA)) {
editor.handle.pasteSerializedNodes(text.substring(16, text.length)); editor.handle.pasteSerializedNodes(text.substring(NODES_DATA.length, text.length));
} else if (text.startsWith(VECTOR_DATA)) {
editor.handle.pasteSerializedVector(text.substring(VECTOR_DATA.length, text.length));
} }
}); });
} }
+1 -1
View File
@@ -1348,7 +1348,7 @@ export class TextLabel extends WidgetProps {
@Transform(({ value }: { value: string }) => value || undefined) @Transform(({ value }: { value: string }) => value || undefined)
tooltip!: string | undefined; tooltip!: string | undefined;
checkboxId!: bigint | undefined; forCheckbox!: bigint | undefined;
} }
export type ReferencePoint = "None" | "TopLeft" | "TopCenter" | "TopRight" | "CenterLeft" | "Center" | "CenterRight" | "BottomLeft" | "BottomCenter" | "BottomRight"; export type ReferencePoint = "None" | "TopLeft" | "TopCenter" | "TopRight" | "CenterLeft" | "Center" | "CenterRight" | "BottomLeft" | "BottomCenter" | "BottomRight";
+8 -1
View File
@@ -629,13 +629,20 @@ impl EditorHandle {
self.dispatch(message); self.dispatch(message);
} }
/// Paste layers from a serialized json representation /// Paste layers from a serialized JSON representation
#[wasm_bindgen(js_name = pasteSerializedData)] #[wasm_bindgen(js_name = pasteSerializedData)]
pub fn paste_serialized_data(&self, data: String) { pub fn paste_serialized_data(&self, data: String) {
let message = PortfolioMessage::PasteSerializedData { data }; let message = PortfolioMessage::PasteSerializedData { data };
self.dispatch(message); self.dispatch(message);
} }
/// Paste vector data into a new layer from a serialized JSON representation
#[wasm_bindgen(js_name = pasteSerializedVector)]
pub fn paste_serialized_vector(&self, data: String) {
let message = PortfolioMessage::PasteSerializedVector { data };
self.dispatch(message);
}
#[wasm_bindgen(js_name = clipLayer)] #[wasm_bindgen(js_name = clipLayer)]
pub fn clip_layer(&self, id: u64) { pub fn clip_layer(&self, id: u64) {
let id = NodeId(id); let id = NodeId(id);
+10 -10
View File
@@ -363,31 +363,31 @@ fn simple_downcast_panic() {
assert_eq!(*downcast::<u32>(x).expect("attempted to perform invalid downcast"), 3_u32); assert_eq!(*downcast::<u32>(x).expect("attempted to perform invalid downcast"), 3_u32);
} }
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
pub trait WasmNotSend: Send {} pub trait WasmNotSend: Send {}
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
pub trait WasmNotSend {} pub trait WasmNotSend {}
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
impl<T: Send> WasmNotSend for T {} impl<T: Send> WasmNotSend for T {}
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
impl<T> WasmNotSend for T {} impl<T> WasmNotSend for T {}
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
pub trait WasmNotSync: Sync {} pub trait WasmNotSync: Sync {}
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
pub trait WasmNotSync {} pub trait WasmNotSync {}
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
impl<T: Sync> WasmNotSync for T {} impl<T: Sync> WasmNotSync for T {}
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
impl<T> WasmNotSync for T {} impl<T> WasmNotSync for T {}
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub type DynFuture<'n, T> = Pin<Box<dyn core::future::Future<Output = T> + 'n + Send>>; pub type DynFuture<'n, T> = Pin<Box<dyn core::future::Future<Output = T> + 'n + Send>>;
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub type DynFuture<'n, T> = Pin<Box<dyn core::future::Future<Output = T> + 'n>>; pub type DynFuture<'n, T> = Pin<Box<dyn core::future::Future<Output = T> + 'n>>;
+5 -5
View File
@@ -42,7 +42,7 @@ pub trait Size {
fn size(&self) -> UVec2; fn size(&self) -> UVec2;
} }
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
impl Size for web_sys::HtmlCanvasElement { impl Size for web_sys::HtmlCanvasElement {
fn size(&self) -> UVec2 { fn size(&self) -> UVec2 {
UVec2::new(self.width(), self.height()) UVec2::new(self.width(), self.height())
@@ -115,9 +115,9 @@ pub struct SurfaceHandle<Surface> {
pub surface: Surface, pub surface: Surface,
} }
// #[cfg(target_arch = "wasm32")] // #[cfg(target_family = "wasm")]
// unsafe impl<T: dyn_any::WasmNotSend> Send for SurfaceHandle<T> {} // unsafe impl<T: dyn_any::WasmNotSend> Send for SurfaceHandle<T> {}
// #[cfg(target_arch = "wasm32")] // #[cfg(target_family = "wasm")]
// unsafe impl<T: dyn_any::WasmNotSync> Sync for SurfaceHandle<T> {} // unsafe impl<T: dyn_any::WasmNotSync> Sync for SurfaceHandle<T> {}
impl<S: Size> Size for SurfaceHandle<S> { impl<S: Size> Size for SurfaceHandle<S> {
@@ -153,9 +153,9 @@ impl<'a, Surface> Drop for SurfaceHandle<'a, Surface> {
} }
}*/ }*/
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
pub type ResourceFuture = Pin<Box<dyn Future<Output = Result<Arc<[u8]>, ApplicationError>>>>; pub type ResourceFuture = Pin<Box<dyn Future<Output = Result<Arc<[u8]>, ApplicationError>>>>;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
pub type ResourceFuture = Pin<Box<dyn Future<Output = Result<Arc<[u8]>, ApplicationError>> + Send>>; pub type ResourceFuture = Pin<Box<dyn Future<Output = Result<Arc<[u8]>, ApplicationError>> + Send>>;
pub trait ApplicationIo { pub trait ApplicationIo {
+41 -41
View File
@@ -5,12 +5,12 @@ use graphene_core::blending::BlendMode;
use graphene_core::bounds::BoundingBox; use graphene_core::bounds::BoundingBox;
use graphene_core::color::{Alpha, Color, Pixel, Sample}; use graphene_core::color::{Alpha, Color, Pixel, Sample};
use graphene_core::generic::FnNode; use graphene_core::generic::FnNode;
use graphene_core::instances::Instance;
use graphene_core::math::bbox::{AxisAlignedBbox, Bbox}; use graphene_core::math::bbox::{AxisAlignedBbox, Bbox};
use graphene_core::raster::BitmapMut; use graphene_core::raster::BitmapMut;
use graphene_core::raster::image::Image; use graphene_core::raster::image::Image;
use graphene_core::raster_types::{CPU, Raster, RasterDataTable}; use graphene_core::raster_types::{CPU, Raster};
use graphene_core::registry::FutureWrapperNode; use graphene_core::registry::FutureWrapperNode;
use graphene_core::table::{Table, TableRow};
use graphene_core::transform::Transform; use graphene_core::transform::Transform;
use graphene_core::value::ClonedNode; use graphene_core::value::ClonedNode;
use graphene_core::{Ctx, Node}; use graphene_core::{Ctx, Node};
@@ -77,7 +77,7 @@ fn brush_stamp_generator(#[unit(" px")] diameter: f64, color: Color, hardness: f
} }
#[node_macro::node(skip_impl)] #[node_macro::node(skip_impl)]
fn blit<BlendFn>(mut target: RasterDataTable<CPU>, texture: Raster<CPU>, positions: Vec<DVec2>, blend_mode: BlendFn) -> RasterDataTable<CPU> fn blit<BlendFn>(mut target: Table<Raster<CPU>>, texture: Raster<CPU>, positions: Vec<DVec2>, blend_mode: BlendFn) -> Table<Raster<CPU>>
where where
BlendFn: for<'any_input> Node<'any_input, (Color, Color), Output = Color>, BlendFn: for<'any_input> Node<'any_input, (Color, Color), Output = Color>,
{ {
@@ -85,14 +85,14 @@ where
return target; return target;
} }
for target_instance in target.instance_mut_iter() { for table_row in target.iter_mut() {
let target_width = target_instance.instance.width; let target_width = table_row.element.width;
let target_height = target_instance.instance.height; let target_height = table_row.element.height;
let target_size = DVec2::new(target_width as f64, target_height as f64); let target_size = DVec2::new(target_width as f64, target_height as f64);
let texture_size = DVec2::new(texture.width as f64, texture.height as f64); let texture_size = DVec2::new(texture.width as f64, texture.height as f64);
let document_to_target = DAffine2::from_translation(-texture_size / 2.) * DAffine2::from_scale(target_size) * target_instance.transform.inverse(); let document_to_target = DAffine2::from_translation(-texture_size / 2.) * DAffine2::from_scale(target_size) * table_row.transform.inverse();
for position in &positions { for position in &positions {
let start = document_to_target.transform_point2(*position).round(); let start = document_to_target.transform_point2(*position).round();
@@ -112,12 +112,12 @@ where
let max_y = (blit_area_offset.y + blit_area_dimensions.y).saturating_sub(1); let max_y = (blit_area_offset.y + blit_area_dimensions.y).saturating_sub(1);
let max_x = (blit_area_offset.x + blit_area_dimensions.x).saturating_sub(1); let max_x = (blit_area_offset.x + blit_area_dimensions.x).saturating_sub(1);
assert!(texture_index(max_x, max_y) < texture.data.len()); assert!(texture_index(max_x, max_y) < texture.data.len());
assert!(target_index(max_x, max_y) < target_instance.instance.data.len()); assert!(target_index(max_x, max_y) < table_row.element.data.len());
for y in blit_area_offset.y..blit_area_offset.y + blit_area_dimensions.y { for y in blit_area_offset.y..blit_area_offset.y + blit_area_dimensions.y {
for x in blit_area_offset.x..blit_area_offset.x + blit_area_dimensions.x { for x in blit_area_offset.x..blit_area_offset.x + blit_area_dimensions.x {
let src_pixel = texture.data[texture_index(x, y)]; let src_pixel = texture.data[texture_index(x, y)];
let dst_pixel = &mut target_instance.instance.data_mut().data[target_index(x + clamp_start.x, y + clamp_start.y)]; let dst_pixel = &mut table_row.element.data_mut().data[target_index(x + clamp_start.x, y + clamp_start.y)];
*dst_pixel = blend_mode.eval((src_pixel, *dst_pixel)); *dst_pixel = blend_mode.eval((src_pixel, *dst_pixel));
} }
} }
@@ -130,13 +130,13 @@ where
pub async fn create_brush_texture(brush_style: &BrushStyle) -> Raster<CPU> { pub async fn create_brush_texture(brush_style: &BrushStyle) -> Raster<CPU> {
let stamp = brush_stamp_generator(brush_style.diameter, brush_style.color, brush_style.hardness, brush_style.flow); let stamp = brush_stamp_generator(brush_style.diameter, brush_style.color, brush_style.hardness, brush_style.flow);
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(brush_style.diameter), 0., -DVec2::splat(brush_style.diameter / 2.)); let transform = DAffine2::from_scale_angle_translation(DVec2::splat(brush_style.diameter), 0., -DVec2::splat(brush_style.diameter / 2.));
let blank_texture = empty_image((), transform, Color::TRANSPARENT).instance_iter().next().unwrap_or_default(); let blank_texture = empty_image((), transform, Color::TRANSPARENT).iter().next().unwrap_or_default();
let image = blend_stamp_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 1.)); let image = blend_stamp_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 1.));
image.instance image.element
} }
pub fn blend_with_mode(background: Instance<Raster<CPU>>, foreground: Instance<Raster<CPU>>, blend_mode: BlendMode, opacity: f64) -> Instance<Raster<CPU>> { pub fn blend_with_mode(background: TableRow<Raster<CPU>>, foreground: TableRow<Raster<CPU>>, blend_mode: BlendMode, opacity: f64) -> TableRow<Raster<CPU>> {
let opacity = opacity / 100.; let opacity = opacity / 100.;
match std::hint::black_box(blend_mode) { match std::hint::black_box(blend_mode) {
// Normal group // Normal group
@@ -179,14 +179,14 @@ pub fn blend_with_mode(background: Instance<Raster<CPU>>, foreground: Instance<R
} }
#[node_macro::node(category("Raster"))] #[node_macro::node(category("Raster"))]
async fn brush(_: impl Ctx, mut image_frame_table: RasterDataTable<CPU>, strokes: Vec<BrushStroke>, cache: BrushCache) -> RasterDataTable<CPU> { async fn brush(_: impl Ctx, mut image_frame_table: Table<Raster<CPU>>, strokes: Vec<BrushStroke>, cache: BrushCache) -> Table<Raster<CPU>> {
if image_frame_table.is_empty() { if image_frame_table.is_empty() {
image_frame_table.push(Instance::default()); image_frame_table.push(TableRow::default());
} }
// TODO: Find a way to handle more than one instance // TODO: Find a way to handle more than one row
let image_frame_instance = image_frame_table.instance_ref_iter().next().expect("Expected the one instance we just pushed").to_instance_cloned(); let table_row = image_frame_table.iter_ref().next().expect("Expected the one row we just pushed").into_cloned();
let [start, end] = image_frame_instance.clone().to_table().bounding_box(DAffine2::IDENTITY, false).unwrap_or([DVec2::ZERO, DVec2::ZERO]); let [start, end] = Table::new_from_row(table_row.clone()).bounding_box(DAffine2::IDENTITY, false).unwrap_or([DVec2::ZERO, DVec2::ZERO]);
let image_bbox = AxisAlignedBbox { start, end }; let image_bbox = AxisAlignedBbox { start, end };
let stroke_bbox = strokes.iter().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO); let stroke_bbox = strokes.iter().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO);
let bbox = if image_bbox.size().length() < 0.1 { stroke_bbox } else { stroke_bbox.union(&image_bbox) }; let bbox = if image_bbox.size().length() < 0.1 { stroke_bbox } else { stroke_bbox.union(&image_bbox) };
@@ -195,11 +195,11 @@ async fn brush(_: impl Ctx, mut image_frame_table: RasterDataTable<CPU>, strokes
let mut draw_strokes: Vec<_> = strokes.iter().filter(|&s| !matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore)).cloned().collect(); let mut draw_strokes: Vec<_> = strokes.iter().filter(|&s| !matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore)).cloned().collect();
let erase_restore_strokes: Vec<_> = strokes.iter().filter(|&s| matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore)).cloned().collect(); let erase_restore_strokes: Vec<_> = strokes.iter().filter(|&s| matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore)).cloned().collect();
let mut brush_plan = cache.compute_brush_plan(image_frame_instance, &draw_strokes); let mut brush_plan = cache.compute_brush_plan(table_row, &draw_strokes);
// TODO: Find a way to handle more than one instance // TODO: Find a way to handle more than one row
let Some(mut actual_image) = extend_image_to_bounds((), brush_plan.background.to_table(), background_bounds).instance_iter().next() else { let Some(mut actual_image) = extend_image_to_bounds((), Table::new_from_row(brush_plan.background), background_bounds).iter().next() else {
return RasterDataTable::default(); return Table::new();
}; };
let final_stroke_idx = brush_plan.strokes.len().saturating_sub(1); let final_stroke_idx = brush_plan.strokes.len().saturating_sub(1);
@@ -238,15 +238,15 @@ async fn brush(_: impl Ctx, mut image_frame_table: RasterDataTable<CPU>, strokes
); );
let blit_target = if idx == 0 { let blit_target = if idx == 0 {
let target = core::mem::take(&mut brush_plan.first_stroke_texture); let target = core::mem::take(&mut brush_plan.first_stroke_texture);
extend_image_to_bounds((), target.to_table(), stroke_to_layer) extend_image_to_bounds((), Table::new_from_row(target), stroke_to_layer)
} else { } else {
empty_image((), stroke_to_layer, Color::TRANSPARENT) empty_image((), stroke_to_layer, Color::TRANSPARENT)
// EmptyImageNode::new(CopiedNode::new(stroke_to_layer), CopiedNode::new(Color::TRANSPARENT)).eval(()) // EmptyImageNode::new(CopiedNode::new(stroke_to_layer), CopiedNode::new(Color::TRANSPARENT)).eval(())
}; };
let instances = blit_node.eval(blit_target).await; let table = blit_node.eval(blit_target).await;
assert_eq!(instances.len(), 1); assert_eq!(table.len(), 1);
instances.instance_iter().next().unwrap_or_default() table.iter().next().unwrap_or_default()
}; };
// Cache image before doing final blend, and store final stroke texture. // Cache image before doing final blend, and store final stroke texture.
@@ -261,8 +261,8 @@ async fn brush(_: impl Ctx, mut image_frame_table: RasterDataTable<CPU>, strokes
let has_erase_strokes = strokes.iter().any(|s| s.style.blend_mode == BlendMode::Erase); let has_erase_strokes = strokes.iter().any(|s| s.style.blend_mode == BlendMode::Erase);
if has_erase_strokes { if has_erase_strokes {
let opaque_image = Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::WHITE); let opaque_image = Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::WHITE);
let mut erase_restore_mask = Instance { let mut erase_restore_mask = TableRow {
instance: Raster::new_cpu(opaque_image), element: Raster::new_cpu(opaque_image),
transform: background_bounds, transform: background_bounds,
..Default::default() ..Default::default()
}; };
@@ -285,7 +285,7 @@ async fn brush(_: impl Ctx, mut image_frame_table: RasterDataTable<CPU>, strokes
FutureWrapperNode::new(ClonedNode::new(positions)), FutureWrapperNode::new(ClonedNode::new(positions)),
FutureWrapperNode::new(ClonedNode::new(blend_params)), FutureWrapperNode::new(ClonedNode::new(blend_params)),
); );
erase_restore_mask = blit_node.eval(erase_restore_mask.to_table()).await.instance_iter().next().unwrap_or_default(); erase_restore_mask = blit_node.eval(Table::new_from_row(erase_restore_mask)).await.iter().next().unwrap_or_default();
} }
// Yes, this is essentially the same as the above, but we duplicate to inline the blend mode. // Yes, this is essentially the same as the above, but we duplicate to inline the blend mode.
BlendMode::Restore => { BlendMode::Restore => {
@@ -295,7 +295,7 @@ async fn brush(_: impl Ctx, mut image_frame_table: RasterDataTable<CPU>, strokes
FutureWrapperNode::new(ClonedNode::new(positions)), FutureWrapperNode::new(ClonedNode::new(positions)),
FutureWrapperNode::new(ClonedNode::new(blend_params)), FutureWrapperNode::new(ClonedNode::new(blend_params)),
); );
erase_restore_mask = blit_node.eval(erase_restore_mask.to_table()).await.instance_iter().next().unwrap_or_default(); erase_restore_mask = blit_node.eval(Table::new_from_row(erase_restore_mask)).await.iter().next().unwrap_or_default();
} }
_ => unreachable!(), _ => unreachable!(),
} }
@@ -305,8 +305,8 @@ async fn brush(_: impl Ctx, mut image_frame_table: RasterDataTable<CPU>, strokes
actual_image = blend_image_closure(erase_restore_mask, actual_image, |a, b| blend_params.eval((a, b))); actual_image = blend_image_closure(erase_restore_mask, actual_image, |a, b| blend_params.eval((a, b)));
} }
let first_row = image_frame_table.instance_mut_iter().next().unwrap(); let first_row = image_frame_table.iter_mut().next().unwrap();
*first_row.instance = actual_image.instance; *first_row.element = actual_image.element;
*first_row.transform = actual_image.transform; *first_row.transform = actual_image.transform;
*first_row.alpha_blending = actual_image.alpha_blending; *first_row.alpha_blending = actual_image.alpha_blending;
*first_row.source_node_id = actual_image.source_node_id; *first_row.source_node_id = actual_image.source_node_id;
@@ -314,9 +314,9 @@ async fn brush(_: impl Ctx, mut image_frame_table: RasterDataTable<CPU>, strokes
image_frame_table image_frame_table
} }
pub fn blend_image_closure(foreground: Instance<Raster<CPU>>, mut background: Instance<Raster<CPU>>, map_fn: impl Fn(Color, Color) -> Color) -> Instance<Raster<CPU>> { pub fn blend_image_closure(foreground: TableRow<Raster<CPU>>, mut background: TableRow<Raster<CPU>>, map_fn: impl Fn(Color, Color) -> Color) -> TableRow<Raster<CPU>> {
let foreground_size = DVec2::new(foreground.instance.width as f64, foreground.instance.height as f64); let foreground_size = DVec2::new(foreground.element.width as f64, foreground.element.height as f64);
let background_size = DVec2::new(background.instance.width as f64, background.instance.height as f64); let background_size = DVec2::new(background.element.width as f64, background.element.height as f64);
// Transforms a point from the background image to the foreground image // Transforms a point from the background image to the foreground image
let background_to_foreground = DAffine2::from_scale(foreground_size) * foreground.transform.inverse() * background.transform * DAffine2::from_scale(1. / background_size); let background_to_foreground = DAffine2::from_scale(foreground_size) * foreground.transform.inverse() * background.transform * DAffine2::from_scale(1. / background_size);
@@ -333,8 +333,8 @@ pub fn blend_image_closure(foreground: Instance<Raster<CPU>>, mut background: In
let background_point = DVec2::new(x as f64, y as f64); let background_point = DVec2::new(x as f64, y as f64);
let foreground_point = background_to_foreground.transform_point2(background_point); let foreground_point = background_to_foreground.transform_point2(background_point);
let source_pixel = foreground.instance.sample(foreground_point); let source_pixel = foreground.element.sample(foreground_point);
let Some(destination_pixel) = background.instance.data_mut().get_pixel_mut(x, y) else { continue }; let Some(destination_pixel) = background.element.data_mut().get_pixel_mut(x, y) else { continue };
*destination_pixel = map_fn(source_pixel, *destination_pixel); *destination_pixel = map_fn(source_pixel, *destination_pixel);
} }
@@ -343,8 +343,8 @@ pub fn blend_image_closure(foreground: Instance<Raster<CPU>>, mut background: In
background background
} }
pub fn blend_stamp_closure(foreground: BrushStampGenerator<Color>, mut background: Instance<Raster<CPU>>, map_fn: impl Fn(Color, Color) -> Color) -> Instance<Raster<CPU>> { pub fn blend_stamp_closure(foreground: BrushStampGenerator<Color>, mut background: TableRow<Raster<CPU>>, map_fn: impl Fn(Color, Color) -> Color) -> TableRow<Raster<CPU>> {
let background_size = DVec2::new(background.instance.width as f64, background.instance.height as f64); let background_size = DVec2::new(background.element.width as f64, background.element.height as f64);
// Transforms a point from the background image to the foreground image // Transforms a point from the background image to the foreground image
let background_to_foreground = background.transform * DAffine2::from_scale(1. / background_size); let background_to_foreground = background.transform * DAffine2::from_scale(1. / background_size);
@@ -363,7 +363,7 @@ pub fn blend_stamp_closure(foreground: BrushStampGenerator<Color>, mut backgroun
let foreground_point = background_to_foreground.transform_point2(background_point); let foreground_point = background_to_foreground.transform_point2(background_point);
let Some(source_pixel) = foreground.sample(foreground_point, area) else { continue }; let Some(source_pixel) = foreground.sample(foreground_point, area) else { continue };
let Some(destination_pixel) = background.instance.data_mut().get_pixel_mut(x, y) else { continue }; let Some(destination_pixel) = background.element.data_mut().get_pixel_mut(x, y) else { continue };
*destination_pixel = map_fn(source_pixel, *destination_pixel); *destination_pixel = map_fn(source_pixel, *destination_pixel);
} }
@@ -391,7 +391,7 @@ mod test {
async fn test_brush_output_size() { async fn test_brush_output_size() {
let image = brush( let image = brush(
(), (),
RasterDataTable::<CPU>::new(Raster::new_cpu(Image::<Color>::default())), Table::new_from_element(Raster::new_cpu(Image::<Color>::default())),
vec![BrushStroke { vec![BrushStroke {
trace: vec![crate::brush_stroke::BrushInputSample { position: DVec2::ZERO }], trace: vec![crate::brush_stroke::BrushInputSample { position: DVec2::ZERO }],
style: BrushStyle { style: BrushStyle {
@@ -406,6 +406,6 @@ mod test {
BrushCache::default(), BrushCache::default(),
) )
.await; .await;
assert_eq!(image.instance_ref_iter().next().unwrap().instance.width, 20); assert_eq!(image.iter_ref().next().unwrap().element.width, 20);
} }
} }
+15 -15
View File
@@ -1,9 +1,9 @@
use crate::brush_stroke::BrushStroke; use crate::brush_stroke::BrushStroke;
use crate::brush_stroke::BrushStyle; use crate::brush_stroke::BrushStyle;
use dyn_any::DynAny; use dyn_any::DynAny;
use graphene_core::instances::Instance;
use graphene_core::raster_types::CPU; use graphene_core::raster_types::CPU;
use graphene_core::raster_types::Raster; use graphene_core::raster_types::Raster;
use graphene_core::table::TableRow;
use std::collections::HashMap; use std::collections::HashMap;
use std::hash::Hash; use std::hash::Hash;
use std::hash::Hasher; use std::hash::Hasher;
@@ -20,12 +20,12 @@ struct BrushCacheImpl {
prev_input: Vec<BrushStroke>, prev_input: Vec<BrushStroke>,
// The strokes that have been fully processed and blended into the background. // The strokes that have been fully processed and blended into the background.
#[serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame_instance")] #[serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame_row")]
background: Instance<Raster<CPU>>, background: TableRow<Raster<CPU>>,
#[serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame_instance")] #[serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame_row")]
blended_image: Instance<Raster<CPU>>, blended_image: TableRow<Raster<CPU>>,
#[serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame_instance")] #[serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame_row")]
last_stroke_texture: Instance<Raster<CPU>>, last_stroke_texture: TableRow<Raster<CPU>>,
// A cache for brush textures. // A cache for brush textures.
#[serde(skip)] #[serde(skip)]
@@ -33,7 +33,7 @@ struct BrushCacheImpl {
} }
impl BrushCacheImpl { impl BrushCacheImpl {
fn compute_brush_plan(&mut self, mut background: Instance<Raster<CPU>>, input: &[BrushStroke]) -> BrushPlan { fn compute_brush_plan(&mut self, mut background: TableRow<Raster<CPU>>, input: &[BrushStroke]) -> BrushPlan {
// Do background invalidation. // Do background invalidation.
if background != self.background { if background != self.background {
self.background = background.clone(); self.background = background.clone();
@@ -60,8 +60,8 @@ impl BrushCacheImpl {
background = std::mem::take(&mut self.blended_image); background = std::mem::take(&mut self.blended_image);
// Check if the first non-blended stroke is an extension of the last one. // Check if the first non-blended stroke is an extension of the last one.
let mut first_stroke_texture = Instance { let mut first_stroke_texture = TableRow {
instance: Raster::<CPU>::default(), element: Raster::<CPU>::default(),
transform: glam::DAffine2::ZERO, transform: glam::DAffine2::ZERO,
..Default::default() ..Default::default()
}; };
@@ -88,7 +88,7 @@ impl BrushCacheImpl {
} }
} }
pub fn cache_results(&mut self, input: Vec<BrushStroke>, blended_image: Instance<Raster<CPU>>, last_stroke_texture: Instance<Raster<CPU>>) { pub fn cache_results(&mut self, input: Vec<BrushStroke>, blended_image: TableRow<Raster<CPU>>, last_stroke_texture: TableRow<Raster<CPU>>) {
self.prev_input = input; self.prev_input = input;
self.blended_image = blended_image; self.blended_image = blended_image;
self.last_stroke_texture = last_stroke_texture; self.last_stroke_texture = last_stroke_texture;
@@ -123,8 +123,8 @@ impl Hash for BrushCacheImpl {
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct BrushPlan { pub struct BrushPlan {
pub strokes: Vec<BrushStroke>, pub strokes: Vec<BrushStroke>,
pub background: Instance<Raster<CPU>>, pub background: TableRow<Raster<CPU>>,
pub first_stroke_texture: Instance<Raster<CPU>>, pub first_stroke_texture: TableRow<Raster<CPU>>,
pub first_stroke_point_skip: usize, pub first_stroke_point_skip: usize,
} }
@@ -160,12 +160,12 @@ impl Hash for BrushCache {
} }
impl BrushCache { impl BrushCache {
pub fn compute_brush_plan(&self, background: Instance<Raster<CPU>>, input: &[BrushStroke]) -> BrushPlan { pub fn compute_brush_plan(&self, background: TableRow<Raster<CPU>>, input: &[BrushStroke]) -> BrushPlan {
let mut inner = self.0.lock().unwrap(); let mut inner = self.0.lock().unwrap();
inner.compute_brush_plan(background, input) inner.compute_brush_plan(background, input)
} }
pub fn cache_results(&self, input: Vec<BrushStroke>, blended_image: Instance<Raster<CPU>>, last_stroke_texture: Instance<Raster<CPU>>) { pub fn cache_results(&self, input: Vec<BrushStroke>, blended_image: TableRow<Raster<CPU>>, last_stroke_texture: TableRow<Raster<CPU>>) {
let mut inner = self.0.lock().unwrap(); let mut inner = self.0.lock().unwrap();
inner.cache_results(input, blended_image, last_stroke_texture) inner.cache_results(input, blended_image, last_stroke_texture)
} }
+149
View File
@@ -0,0 +1,149 @@
use crate::blending::AlphaBlending;
use crate::bounds::BoundingBox;
use crate::math::quad::Quad;
use crate::raster_types::{CPU, GPU, Raster};
use crate::table::{Table, TableRow};
use crate::transform::TransformMut;
use crate::uuid::NodeId;
use crate::vector::VectorData;
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, Graphic, OwnedContextImpl};
use dyn_any::DynAny;
use glam::{DAffine2, DVec2, IVec2};
use std::hash::Hash;
/// Some [`ArtboardData`] with some optional clipping bounds that can be exported.
#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
pub struct Artboard {
pub graphic_group: Table<Graphic>,
pub label: String,
pub location: IVec2,
pub dimensions: IVec2,
pub background: Color,
pub clip: bool,
}
impl Default for Artboard {
fn default() -> Self {
Self::new(IVec2::ZERO, IVec2::new(1920, 1080))
}
}
impl Artboard {
pub fn new(location: IVec2, dimensions: IVec2) -> Self {
Self {
graphic_group: Table::new(),
label: "Artboard".to_string(),
location: location.min(location + dimensions),
dimensions: dimensions.abs(),
background: Color::WHITE,
clip: false,
}
}
}
impl BoundingBox for Artboard {
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> {
let artboard_bounds = (transform * Quad::from_box([self.location.as_dvec2(), self.location.as_dvec2() + self.dimensions.as_dvec2()])).bounding_box();
if self.clip {
Some(artboard_bounds)
} else {
[self.graphic_group.bounding_box(transform, include_stroke), Some(artboard_bounds)]
.into_iter()
.flatten()
.reduce(Quad::combine_bounds)
}
}
}
// TODO: Eventually remove this migration document upgrade code
pub fn migrate_artboard_group<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Artboard>, D::Error> {
use serde::Deserialize;
#[derive(Clone, Default, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
pub struct ArtboardGroup {
pub artboards: Vec<(Artboard, Option<NodeId>)>,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
enum EitherFormat {
ArtboardGroup(ArtboardGroup),
ArtboardGroupTable(Table<Artboard>),
}
Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::ArtboardGroup(artboard_group) => {
let mut table = Table::new();
for (artboard, source_node_id) in artboard_group.artboards {
table.push(TableRow {
element: artboard,
transform: DAffine2::IDENTITY,
alpha_blending: AlphaBlending::default(),
source_node_id,
});
}
table
}
EitherFormat::ArtboardGroupTable(artboard_group_table) => artboard_group_table,
})
}
impl BoundingBox for Table<Artboard> {
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> {
self.iter_ref().filter_map(|row| row.element.bounding_box(transform, include_stroke)).reduce(Quad::combine_bounds)
}
}
#[node_macro::node(category(""))]
async fn to_artboard<Data: Into<Table<Graphic>> + 'n>(
ctx: impl ExtractAll + CloneVarArgs + Ctx,
#[implementations(
Context -> Table<Graphic>,
Context -> Table<VectorData>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
Context -> DAffine2,
)]
contents: impl Node<Context<'static>, Output = Data>,
label: String,
location: DVec2,
dimensions: DVec2,
background: Color,
clip: bool,
) -> Artboard {
let location = location.as_ivec2();
let dimensions = dimensions.as_ivec2().max(IVec2::ONE);
let footprint = ctx.try_footprint().copied();
let mut new_ctx = OwnedContextImpl::from(ctx);
if let Some(mut footprint) = footprint {
footprint.translate(location.as_dvec2());
new_ctx = new_ctx.with_footprint(footprint);
}
let graphic_group = contents.eval(new_ctx.into_context()).await;
Artboard {
graphic_group: graphic_group.into(),
label,
location: location.min(location + dimensions),
dimensions: dimensions.abs(),
background,
clip,
}
}
#[node_macro::node(category(""))]
pub async fn append_artboard(_ctx: impl Ctx, mut artboards: Table<Artboard>, artboard: Artboard, node_path: Vec<NodeId>) -> Table<Artboard> {
// Get the penultimate element of the node path, or None if the path is too short.
// This is used to get the ID of the user-facing "Artboard" node (which encapsulates this internal "Append Artboard" node).
let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
artboards.push(TableRow {
element: artboard,
transform: DAffine2::IDENTITY,
alpha_blending: AlphaBlending::default(),
source_node_id: encapsulating_node_id,
});
artboards
}
+52 -55
View File
@@ -1,8 +1,8 @@
use crate::raster::Image; use crate::raster_types::{CPU, Raster};
use crate::raster_types::{CPU, RasterDataTable};
use crate::registry::types::Percentage; use crate::registry::types::Percentage;
use crate::vector::VectorDataTable; use crate::table::Table;
use crate::{BlendMode, Color, Ctx, GraphicElement, GraphicGroupTable}; use crate::vector::VectorData;
use crate::{BlendMode, Color, Ctx, Graphic};
pub(super) trait MultiplyAlpha { pub(super) trait MultiplyAlpha {
fn multiply_alpha(&mut self, factor: f64); fn multiply_alpha(&mut self, factor: f64);
@@ -13,27 +13,24 @@ impl MultiplyAlpha for Color {
*self = Color::from_rgbaf32_unchecked(self.r(), self.g(), self.b(), (self.a() * factor as f32).clamp(0., 1.)) *self = Color::from_rgbaf32_unchecked(self.r(), self.g(), self.b(), (self.a() * factor as f32).clamp(0., 1.))
} }
} }
impl MultiplyAlpha for VectorDataTable { impl MultiplyAlpha for Table<VectorData> {
fn multiply_alpha(&mut self, factor: f64) { fn multiply_alpha(&mut self, factor: f64) {
for instance in self.instance_mut_iter() { for row in self.iter_mut() {
instance.alpha_blending.opacity *= factor as f32; row.alpha_blending.opacity *= factor as f32;
} }
} }
} }
impl MultiplyAlpha for GraphicGroupTable { impl MultiplyAlpha for Table<Graphic> {
fn multiply_alpha(&mut self, factor: f64) { fn multiply_alpha(&mut self, factor: f64) {
for instance in self.instance_mut_iter() { for row in self.iter_mut() {
instance.alpha_blending.opacity *= factor as f32; row.alpha_blending.opacity *= factor as f32;
} }
} }
} }
impl MultiplyAlpha for RasterDataTable<CPU> impl MultiplyAlpha for Table<Raster<CPU>> {
where
GraphicElement: From<Image<Color>>,
{
fn multiply_alpha(&mut self, factor: f64) { fn multiply_alpha(&mut self, factor: f64) {
for instance in self.instance_mut_iter() { for row in self.iter_mut() {
instance.alpha_blending.opacity *= factor as f32; row.alpha_blending.opacity *= factor as f32;
} }
} }
} }
@@ -46,24 +43,24 @@ impl MultiplyFill for Color {
*self = Color::from_rgbaf32_unchecked(self.r(), self.g(), self.b(), (self.a() * factor as f32).clamp(0., 1.)) *self = Color::from_rgbaf32_unchecked(self.r(), self.g(), self.b(), (self.a() * factor as f32).clamp(0., 1.))
} }
} }
impl MultiplyFill for VectorDataTable { impl MultiplyFill for Table<VectorData> {
fn multiply_fill(&mut self, factor: f64) { fn multiply_fill(&mut self, factor: f64) {
for instance in self.instance_mut_iter() { for row in self.iter_mut() {
instance.alpha_blending.fill *= factor as f32; row.alpha_blending.fill *= factor as f32;
} }
} }
} }
impl MultiplyFill for GraphicGroupTable { impl MultiplyFill for Table<Graphic> {
fn multiply_fill(&mut self, factor: f64) { fn multiply_fill(&mut self, factor: f64) {
for instance in self.instance_mut_iter() { for row in self.iter_mut() {
instance.alpha_blending.fill *= factor as f32; row.alpha_blending.fill *= factor as f32;
} }
} }
} }
impl MultiplyFill for RasterDataTable<CPU> { impl MultiplyFill for Table<Raster<CPU>> {
fn multiply_fill(&mut self, factor: f64) { fn multiply_fill(&mut self, factor: f64) {
for instance in self.instance_mut_iter() { for row in self.iter_mut() {
instance.alpha_blending.fill *= factor as f32; row.alpha_blending.fill *= factor as f32;
} }
} }
} }
@@ -72,24 +69,24 @@ trait SetBlendMode {
fn set_blend_mode(&mut self, blend_mode: BlendMode); fn set_blend_mode(&mut self, blend_mode: BlendMode);
} }
impl SetBlendMode for VectorDataTable { impl SetBlendMode for Table<VectorData> {
fn set_blend_mode(&mut self, blend_mode: BlendMode) { fn set_blend_mode(&mut self, blend_mode: BlendMode) {
for instance in self.instance_mut_iter() { for row in self.iter_mut() {
instance.alpha_blending.blend_mode = blend_mode; row.alpha_blending.blend_mode = blend_mode;
} }
} }
} }
impl SetBlendMode for GraphicGroupTable { impl SetBlendMode for Table<Graphic> {
fn set_blend_mode(&mut self, blend_mode: BlendMode) { fn set_blend_mode(&mut self, blend_mode: BlendMode) {
for instance in self.instance_mut_iter() { for row in self.iter_mut() {
instance.alpha_blending.blend_mode = blend_mode; row.alpha_blending.blend_mode = blend_mode;
} }
} }
} }
impl SetBlendMode for RasterDataTable<CPU> { impl SetBlendMode for Table<Raster<CPU>> {
fn set_blend_mode(&mut self, blend_mode: BlendMode) { fn set_blend_mode(&mut self, blend_mode: BlendMode) {
for instance in self.instance_mut_iter() { for row in self.iter_mut() {
instance.alpha_blending.blend_mode = blend_mode; row.alpha_blending.blend_mode = blend_mode;
} }
} }
} }
@@ -98,24 +95,24 @@ trait SetClip {
fn set_clip(&mut self, clip: bool); fn set_clip(&mut self, clip: bool);
} }
impl SetClip for VectorDataTable { impl SetClip for Table<VectorData> {
fn set_clip(&mut self, clip: bool) { fn set_clip(&mut self, clip: bool) {
for instance in self.instance_mut_iter() { for row in self.iter_mut() {
instance.alpha_blending.clip = clip; row.alpha_blending.clip = clip;
} }
} }
} }
impl SetClip for GraphicGroupTable { impl SetClip for Table<Graphic> {
fn set_clip(&mut self, clip: bool) { fn set_clip(&mut self, clip: bool) {
for instance in self.instance_mut_iter() { for row in self.iter_mut() {
instance.alpha_blending.clip = clip; row.alpha_blending.clip = clip;
} }
} }
} }
impl SetClip for RasterDataTable<CPU> { impl SetClip for Table<Raster<CPU>> {
fn set_clip(&mut self, clip: bool) { fn set_clip(&mut self, clip: bool) {
for instance in self.instance_mut_iter() { for row in self.iter_mut() {
instance.alpha_blending.clip = clip; row.alpha_blending.clip = clip;
} }
} }
} }
@@ -124,14 +121,14 @@ impl SetClip for RasterDataTable<CPU> {
fn blend_mode<T: SetBlendMode>( fn blend_mode<T: SetBlendMode>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
GraphicGroupTable, Table<Graphic>,
VectorDataTable, Table<VectorData>,
RasterDataTable<CPU>, Table<Raster<CPU>>,
)] )]
mut value: T, mut value: T,
blend_mode: BlendMode, blend_mode: BlendMode,
) -> T { ) -> T {
// TODO: Find a way to make this apply once to the table's parent (i.e. its row in its parent table or Instance<T>) rather than applying to each row in its own table, which produces the undesired result // TODO: Find a way to make this apply once to the table's parent (i.e. its row in its parent table or TableRow<T>) rather than applying to each row in its own table, which produces the undesired result
value.set_blend_mode(blend_mode); value.set_blend_mode(blend_mode);
value value
} }
@@ -140,14 +137,14 @@ fn blend_mode<T: SetBlendMode>(
fn opacity<T: MultiplyAlpha>( fn opacity<T: MultiplyAlpha>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
GraphicGroupTable, Table<Graphic>,
VectorDataTable, Table<VectorData>,
RasterDataTable<CPU>, Table<Raster<CPU>>,
)] )]
mut value: T, mut value: T,
#[default(100.)] opacity: Percentage, #[default(100.)] opacity: Percentage,
) -> T { ) -> T {
// TODO: Find a way to make this apply once to the table's parent (i.e. its row in its parent table or Instance<T>) rather than applying to each row in its own table, which produces the undesired result // TODO: Find a way to make this apply once to the table's parent (i.e. its row in its parent table or TableRow<T>) rather than applying to each row in its own table, which produces the undesired result
value.multiply_alpha(opacity / 100.); value.multiply_alpha(opacity / 100.);
value value
} }
@@ -156,9 +153,9 @@ fn opacity<T: MultiplyAlpha>(
fn blending<T: SetBlendMode + MultiplyAlpha + MultiplyFill + SetClip>( fn blending<T: SetBlendMode + MultiplyAlpha + MultiplyFill + SetClip>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
GraphicGroupTable, Table<Graphic>,
VectorDataTable, Table<VectorData>,
RasterDataTable<CPU>, Table<Raster<CPU>>,
)] )]
mut value: T, mut value: T,
blend_mode: BlendMode, blend_mode: BlendMode,
@@ -166,7 +163,7 @@ fn blending<T: SetBlendMode + MultiplyAlpha + MultiplyFill + SetClip>(
#[default(100.)] fill: Percentage, #[default(100.)] fill: Percentage,
#[default(false)] clip: bool, #[default(false)] clip: bool,
) -> T { ) -> T {
// TODO: Find a way to make this apply once to the table's parent (i.e. its row in its parent table or Instance<T>) rather than applying to each row in its own table, which produces the undesired result // TODO: Find a way to make this apply once to the table's parent (i.e. its row in its parent table or TableRow<T>) rather than applying to each row in its own table, which produces the undesired result
value.set_blend_mode(blend_mode); value.set_blend_mode(blend_mode);
value.multiply_alpha(opacity / 100.); value.multiply_alpha(opacity / 100.);
value.multiply_fill(fill / 100.); value.multiply_fill(fill / 100.);
+5 -4
View File
@@ -1,10 +1,11 @@
use crate::raster_types::{CPU, RasterDataTable}; use crate::raster_types::{CPU, Raster};
use crate::vector::VectorDataTable; use crate::table::Table;
use crate::vector::VectorData;
use crate::{Color, Ctx}; use crate::{Color, Ctx};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
#[node_macro::node(category("Debug"), name("Log to Console"))] #[node_macro::node(category("Debug"), name("Log to Console"))]
fn log_to_console<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2, Color, Option<Color>)] value: T) -> T { fn log_to_console<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, Table<VectorData>, DAffine2, Color, Option<Color>)] value: T) -> T {
// KEEP THIS `debug!()` - It acts as the output for the debug node itself // KEEP THIS `debug!()` - It acts as the output for the debug node itself
log::debug!("{:#?}", value); log::debug!("{:#?}", value);
value value
@@ -30,6 +31,6 @@ fn unwrap<T: Default>(_: impl Ctx, #[implementations(Option<f64>, Option<f32>, O
/// Meant for debugging purposes, not general use. Clones the input value. /// Meant for debugging purposes, not general use. Clones the input value.
#[node_macro::node(category("Debug"))] #[node_macro::node(category("Debug"))]
fn clone<'i, T: Clone + 'i>(_: impl Ctx, #[implementations(&RasterDataTable<CPU>)] value: &'i T) -> T { fn clone<'i, T: Clone + 'i>(_: impl Ctx, #[implementations(&Table<Raster<CPU>>)] value: &'i T) -> T {
value.clone() value.clone()
} }
+238 -384
View File
@@ -1,191 +1,174 @@
use crate::blending::AlphaBlending; use crate::blending::AlphaBlending;
use crate::bounds::BoundingBox; use crate::bounds::BoundingBox;
use crate::instances::{Instance, Instances};
use crate::math::quad::Quad; use crate::math::quad::Quad;
use crate::raster::image::Image; use crate::raster_types::{CPU, GPU, Raster};
use crate::raster_types::{CPU, GPU, Raster, RasterDataTable}; use crate::table::{Table, TableRow};
use crate::transform::TransformMut;
use crate::uuid::NodeId; use crate::uuid::NodeId;
use crate::vector::{VectorData, VectorDataTable}; use crate::vector::VectorData;
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl}; use crate::{Color, Ctx};
use dyn_any::DynAny; use dyn_any::DynAny;
use glam::{DAffine2, DVec2, IVec2}; use glam::{DAffine2, DVec2};
use std::hash::Hash; use std::hash::Hash;
// TODO: Eventually remove this migration document upgrade code /// The possible forms of graphical content that can be rendered by the Render node into either an image or SVG syntax.
pub fn migrate_graphic_group<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<GraphicGroupTable, D::Error> {
use serde::Deserialize;
#[derive(Clone, Debug, PartialEq, DynAny, Default, serde::Serialize, serde::Deserialize)]
pub struct OldGraphicGroup {
elements: Vec<(GraphicElement, Option<NodeId>)>,
transform: DAffine2,
alpha_blending: AlphaBlending,
}
#[derive(Clone, Debug, PartialEq, DynAny, Default, serde::Serialize, serde::Deserialize)]
pub struct GraphicGroup {
elements: Vec<(GraphicElement, Option<NodeId>)>,
}
pub type OldGraphicGroupTable = Instances<GraphicGroup>;
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
enum EitherFormat {
OldGraphicGroup(OldGraphicGroup),
InstanceTable(serde_json::Value),
}
Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::OldGraphicGroup(old) => {
let mut graphic_group_table = GraphicGroupTable::default();
for (graphic_element, source_node_id) in old.elements {
graphic_group_table.push(Instance {
instance: graphic_element,
transform: old.transform,
alpha_blending: old.alpha_blending,
source_node_id,
});
}
graphic_group_table
}
EitherFormat::InstanceTable(value) => {
// Try to deserialize as either table format
if let Ok(old_table) = serde_json::from_value::<OldGraphicGroupTable>(value.clone()) {
let mut graphic_group_table = GraphicGroupTable::default();
for instance in old_table.instance_ref_iter() {
for (graphic_element, source_node_id) in &instance.instance.elements {
graphic_group_table.push(Instance {
instance: graphic_element.clone(),
transform: *instance.transform,
alpha_blending: *instance.alpha_blending,
source_node_id: *source_node_id,
});
}
}
graphic_group_table
} else if let Ok(new_table) = serde_json::from_value::<GraphicGroupTable>(value) {
new_table
} else {
return Err(serde::de::Error::custom("Failed to deserialize GraphicGroupTable"));
}
}
})
}
// TODO: Rename to GraphicElementTable
pub type GraphicGroupTable = Instances<GraphicElement>;
impl From<VectorData> for GraphicGroupTable {
fn from(vector_data: VectorData) -> Self {
Self::new(GraphicElement::VectorData(VectorDataTable::new(vector_data)))
}
}
impl From<VectorDataTable> for GraphicGroupTable {
fn from(vector_data: VectorDataTable) -> Self {
Self::new(GraphicElement::VectorData(vector_data))
}
}
impl From<Image<Color>> for GraphicGroupTable {
fn from(image: Image<Color>) -> Self {
Self::new(GraphicElement::RasterDataCPU(RasterDataTable::<CPU>::new(Raster::new_cpu(image))))
}
}
impl From<RasterDataTable<CPU>> for GraphicGroupTable {
fn from(raster_data_table: RasterDataTable<CPU>) -> Self {
Self::new(GraphicElement::RasterDataCPU(raster_data_table))
}
}
impl From<RasterDataTable<GPU>> for GraphicGroupTable {
fn from(raster_data_table: RasterDataTable<GPU>) -> Self {
Self::new(GraphicElement::RasterDataGPU(raster_data_table))
}
}
impl From<DAffine2> for GraphicGroupTable {
fn from(_: DAffine2) -> Self {
GraphicGroupTable::default()
}
}
/// The possible forms of graphical content held in a Vec by the `elements` field of [`GraphicElement`].
#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)] #[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
pub enum GraphicElement { pub enum Graphic {
/// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g /// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g
GraphicGroup(GraphicGroupTable), GraphicGroup(Table<Graphic>),
/// A vector shape, equivalent to the SVG <path> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path /// A vector shape, equivalent to the SVG <path> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
VectorData(VectorDataTable), VectorData(Table<VectorData>),
RasterDataCPU(RasterDataTable<CPU>), RasterDataCPU(Table<Raster<CPU>>),
RasterDataGPU(RasterDataTable<GPU>), RasterDataGPU(Table<Raster<GPU>>),
} }
impl Default for GraphicElement { impl Default for Graphic {
fn default() -> Self { fn default() -> Self {
Self::GraphicGroup(GraphicGroupTable::default()) Self::GraphicGroup(Default::default())
} }
} }
impl From<DAffine2> for GraphicElement { // GraphicGroup
impl From<Table<Graphic>> for Graphic {
fn from(graphic_group: Table<Graphic>) -> Self {
Graphic::GraphicGroup(graphic_group)
}
}
// VectorData
impl From<VectorData> for Graphic {
fn from(vector_data: VectorData) -> Self {
Graphic::VectorData(Table::new_from_element(vector_data))
}
}
impl From<Table<VectorData>> for Graphic {
fn from(vector_data: Table<VectorData>) -> Self {
Graphic::VectorData(vector_data)
}
}
impl From<VectorData> for Table<Graphic> {
fn from(vector_data: VectorData) -> Self {
Table::new_from_element(Graphic::VectorData(Table::new_from_element(vector_data)))
}
}
impl From<Table<VectorData>> for Table<Graphic> {
fn from(vector_data: Table<VectorData>) -> Self {
Table::new_from_element(Graphic::VectorData(vector_data))
}
}
// Raster<CPU>
impl From<Raster<CPU>> for Graphic {
fn from(raster_data: Raster<CPU>) -> Self {
Graphic::RasterDataCPU(Table::new_from_element(raster_data))
}
}
impl From<Table<Raster<CPU>>> for Graphic {
fn from(raster_data: Table<Raster<CPU>>) -> Self {
Graphic::RasterDataCPU(raster_data)
}
}
impl From<Raster<CPU>> for Table<Graphic> {
fn from(raster_data: Raster<CPU>) -> Self {
Table::new_from_element(Graphic::RasterDataCPU(Table::new_from_element(raster_data)))
}
}
impl From<Table<Raster<CPU>>> for Table<Graphic> {
fn from(raster_data_table: Table<Raster<CPU>>) -> Self {
Table::new_from_element(Graphic::RasterDataCPU(raster_data_table))
}
}
// Raster<GPU>
impl From<Raster<GPU>> for Graphic {
fn from(raster_data: Raster<GPU>) -> Self {
Graphic::RasterDataGPU(Table::new_from_element(raster_data))
}
}
impl From<Table<Raster<GPU>>> for Graphic {
fn from(raster_data: Table<Raster<GPU>>) -> Self {
Graphic::RasterDataGPU(raster_data)
}
}
impl From<Raster<GPU>> for Table<Graphic> {
fn from(raster_data: Raster<GPU>) -> Self {
Table::new_from_element(Graphic::RasterDataGPU(Table::new_from_element(raster_data)))
}
}
impl From<Table<Raster<GPU>>> for Table<Graphic> {
fn from(raster_data_table: Table<Raster<GPU>>) -> Self {
Table::new_from_element(Graphic::RasterDataGPU(raster_data_table))
}
}
// DAffine2
impl From<DAffine2> for Graphic {
fn from(_: DAffine2) -> Self { fn from(_: DAffine2) -> Self {
GraphicElement::default() Graphic::default()
}
}
impl From<DAffine2> for Table<Graphic> {
fn from(_: DAffine2) -> Self {
Table::new()
} }
} }
impl GraphicElement { impl Graphic {
pub fn as_group(&self) -> Option<&GraphicGroupTable> { pub fn as_group(&self) -> Option<&Table<Graphic>> {
match self { match self {
GraphicElement::GraphicGroup(group) => Some(group), Graphic::GraphicGroup(group) => Some(group),
_ => None, _ => None,
} }
} }
pub fn as_group_mut(&mut self) -> Option<&mut GraphicGroupTable> { pub fn as_group_mut(&mut self) -> Option<&mut Table<Graphic>> {
match self { match self {
GraphicElement::GraphicGroup(group) => Some(group), Graphic::GraphicGroup(group) => Some(group),
_ => None, _ => None,
} }
} }
pub fn as_vector_data(&self) -> Option<&VectorDataTable> { pub fn as_vector_data(&self) -> Option<&Table<VectorData>> {
match self { match self {
GraphicElement::VectorData(data) => Some(data), Graphic::VectorData(data) => Some(data),
_ => None, _ => None,
} }
} }
pub fn as_vector_data_mut(&mut self) -> Option<&mut VectorDataTable> { pub fn as_vector_data_mut(&mut self) -> Option<&mut Table<VectorData>> {
match self { match self {
GraphicElement::VectorData(data) => Some(data), Graphic::VectorData(data) => Some(data),
_ => None, _ => None,
} }
} }
pub fn as_raster(&self) -> Option<&RasterDataTable<CPU>> { pub fn as_raster(&self) -> Option<&Table<Raster<CPU>>> {
match self { match self {
GraphicElement::RasterDataCPU(raster) => Some(raster), Graphic::RasterDataCPU(raster) => Some(raster),
_ => None, _ => None,
} }
} }
pub fn as_raster_mut(&mut self) -> Option<&mut RasterDataTable<CPU>> { pub fn as_raster_mut(&mut self) -> Option<&mut Table<Raster<CPU>>> {
match self { match self {
GraphicElement::RasterDataCPU(raster) => Some(raster), Graphic::RasterDataCPU(raster) => Some(raster),
_ => None, _ => None,
} }
} }
pub fn had_clip_enabled(&self) -> bool { pub fn had_clip_enabled(&self) -> bool {
match self { match self {
GraphicElement::VectorData(data) => data.instance_ref_iter().all(|instance| instance.alpha_blending.clip), Graphic::VectorData(data) => data.iter_ref().all(|row| row.alpha_blending.clip),
GraphicElement::GraphicGroup(data) => data.instance_ref_iter().all(|instance| instance.alpha_blending.clip), Graphic::GraphicGroup(data) => data.iter_ref().all(|row| row.alpha_blending.clip),
GraphicElement::RasterDataCPU(data) => data.instance_ref_iter().all(|instance| instance.alpha_blending.clip), Graphic::RasterDataCPU(data) => data.iter_ref().all(|row| row.alpha_blending.clip),
GraphicElement::RasterDataGPU(data) => data.instance_ref_iter().all(|instance| instance.alpha_blending.clip), Graphic::RasterDataGPU(data) => data.iter_ref().all(|row| row.alpha_blending.clip),
} }
} }
pub fn can_reduce_to_clip_path(&self) -> bool { pub fn can_reduce_to_clip_path(&self) -> bool {
match self { match self {
GraphicElement::VectorData(vector_data_table) => vector_data_table.instance_ref_iter().all(|instance_data| { Graphic::VectorData(vector_data_table) => vector_data_table.iter_ref().all(|row| {
let style = &instance_data.instance.style; let style = &row.element.style;
let alpha_blending = &instance_data.alpha_blending; let alpha_blending = &row.alpha_blending;
(alpha_blending.opacity > 1. - f32::EPSILON) && style.fill().is_opaque() && style.stroke().is_none_or(|stroke| !stroke.has_renderable_stroke()) (alpha_blending.opacity > 1. - f32::EPSILON) && style.fill().is_opaque() && style.stroke().is_none_or(|stroke| !stroke.has_renderable_stroke())
}), }),
_ => false, _ => false,
@@ -193,108 +176,21 @@ impl GraphicElement {
} }
} }
impl BoundingBox for GraphicElement { impl BoundingBox for Graphic {
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> { fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> {
match self { match self {
GraphicElement::VectorData(vector_data) => vector_data.bounding_box(transform, include_stroke), Graphic::VectorData(vector_data) => vector_data.bounding_box(transform, include_stroke),
GraphicElement::RasterDataCPU(raster) => raster.bounding_box(transform, include_stroke), Graphic::RasterDataCPU(raster) => raster.bounding_box(transform, include_stroke),
GraphicElement::RasterDataGPU(raster) => raster.bounding_box(transform, include_stroke), Graphic::RasterDataGPU(raster) => raster.bounding_box(transform, include_stroke),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.bounding_box(transform, include_stroke), Graphic::GraphicGroup(graphic_group) => graphic_group.bounding_box(transform, include_stroke),
} }
} }
} }
impl BoundingBox for GraphicGroupTable { impl BoundingBox for Table<Graphic> {
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> { fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> {
self.instance_ref_iter() self.iter_ref()
.filter_map(|element| element.instance.bounding_box(transform * *element.transform, include_stroke)) .filter_map(|element| element.element.bounding_box(transform * *element.transform, include_stroke))
.reduce(Quad::combine_bounds)
}
}
/// Some [`ArtboardData`] with some optional clipping bounds that can be exported.
#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
pub struct Artboard {
pub graphic_group: GraphicGroupTable,
pub label: String,
pub location: IVec2,
pub dimensions: IVec2,
pub background: Color,
pub clip: bool,
}
impl Default for Artboard {
fn default() -> Self {
Self::new(IVec2::ZERO, IVec2::new(1920, 1080))
}
}
impl Artboard {
pub fn new(location: IVec2, dimensions: IVec2) -> Self {
Self {
graphic_group: GraphicGroupTable::default(),
label: "Artboard".to_string(),
location: location.min(location + dimensions),
dimensions: dimensions.abs(),
background: Color::WHITE,
clip: false,
}
}
}
impl BoundingBox for Artboard {
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> {
let artboard_bounds = (transform * Quad::from_box([self.location.as_dvec2(), self.location.as_dvec2() + self.dimensions.as_dvec2()])).bounding_box();
if self.clip {
Some(artboard_bounds)
} else {
[self.graphic_group.bounding_box(transform, include_stroke), Some(artboard_bounds)]
.into_iter()
.flatten()
.reduce(Quad::combine_bounds)
}
}
}
// TODO: Eventually remove this migration document upgrade code
pub fn migrate_artboard_group<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<ArtboardGroupTable, D::Error> {
use serde::Deserialize;
#[derive(Clone, Default, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
pub struct ArtboardGroup {
pub artboards: Vec<(Artboard, Option<NodeId>)>,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
enum EitherFormat {
ArtboardGroup(ArtboardGroup),
ArtboardGroupTable(ArtboardGroupTable),
}
Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::ArtboardGroup(artboard_group) => {
let mut table = ArtboardGroupTable::default();
for (artboard, source_node_id) in artboard_group.artboards {
table.push(Instance {
instance: artboard,
transform: DAffine2::IDENTITY,
alpha_blending: AlphaBlending::default(),
source_node_id,
});
}
table
}
EitherFormat::ArtboardGroupTable(artboard_group_table) => artboard_group_table,
})
}
pub type ArtboardGroupTable = Instances<Artboard>;
impl BoundingBox for ArtboardGroupTable {
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> {
self.instance_ref_iter()
.filter_map(|instance| instance.instance.bounding_box(transform, include_stroke))
.reduce(Quad::combine_bounds) .reduce(Quad::combine_bounds)
} }
} }
@@ -302,15 +198,15 @@ impl BoundingBox for ArtboardGroupTable {
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
async fn layer<I: 'n + Send + Clone>( async fn layer<I: 'n + Send + Clone>(
_: impl Ctx, _: impl Ctx,
#[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable<CPU>, RasterDataTable<GPU>)] mut stack: Instances<I>, #[implementations(Table<Graphic>, Table<VectorData>, Table<Raster<CPU>>, Table<Raster<GPU>>)] mut stack: Table<I>,
#[implementations(GraphicElement, VectorData, Raster<CPU>, Raster<GPU>)] element: I, #[implementations(Graphic, VectorData, Raster<CPU>, Raster<GPU>)] element: I,
node_path: Vec<NodeId>, node_path: Vec<NodeId>,
) -> Instances<I> { ) -> Table<I> {
// Get the penultimate element of the node path, or None if the path is too short // Get the penultimate element of the node path, or None if the path is too short
let source_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied(); let source_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
stack.push(Instance { stack.push(TableRow {
instance: element, element,
transform: DAffine2::IDENTITY, transform: DAffine2::IDENTITY,
alpha_blending: AlphaBlending::default(), alpha_blending: AlphaBlending::default(),
source_node_id, source_node_id,
@@ -320,60 +216,60 @@ async fn layer<I: 'n + Send + Clone>(
} }
#[node_macro::node(category("Debug"))] #[node_macro::node(category("Debug"))]
async fn to_element<Data: Into<GraphicElement> + 'n>( async fn to_element<Data: Into<Graphic> + 'n>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
GraphicGroupTable, Table<Graphic>,
VectorDataTable, Table<VectorData>,
RasterDataTable<CPU>, Table<Raster<CPU>>,
RasterDataTable<GPU>, Table<Raster<GPU>>,
DAffine2, DAffine2,
)] )]
data: Data, data: Data,
) -> GraphicElement { ) -> Graphic {
data.into() data.into()
} }
#[node_macro::node(category("General"))] #[node_macro::node(category("General"))]
async fn to_group<Data: Into<GraphicGroupTable> + 'n>( async fn to_group<Data: Into<Table<Graphic>> + 'n>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
GraphicGroupTable, Table<Graphic>,
VectorDataTable, Table<VectorData>,
RasterDataTable<CPU>, Table<Raster<CPU>>,
RasterDataTable<GPU>, Table<Raster<GPU>>,
)] )]
element: Data, element: Data,
) -> GraphicGroupTable { ) -> Table<Graphic> {
element.into() element.into()
} }
#[node_macro::node(category("General"))] #[node_macro::node(category("General"))]
async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: bool) -> GraphicGroupTable { async fn flatten_group(_: impl Ctx, group: Table<Graphic>, fully_flatten: bool) -> Table<Graphic> {
// TODO: Avoid mutable reference, instead return a new GraphicGroupTable? // TODO: Avoid mutable reference, instead return a new Table<Graphic>?
fn flatten_group(output_group_table: &mut GraphicGroupTable, current_group_table: GraphicGroupTable, fully_flatten: bool, recursion_depth: usize) { fn flatten_group(output_group_table: &mut Table<Graphic>, current_group_table: Table<Graphic>, fully_flatten: bool, recursion_depth: usize) {
for current_instance in current_group_table.instance_ref_iter() { for current_row in current_group_table.iter_ref() {
let current_element = current_instance.instance.clone(); let current_element = current_row.element.clone();
let reference = *current_instance.source_node_id; let reference = *current_row.source_node_id;
let recurse = fully_flatten || recursion_depth == 0; let recurse = fully_flatten || recursion_depth == 0;
match current_element { match current_element {
// If we're allowed to recurse, flatten any GraphicGroups we encounter // If we're allowed to recurse, flatten any GraphicGroups we encounter
GraphicElement::GraphicGroup(mut current_element) if recurse => { Graphic::GraphicGroup(mut current_element) if recurse => {
// Apply the parent group's transform to all child elements // Apply the parent group's transform to all child elements
for graphic_element in current_element.instance_mut_iter() { for graphic_element in current_element.iter_mut() {
*graphic_element.transform = *current_instance.transform * *graphic_element.transform; *graphic_element.transform = *current_row.transform * *graphic_element.transform;
} }
flatten_group(output_group_table, current_element, fully_flatten, recursion_depth + 1); flatten_group(output_group_table, current_element, fully_flatten, recursion_depth + 1);
} }
// Handle any leaf elements we encounter, which can be either non-GraphicGroup elements or GraphicGroups that we don't want to flatten // Handle any leaf elements we encounter, which can be either non-GraphicGroup elements or GraphicGroups that we don't want to flatten
_ => { _ => {
output_group_table.push(Instance { output_group_table.push(TableRow {
instance: current_element, element: current_element,
transform: *current_instance.transform, transform: *current_row.transform,
alpha_blending: *current_instance.alpha_blending, alpha_blending: *current_row.alpha_blending,
source_node_id: reference, source_node_id: reference,
}); });
} }
@@ -381,41 +277,41 @@ async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: boo
} }
} }
let mut output = GraphicGroupTable::default(); let mut output = Table::new();
flatten_group(&mut output, group, fully_flatten, 0); flatten_group(&mut output, group, fully_flatten, 0);
output output
} }
#[node_macro::node(category("Vector"))] #[node_macro::node(category("Vector"))]
async fn flatten_vector(_: impl Ctx, group: GraphicGroupTable) -> VectorDataTable { async fn flatten_vector(_: impl Ctx, group: Table<Graphic>) -> Table<VectorData> {
// TODO: Avoid mutable reference, instead return a new GraphicGroupTable? // TODO: Avoid mutable reference, instead return a new Table<Graphic>?
fn flatten_group(output_group_table: &mut VectorDataTable, current_group_table: GraphicGroupTable) { fn flatten_group(output_group_table: &mut Table<VectorData>, current_group_table: Table<Graphic>) {
for current_instance in current_group_table.instance_ref_iter() { for current_graphic_element_row in current_group_table.iter_ref() {
let current_element = current_instance.instance.clone(); let current_element = current_graphic_element_row.element.clone();
let reference = *current_instance.source_node_id; let reference = *current_graphic_element_row.source_node_id;
match current_element { match current_element {
// If we're allowed to recurse, flatten any GraphicGroups we encounter // If we're allowed to recurse, flatten any GraphicGroups we encounter
GraphicElement::GraphicGroup(mut current_element) => { Graphic::GraphicGroup(mut current_element) => {
// Apply the parent group's transform to all child elements // Apply the parent group's transform to all child elements
for graphic_element in current_element.instance_mut_iter() { for graphic_element in current_element.iter_mut() {
*graphic_element.transform = *current_instance.transform * *graphic_element.transform; *graphic_element.transform = *current_graphic_element_row.transform * *graphic_element.transform;
} }
flatten_group(output_group_table, current_element); flatten_group(output_group_table, current_element);
} }
// Handle any leaf elements we encounter, which can be either non-GraphicGroup elements or GraphicGroups that we don't want to flatten // Handle any leaf elements we encounter, which can be either non-GraphicGroup elements or GraphicGroups that we don't want to flatten
GraphicElement::VectorData(vector_instance) => { Graphic::VectorData(vector_table) => {
for current_element in vector_instance.instance_ref_iter() { for current_vector_row in vector_table.iter_ref() {
output_group_table.push(Instance { output_group_table.push(TableRow {
instance: current_element.instance.clone(), element: current_vector_row.element.clone(),
transform: *current_instance.transform * *current_element.transform, transform: *current_graphic_element_row.transform * *current_vector_row.transform,
alpha_blending: AlphaBlending { alpha_blending: AlphaBlending {
blend_mode: current_element.alpha_blending.blend_mode, blend_mode: current_vector_row.alpha_blending.blend_mode,
opacity: current_instance.alpha_blending.opacity * current_element.alpha_blending.opacity, opacity: current_graphic_element_row.alpha_blending.opacity * current_vector_row.alpha_blending.opacity,
fill: current_element.alpha_blending.fill, fill: current_vector_row.alpha_blending.fill,
clip: current_element.alpha_blending.clip, clip: current_vector_row.alpha_blending.clip,
}, },
source_node_id: reference, source_node_id: reference,
}); });
@@ -426,113 +322,12 @@ async fn flatten_vector(_: impl Ctx, group: GraphicGroupTable) -> VectorDataTabl
} }
} }
let mut output = VectorDataTable::default(); let mut output = Table::new();
flatten_group(&mut output, group); flatten_group(&mut output, group);
output output
} }
#[node_macro::node(category(""))]
async fn to_artboard<Data: Into<GraphicGroupTable> + 'n>(
ctx: impl ExtractAll + CloneVarArgs + Ctx,
#[implementations(
Context -> GraphicGroupTable,
Context -> VectorDataTable,
Context -> RasterDataTable<CPU>,
Context -> RasterDataTable<GPU>,
Context -> DAffine2,
)]
contents: impl Node<Context<'static>, Output = Data>,
label: String,
location: DVec2,
dimensions: DVec2,
background: Color,
clip: bool,
) -> Artboard {
let location = location.as_ivec2();
let dimensions = dimensions.as_ivec2().max(IVec2::ONE);
let footprint = ctx.try_footprint().copied();
let mut new_ctx = OwnedContextImpl::from(ctx);
if let Some(mut footprint) = footprint {
footprint.translate(location.as_dvec2());
new_ctx = new_ctx.with_footprint(footprint);
}
let graphic_group = contents.eval(new_ctx.into_context()).await;
Artboard {
graphic_group: graphic_group.into(),
label,
location: location.min(location + dimensions),
dimensions: dimensions.abs(),
background,
clip,
}
}
#[node_macro::node(category(""))]
pub async fn append_artboard(_ctx: impl Ctx, mut artboards: ArtboardGroupTable, artboard: Artboard, node_path: Vec<NodeId>) -> ArtboardGroupTable {
// Get the penultimate element of the node path, or None if the path is too short.
// This is used to get the ID of the user-facing "Artboard" node (which encapsulates this internal "Append Artboard" node).
let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
artboards.push(Instance {
instance: artboard,
transform: DAffine2::IDENTITY,
alpha_blending: AlphaBlending::default(),
source_node_id: encapsulating_node_id,
});
artboards
}
// TODO: Remove this one
impl From<Image<Color>> for GraphicElement {
fn from(raster_data: Image<Color>) -> Self {
GraphicElement::RasterDataCPU(RasterDataTable::<CPU>::new(Raster::new_cpu(raster_data)))
}
}
impl From<RasterDataTable<CPU>> for GraphicElement {
fn from(raster_data: RasterDataTable<CPU>) -> Self {
GraphicElement::RasterDataCPU(raster_data)
}
}
impl From<RasterDataTable<GPU>> for GraphicElement {
fn from(raster_data: RasterDataTable<GPU>) -> Self {
GraphicElement::RasterDataGPU(raster_data)
}
}
impl From<Raster<CPU>> for GraphicElement {
fn from(raster_data: Raster<CPU>) -> Self {
GraphicElement::RasterDataCPU(RasterDataTable::new(raster_data))
}
}
impl From<Raster<GPU>> for GraphicElement {
fn from(raster_data: Raster<GPU>) -> Self {
GraphicElement::RasterDataGPU(RasterDataTable::new(raster_data))
}
}
// TODO: Remove this one
impl From<VectorData> for GraphicElement {
fn from(vector_data: VectorData) -> Self {
GraphicElement::VectorData(VectorDataTable::new(vector_data))
}
}
impl From<VectorDataTable> for GraphicElement {
fn from(vector_data: VectorDataTable) -> Self {
GraphicElement::VectorData(vector_data)
}
}
impl From<GraphicGroupTable> for GraphicElement {
fn from(graphic_group: GraphicGroupTable) -> Self {
GraphicElement::GraphicGroup(graphic_group)
}
}
pub trait ToGraphicElement {
fn to_graphic_element(&self) -> GraphicElement;
}
/// Returns the value at the specified index in the collection. /// Returns the value at the specified index in the collection.
/// If that index has no value, the type's default value is returned. /// If that index has no value, the type's default value is returned.
#[node_macro::node(category("General"))] #[node_macro::node(category("General"))]
@@ -544,9 +339,9 @@ fn index<T: AtIndex + Clone + Default>(
Vec<Option<Color>>, Vec<Option<Color>>,
Vec<f64>, Vec<u64>, Vec<f64>, Vec<u64>,
Vec<DVec2>, Vec<DVec2>,
VectorDataTable, Table<VectorData>,
RasterDataTable<CPU>, Table<Raster<CPU>>,
GraphicGroupTable, Table<Graphic>,
)] )]
collection: T, collection: T,
/// The index of the item to retrieve, starting from 0 for the first item. /// The index of the item to retrieve, starting from 0 for the first item.
@@ -569,16 +364,75 @@ impl<T: Clone> AtIndex for Vec<T> {
self.get(index).cloned() self.get(index).cloned()
} }
} }
impl<T: Clone> AtIndex for Instances<T> { impl<T: Clone> AtIndex for Table<T> {
type Output = Instances<T>; type Output = Table<T>;
fn at_index(&self, index: usize) -> Option<Self::Output> { fn at_index(&self, index: usize) -> Option<Self::Output> {
let mut result_table = Self::default(); let mut result_table = Self::default();
if let Some(row) = self.instance_ref_iter().nth(index) { if let Some(row) = self.iter_ref().nth(index) {
result_table.push(row.to_instance_cloned()); result_table.push(row.into_cloned());
Some(result_table) Some(result_table)
} else { } else {
None None
} }
} }
} }
// TODO: Eventually remove this migration document upgrade code
pub fn migrate_graphic_group<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Graphic>, D::Error> {
use serde::Deserialize;
#[derive(Clone, Debug, PartialEq, DynAny, Default, serde::Serialize, serde::Deserialize)]
pub struct OldGraphicGroup {
elements: Vec<(Graphic, Option<NodeId>)>,
transform: DAffine2,
alpha_blending: AlphaBlending,
}
#[derive(Clone, Debug, PartialEq, DynAny, Default, serde::Serialize, serde::Deserialize)]
pub struct GraphicGroup {
elements: Vec<(Graphic, Option<NodeId>)>,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
enum EitherFormat {
OldGraphicGroup(OldGraphicGroup),
Table(serde_json::Value),
}
Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::OldGraphicGroup(old) => {
let mut graphic_group_table = Table::new();
for (graphic_element, source_node_id) in old.elements {
graphic_group_table.push(TableRow {
element: graphic_element,
transform: old.transform,
alpha_blending: old.alpha_blending,
source_node_id,
});
}
graphic_group_table
}
EitherFormat::Table(value) => {
// Try to deserialize as either table format
if let Ok(old_table) = serde_json::from_value::<Table<GraphicGroup>>(value.clone()) {
let mut graphic_group_table = Table::new();
for row in old_table.iter_ref() {
for (graphic_element, source_node_id) in &row.element.elements {
graphic_group_table.push(TableRow {
element: graphic_element.clone(),
transform: *row.transform,
alpha_blending: *row.alpha_blending,
source_node_id: *source_node_id,
});
}
}
graphic_group_table
} else if let Ok(new_table) = serde_json::from_value::<Table<Graphic>>(value) {
new_table
} else {
return Err(serde::de::Error::custom("Failed to deserialize Table<Graphic>"));
}
}
})
}
-281
View File
@@ -1,281 +0,0 @@
use crate::AlphaBlending;
use crate::transform::ApplyTransform;
use crate::uuid::NodeId;
use dyn_any::StaticType;
use glam::DAffine2;
use std::hash::Hash;
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct Instances<T> {
#[serde(alias = "instances")]
instance: Vec<T>,
#[serde(default = "one_daffine2_default")]
transform: Vec<DAffine2>,
#[serde(default = "one_alpha_blending_default")]
alpha_blending: Vec<AlphaBlending>,
#[serde(default = "one_source_node_id_default")]
source_node_id: Vec<Option<NodeId>>,
}
impl<T> Instances<T> {
pub fn new(instance: T) -> Self {
Self {
instance: vec![instance],
transform: vec![DAffine2::IDENTITY],
alpha_blending: vec![AlphaBlending::default()],
source_node_id: vec![None],
}
}
pub fn new_instance(instance: Instance<T>) -> Self {
Self {
instance: vec![instance.instance],
transform: vec![instance.transform],
alpha_blending: vec![instance.alpha_blending],
source_node_id: vec![instance.source_node_id],
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
instance: Vec::with_capacity(capacity),
transform: Vec::with_capacity(capacity),
alpha_blending: Vec::with_capacity(capacity),
source_node_id: Vec::with_capacity(capacity),
}
}
pub fn push(&mut self, instance: Instance<T>) {
self.instance.push(instance.instance);
self.transform.push(instance.transform);
self.alpha_blending.push(instance.alpha_blending);
self.source_node_id.push(instance.source_node_id);
}
pub fn extend(&mut self, instances: Instances<T>) {
self.instance.extend(instances.instance);
self.transform.extend(instances.transform);
self.alpha_blending.extend(instances.alpha_blending);
self.source_node_id.extend(instances.source_node_id);
}
pub fn instance_iter(self) -> impl DoubleEndedIterator<Item = Instance<T>> {
self.instance
.into_iter()
.zip(self.transform)
.zip(self.alpha_blending)
.zip(self.source_node_id)
.map(|(((instance, transform), alpha_blending), source_node_id)| Instance {
instance,
transform,
alpha_blending,
source_node_id,
})
}
pub fn instance_ref_iter(&self) -> impl DoubleEndedIterator<Item = InstanceRef<'_, T>> + Clone {
self.instance
.iter()
.zip(self.transform.iter())
.zip(self.alpha_blending.iter())
.zip(self.source_node_id.iter())
.map(|(((instance, transform), alpha_blending), source_node_id)| InstanceRef {
instance,
transform,
alpha_blending,
source_node_id,
})
}
pub fn instance_mut_iter(&mut self) -> impl DoubleEndedIterator<Item = InstanceMut<'_, T>> {
self.instance
.iter_mut()
.zip(self.transform.iter_mut())
.zip(self.alpha_blending.iter_mut())
.zip(self.source_node_id.iter_mut())
.map(|(((instance, transform), alpha_blending), source_node_id)| InstanceMut {
instance,
transform,
alpha_blending,
source_node_id,
})
}
pub fn get(&self, index: usize) -> Option<InstanceRef<'_, T>> {
if index >= self.instance.len() {
return None;
}
Some(InstanceRef {
instance: &self.instance[index],
transform: &self.transform[index],
alpha_blending: &self.alpha_blending[index],
source_node_id: &self.source_node_id[index],
})
}
pub fn get_mut(&mut self, index: usize) -> Option<InstanceMut<'_, T>> {
if index >= self.instance.len() {
return None;
}
Some(InstanceMut {
instance: &mut self.instance[index],
transform: &mut self.transform[index],
alpha_blending: &mut self.alpha_blending[index],
source_node_id: &mut self.source_node_id[index],
})
}
pub fn len(&self) -> usize {
self.instance.len()
}
pub fn is_empty(&self) -> bool {
self.instance.is_empty()
}
}
impl<T> Default for Instances<T> {
fn default() -> Self {
Self {
instance: Vec::new(),
transform: Vec::new(),
alpha_blending: Vec::new(),
source_node_id: Vec::new(),
}
}
}
impl<T: Hash> Hash for Instances<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
for instance in &self.instance {
instance.hash(state);
}
}
}
impl<T> ApplyTransform for Instances<T> {
fn apply_transform(&mut self, modification: &DAffine2) {
for transform in &mut self.transform {
*transform *= *modification;
}
}
fn left_apply_transform(&mut self, modification: &DAffine2) {
for transform in &mut self.transform {
*transform = *modification * *transform;
}
}
}
impl<T: PartialEq> PartialEq for Instances<T> {
fn eq(&self, other: &Self) -> bool {
self.instance.len() == other.instance.len() && { self.instance.iter().zip(other.instance.iter()).all(|(a, b)| a == b) }
}
}
unsafe impl<T: StaticType + 'static> StaticType for Instances<T> {
type Static = Instances<T>;
}
impl<T> FromIterator<Instance<T>> for Instances<T> {
fn from_iter<I: IntoIterator<Item = Instance<T>>>(iter: I) -> Self {
let iter = iter.into_iter();
let (lower, _) = iter.size_hint();
let mut instances = Self::with_capacity(lower);
for instance in iter {
instances.push(instance);
}
instances
}
}
fn one_daffine2_default() -> Vec<DAffine2> {
vec![DAffine2::IDENTITY]
}
fn one_alpha_blending_default() -> Vec<AlphaBlending> {
vec![AlphaBlending::default()]
}
fn one_source_node_id_default() -> Vec<Option<NodeId>> {
vec![None]
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct InstanceRef<'a, T> {
pub instance: &'a T,
pub transform: &'a DAffine2,
pub alpha_blending: &'a AlphaBlending,
pub source_node_id: &'a Option<NodeId>,
}
impl<T> InstanceRef<'_, T> {
pub fn to_instance_cloned(self) -> Instance<T>
where
T: Clone,
{
Instance {
instance: self.instance.clone(),
transform: *self.transform,
alpha_blending: *self.alpha_blending,
source_node_id: *self.source_node_id,
}
}
}
#[derive(Debug)]
pub struct InstanceMut<'a, T> {
pub instance: &'a mut T,
pub transform: &'a mut DAffine2,
pub alpha_blending: &'a mut AlphaBlending,
pub source_node_id: &'a mut Option<NodeId>,
}
#[derive(Copy, Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Instance<T> {
pub instance: T,
pub transform: DAffine2,
pub alpha_blending: AlphaBlending,
pub source_node_id: Option<NodeId>,
}
impl<T> Instance<T> {
pub fn to_graphic_element<U>(self) -> Instance<U>
where
T: Into<U>,
{
Instance {
instance: self.instance.into(),
transform: self.transform,
alpha_blending: self.alpha_blending,
source_node_id: self.source_node_id,
}
}
pub fn to_instance_ref(&self) -> InstanceRef<'_, T> {
InstanceRef {
instance: &self.instance,
transform: &self.transform,
alpha_blending: &self.alpha_blending,
source_node_id: &self.source_node_id,
}
}
pub fn to_instance_mut(&mut self) -> InstanceMut<'_, T> {
InstanceMut {
instance: &mut self.instance,
transform: &mut self.transform,
alpha_blending: &mut self.alpha_blending,
source_node_id: &mut self.source_node_id,
}
}
pub fn to_table(self) -> Instances<T> {
Instances {
instance: vec![self.instance],
transform: vec![self.transform],
alpha_blending: vec![self.alpha_blending],
source_node_id: vec![self.source_node_id],
}
}
}
+4 -2
View File
@@ -2,6 +2,7 @@
extern crate log; extern crate log;
pub mod animation; pub mod animation;
pub mod artboard;
pub mod blending_nodes; pub mod blending_nodes;
pub mod bounds; pub mod bounds;
pub mod consts; pub mod consts;
@@ -11,7 +12,6 @@ pub mod extract_xy;
pub mod generic; pub mod generic;
pub mod gradient; pub mod gradient;
pub mod graphic_element; pub mod graphic_element;
pub mod instances;
pub mod logic; pub mod logic;
pub mod math; pub mod math;
pub mod memo; pub mod memo;
@@ -22,6 +22,7 @@ pub mod raster_types;
pub mod registry; pub mod registry;
pub mod render_complexity; pub mod render_complexity;
pub mod structural; pub mod structural;
pub mod table;
pub mod text; pub mod text;
pub mod transform; pub mod transform;
pub mod transform_nodes; pub mod transform_nodes;
@@ -30,6 +31,7 @@ pub mod value;
pub mod vector; pub mod vector;
pub use crate as graphene_core; pub use crate as graphene_core;
pub use artboard::Artboard;
pub use blending::*; pub use blending::*;
pub use color::Color; pub use color::Color;
pub use context::*; pub use context::*;
@@ -39,7 +41,7 @@ pub use graphene_core_shaders::AsU32;
pub use graphene_core_shaders::blending; pub use graphene_core_shaders::blending;
pub use graphene_core_shaders::choice_type; pub use graphene_core_shaders::choice_type;
pub use graphene_core_shaders::color; pub use graphene_core_shaders::color;
pub use graphic_element::{Artboard, ArtboardGroupTable, GraphicElement, GraphicGroupTable}; pub use graphic_element::Graphic;
pub use memo::MemoHash; pub use memo::MemoHash;
pub use num_traits; pub use num_traits;
use std::any::TypeId; use std::any::TypeId;
+19 -19
View File
@@ -1,23 +1,23 @@
use crate::ArtboardGroupTable; use crate::Artboard;
use crate::Color; use crate::Color;
use crate::GraphicElement; use crate::Graphic;
use crate::GraphicGroupTable;
use crate::gradient::GradientStops; use crate::gradient::GradientStops;
use crate::graphene_core::registry::types::TextArea; use crate::graphene_core::registry::types::TextArea;
use crate::raster_types::{CPU, GPU, RasterDataTable}; use crate::raster_types::{CPU, GPU, Raster};
use crate::vector::VectorDataTable; use crate::table::Table;
use crate::vector::VectorData;
use crate::{Context, Ctx}; use crate::{Context, Ctx};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
#[node_macro::node(category("Text"))] #[node_macro::node(category("Text"))]
fn to_string<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, VectorDataTable)] value: T) -> String { fn to_string<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, Table<VectorData>)] value: T) -> String {
format!("{:?}", value) format!("{:?}", value)
} }
#[node_macro::node(category("Text"))] #[node_macro::node(category("Text"))]
fn serialize<T: serde::Serialize>( fn serialize<T: serde::Serialize>(
_: impl Ctx, _: impl Ctx,
#[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, Color, Option<Color>, GraphicGroupTable, VectorDataTable, RasterDataTable<CPU>)] value: T, #[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, Color, Option<Color>, Table<Graphic>, Table<VectorData>, Table<Raster<CPU>>)] value: T,
) -> String { ) -> String {
serde_json::to_string(&value).unwrap_or_else(|_| "Serialization Error".to_string()) serde_json::to_string(&value).unwrap_or_else(|_| "Serialization Error".to_string())
} }
@@ -59,12 +59,12 @@ async fn switch<T, C: Send + 'n + Clone>(
Context -> u64, Context -> u64,
Context -> DVec2, Context -> DVec2,
Context -> DAffine2, Context -> DAffine2,
Context -> ArtboardGroupTable, Context -> Table<Artboard>,
Context -> VectorDataTable, Context -> Table<VectorData>,
Context -> GraphicGroupTable, Context -> Table<Graphic>,
Context -> RasterDataTable<CPU>, Context -> Table<Raster<CPU>>,
Context -> RasterDataTable<GPU>, Context -> Table<Raster<GPU>>,
Context -> GraphicElement, Context -> Graphic,
Context -> Color, Context -> Color,
Context -> Option<Color>, Context -> Option<Color>,
Context -> GradientStops, Context -> GradientStops,
@@ -80,12 +80,12 @@ async fn switch<T, C: Send + 'n + Clone>(
Context -> u64, Context -> u64,
Context -> DVec2, Context -> DVec2,
Context -> DAffine2, Context -> DAffine2,
Context -> ArtboardGroupTable, Context -> Table<Artboard>,
Context -> VectorDataTable, Context -> Table<VectorData>,
Context -> GraphicGroupTable, Context -> Table<Graphic>,
Context -> RasterDataTable<CPU>, Context -> Table<Raster<CPU>>,
Context -> RasterDataTable<GPU>, Context -> Table<Raster<GPU>>,
Context -> GraphicElement, Context -> Graphic,
Context -> Color, Context -> Color,
Context -> Option<Color>, Context -> Option<Color>,
Context -> GradientStops, Context -> GradientStops,
+1 -3
View File
@@ -6,10 +6,8 @@ pub mod color {
pub mod image; pub mod image;
pub use self::image::Image; pub use self::image::Image;
use crate::GraphicGroupTable;
pub use crate::color::*; pub use crate::color::*;
use crate::raster_types::{CPU, RasterDataTable}; use crate::raster_types::CPU;
use crate::vector::VectorDataTable;
use std::fmt::Debug; use std::fmt::Debug;
pub trait Bitmap { pub trait Bitmap {
+41 -63
View File
@@ -1,8 +1,9 @@
use super::Color; use super::Color;
use crate::AlphaBlending; use crate::AlphaBlending;
use crate::color::float_to_srgb_u8; use crate::color::float_to_srgb_u8;
use crate::instances::{Instance, Instances};
use crate::raster_types::Raster; use crate::raster_types::Raster;
use crate::table::{Table, TableRow};
use crate::vector::VectorData;
use core::hash::{Hash, Hasher}; use core::hash::{Hash, Hasher};
use dyn_any::{DynAny, StaticType}; use dyn_any::{DynAny, StaticType};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
@@ -212,25 +213,23 @@ impl<P: Pixel> IntoIterator for Image<P> {
} }
// TODO: Eventually remove this migration document upgrade code // TODO: Eventually remove this migration document upgrade code
pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<RasterDataTable<CPU>, D::Error> { pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Raster<CPU>>, D::Error> {
use serde::Deserialize; use serde::Deserialize;
type ImageFrameTable<P> = Instances<Image<P>>;
#[derive(Clone, Debug, Hash, PartialEq, DynAny)] #[derive(Clone, Debug, Hash, PartialEq, DynAny)]
enum RasterFrame { enum RasterFrame {
/// A CPU-based bitmap image with a finite position and extent, equivalent to the SVG <image> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image /// A CPU-based bitmap image with a finite position and extent, equivalent to the SVG <image> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image
ImageFrame(ImageFrameTable<Color>), ImageFrame(Table<Image<Color>>),
} }
impl<'de> serde::Deserialize<'de> for RasterFrame { impl<'de> serde::Deserialize<'de> for RasterFrame {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Ok(RasterFrame::ImageFrame(ImageFrameTable::new(Image::deserialize(deserializer)?))) Ok(RasterFrame::ImageFrame(Table::new_from_element(Image::deserialize(deserializer)?)))
} }
} }
impl serde::Serialize for RasterFrame { impl serde::Serialize for RasterFrame {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self { match self {
RasterFrame::ImageFrame(image_instances) => image_instances.serialize(serializer), RasterFrame::ImageFrame(table) => table.serialize(serializer),
} }
} }
} }
@@ -238,9 +237,9 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)] #[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
pub enum GraphicElement { pub enum GraphicElement {
/// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g /// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g
GraphicGroup(GraphicGroupTable), GraphicGroup(Table<GraphicElement>),
/// A vector shape, equivalent to the SVG <path> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path /// A vector shape, equivalent to the SVG <path> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
VectorData(VectorDataTable), VectorData(Table<VectorData>),
RasterFrame(RasterFrame), RasterFrame(RasterFrame),
} }
@@ -250,14 +249,14 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
} }
impl From<ImageFrame<Color>> for GraphicElement { impl From<ImageFrame<Color>> for GraphicElement {
fn from(image_frame: ImageFrame<Color>) -> Self { fn from(image_frame: ImageFrame<Color>) -> Self {
GraphicElement::RasterFrame(RasterFrame::ImageFrame(ImageFrameTable::new(image_frame.image))) GraphicElement::RasterFrame(RasterFrame::ImageFrame(Table::new_from_element(image_frame.image)))
} }
} }
impl From<GraphicElement> for ImageFrame<Color> { impl From<GraphicElement> for ImageFrame<Color> {
fn from(element: GraphicElement) -> Self { fn from(element: GraphicElement) -> Self {
match element { match element {
GraphicElement::RasterFrame(RasterFrame::ImageFrame(image)) => Self { GraphicElement::RasterFrame(RasterFrame::ImageFrame(image)) => Self {
image: image.instance_ref_iter().next().unwrap().instance.clone(), image: image.iter_ref().next().unwrap().element.clone(),
}, },
_ => panic!("Expected Image, found {:?}", element), _ => panic!("Expected Image, found {:?}", element),
} }
@@ -284,54 +283,52 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
enum FormatVersions { enum FormatVersions {
Image(Image<Color>), Image(Image<Color>),
OldImageFrame(OldImageFrame<Color>), OldImageFrame(OldImageFrame<Color>),
ImageFrame(Instances<ImageFrame<Color>>), ImageFrame(Table<ImageFrame<Color>>),
ImageFrameTable(ImageFrameTable<Color>), ImageFrameTable(Table<Image<Color>>),
RasterDataTable(RasterDataTable<CPU>), RasterDataTable(Table<Raster<CPU>>),
} }
Ok(match FormatVersions::deserialize(deserializer)? { Ok(match FormatVersions::deserialize(deserializer)? {
FormatVersions::Image(image) => RasterDataTable::new(Raster::new_cpu(image)), FormatVersions::Image(image) => Table::new_from_element(Raster::new_cpu(image)),
FormatVersions::OldImageFrame(image_frame_with_transform_and_blending) => { FormatVersions::OldImageFrame(image_frame_with_transform_and_blending) => {
let OldImageFrame { image, transform, alpha_blending } = image_frame_with_transform_and_blending; let OldImageFrame { image, transform, alpha_blending } = image_frame_with_transform_and_blending;
let mut image_frame_table = RasterDataTable::new(Raster::new_cpu(image)); let mut image_frame_table = Table::new_from_element(Raster::new_cpu(image));
*image_frame_table.instance_mut_iter().next().unwrap().transform = transform; *image_frame_table.iter_mut().next().unwrap().transform = transform;
*image_frame_table.instance_mut_iter().next().unwrap().alpha_blending = alpha_blending; *image_frame_table.iter_mut().next().unwrap().alpha_blending = alpha_blending;
image_frame_table image_frame_table
} }
FormatVersions::ImageFrame(image_frame) => RasterDataTable::new(Raster::new_cpu( FormatVersions::ImageFrame(image_frame) => Table::new_from_element(Raster::new_cpu(
image_frame image_frame
.instance_ref_iter() .iter_ref()
.next() .next()
.unwrap_or(Instances::new(ImageFrame::default()).instance_ref_iter().next().unwrap()) .unwrap_or(Table::new_from_element(ImageFrame::default()).iter_ref().next().unwrap())
.instance .element
.image .image
.clone(), .clone(),
)), )),
FormatVersions::ImageFrameTable(image_frame_table) => RasterDataTable::new(Raster::new_cpu(image_frame_table.instance_ref_iter().next().unwrap().instance.clone())), FormatVersions::ImageFrameTable(image_frame_table) => Table::new_from_element(Raster::new_cpu(image_frame_table.iter_ref().next().unwrap().element.clone())),
FormatVersions::RasterDataTable(raster_data_table) => raster_data_table, FormatVersions::RasterDataTable(raster_data_table) => raster_data_table,
}) })
} }
// TODO: Eventually remove this migration document upgrade code // TODO: Eventually remove this migration document upgrade code
pub fn migrate_image_frame_instance<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Instance<Raster<CPU>>, D::Error> { pub fn migrate_image_frame_row<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<TableRow<Raster<CPU>>, D::Error> {
use serde::Deserialize; use serde::Deserialize;
type ImageFrameTable<P> = Instances<Image<P>>;
#[derive(Clone, Debug, Hash, PartialEq, DynAny)] #[derive(Clone, Debug, Hash, PartialEq, DynAny)]
enum RasterFrame { enum RasterFrame {
/// A CPU-based bitmap image with a finite position and extent, equivalent to the SVG <image> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image /// A CPU-based bitmap image with a finite position and extent, equivalent to the SVG <image> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image
ImageFrame(ImageFrameTable<Color>), ImageFrame(Table<Image<Color>>),
} }
impl<'de> serde::Deserialize<'de> for RasterFrame { impl<'de> serde::Deserialize<'de> for RasterFrame {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Ok(RasterFrame::ImageFrame(ImageFrameTable::new(Image::deserialize(deserializer)?))) Ok(RasterFrame::ImageFrame(Table::new_from_element(Image::deserialize(deserializer)?)))
} }
} }
impl serde::Serialize for RasterFrame { impl serde::Serialize for RasterFrame {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self { match self {
RasterFrame::ImageFrame(image_instances) => image_instances.serialize(serializer), RasterFrame::ImageFrame(table) => table.serialize(serializer),
} }
} }
} }
@@ -339,9 +336,9 @@ pub fn migrate_image_frame_instance<'de, D: serde::Deserializer<'de>>(deserializ
#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)] #[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
pub enum GraphicElement { pub enum GraphicElement {
/// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g /// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g
GraphicGroup(GraphicGroupTable), GraphicGroup(Table<GraphicElement>),
/// A vector shape, equivalent to the SVG <path> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path /// A vector shape, equivalent to the SVG <path> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
VectorData(VectorDataTable), VectorData(Table<VectorData>),
RasterFrame(RasterFrame), RasterFrame(RasterFrame),
} }
@@ -351,14 +348,14 @@ pub fn migrate_image_frame_instance<'de, D: serde::Deserializer<'de>>(deserializ
} }
impl From<ImageFrame<Color>> for GraphicElement { impl From<ImageFrame<Color>> for GraphicElement {
fn from(image_frame: ImageFrame<Color>) -> Self { fn from(image_frame: ImageFrame<Color>) -> Self {
GraphicElement::RasterFrame(RasterFrame::ImageFrame(ImageFrameTable::new(image_frame.image))) GraphicElement::RasterFrame(RasterFrame::ImageFrame(Table::new_from_element(image_frame.image)))
} }
} }
impl From<GraphicElement> for ImageFrame<Color> { impl From<GraphicElement> for ImageFrame<Color> {
fn from(element: GraphicElement) -> Self { fn from(element: GraphicElement) -> Self {
match element { match element {
GraphicElement::RasterFrame(RasterFrame::ImageFrame(image)) => Self { GraphicElement::RasterFrame(RasterFrame::ImageFrame(image)) => Self {
image: image.instance_ref_iter().next().unwrap().instance.clone(), image: image.iter_ref().next().unwrap().element.clone(),
}, },
_ => panic!("Expected Image, found {:?}", element), _ => panic!("Expected Image, found {:?}", element),
} }
@@ -385,33 +382,31 @@ pub fn migrate_image_frame_instance<'de, D: serde::Deserializer<'de>>(deserializ
enum FormatVersions { enum FormatVersions {
Image(Image<Color>), Image(Image<Color>),
OldImageFrame(OldImageFrame<Color>), OldImageFrame(OldImageFrame<Color>),
ImageFrame(Instances<ImageFrame<Color>>), ImageFrame(Table<ImageFrame<Color>>),
RasterDataTable(RasterDataTable<CPU>), RasterDataTable(Table<Raster<CPU>>),
ImageInstance(Instance<Raster<CPU>>), ImageTableRow(TableRow<Raster<CPU>>),
} }
Ok(match FormatVersions::deserialize(deserializer)? { Ok(match FormatVersions::deserialize(deserializer)? {
FormatVersions::Image(image) => Instance { FormatVersions::Image(image) => TableRow {
instance: Raster::new_cpu(image), element: Raster::new_cpu(image),
..Default::default() ..Default::default()
}, },
FormatVersions::OldImageFrame(image_frame_with_transform_and_blending) => Instance { FormatVersions::OldImageFrame(image_frame_with_transform_and_blending) => TableRow {
instance: Raster::new_cpu(image_frame_with_transform_and_blending.image), element: Raster::new_cpu(image_frame_with_transform_and_blending.image),
transform: image_frame_with_transform_and_blending.transform, transform: image_frame_with_transform_and_blending.transform,
alpha_blending: image_frame_with_transform_and_blending.alpha_blending, alpha_blending: image_frame_with_transform_and_blending.alpha_blending,
source_node_id: None, source_node_id: None,
}, },
FormatVersions::ImageFrame(image_frame) => Instance { FormatVersions::ImageFrame(image_frame) => TableRow {
instance: Raster::new_cpu(image_frame.instance_ref_iter().next().unwrap().instance.image.clone()), element: Raster::new_cpu(image_frame.iter_ref().next().unwrap().element.image.clone()),
..Default::default() ..Default::default()
}, },
FormatVersions::RasterDataTable(image_frame_table) => image_frame_table.instance_iter().next().unwrap_or_default(), FormatVersions::RasterDataTable(image_frame_table) => image_frame_table.iter().next().unwrap_or_default(),
FormatVersions::ImageInstance(image_instance) => image_instance, FormatVersions::ImageTableRow(image_table_row) => image_table_row,
}) })
} }
// pub type RasterDataTable<P> = Instances<Image<P>>;
impl<P: Debug + Copy + Pixel> Sample for Image<P> { impl<P: Debug + Copy + Pixel> Sample for Image<P> {
type Pixel = P; type Pixel = P;
@@ -458,23 +453,6 @@ impl From<Image<Color>> for Image<SRGBA8> {
} }
} }
// impl From<RasterDataTable<CPU>> for RasterDataTable<SRGBA8> {
// fn from(image_frame_table: RasterDataTable<CPU>) -> Self {
// let mut result_table = RasterDataTable::<SRGBA8>::default();
// for image_frame_instance in image_frame_table.instance_iter() {
// result_table.push(Instance {
// instance: image_frame_instance.instance,
// transform: image_frame_instance.transform,
// alpha_blending: image_frame_instance.alpha_blending,
// source_node_id: image_frame_instance.source_node_id,
// });
// }
// result_table
// }
// }
impl From<Image<SRGBA8>> for Image<Color> { impl From<Image<SRGBA8>> for Image<Color> {
fn from(image: Image<SRGBA8>) -> Self { fn from(image: Image<SRGBA8>) -> Self {
let data = image.data.into_iter().map(|x| x.into()).collect(); let data = image.data.into_iter().map(|x| x.into()).collect();
+10 -9
View File
@@ -1,8 +1,8 @@
use crate::Color; use crate::Color;
use crate::bounds::BoundingBox; use crate::bounds::BoundingBox;
use crate::instances::Instances;
use crate::math::quad::Quad; use crate::math::quad::Quad;
use crate::raster::Image; use crate::raster::Image;
use crate::table::Table;
use core::ops::Deref; use core::ops::Deref;
use dyn_any::DynAny; use dyn_any::DynAny;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
@@ -61,8 +61,6 @@ where
} }
} }
pub type RasterDataTable<Storage> = Instances<Raster<Storage>>;
pub use cpu::CPU; pub use cpu::CPU;
mod cpu { mod cpu {
@@ -165,10 +163,13 @@ mod gpu {
#[cfg(not(feature = "wgpu"))] #[cfg(not(feature = "wgpu"))]
mod gpu { mod gpu {
use super::*; use super::*;
use crate::raster_types::__private::Sealed;
#[derive(Clone, Debug)] #[derive(Clone, Debug, PartialEq, Hash)]
pub struct GPU; pub struct GPU;
impl Sealed for Raster<GPU> {}
impl Storage for Raster<GPU> { impl Storage for Raster<GPU> {
fn is_empty(&self) -> bool { fn is_empty(&self) -> bool {
true true
@@ -198,15 +199,15 @@ mod gpu_common {
} }
} }
impl<T> BoundingBox for RasterDataTable<T> impl<T> BoundingBox for Table<Raster<T>>
where where
Raster<T>: Storage, Raster<T>: Storage,
{ {
fn bounding_box(&self, transform: DAffine2, _include_stroke: bool) -> Option<[DVec2; 2]> { fn bounding_box(&self, transform: DAffine2, _include_stroke: bool) -> Option<[DVec2; 2]> {
self.instance_ref_iter() self.iter_ref()
.filter(|instance| !instance.instance.is_empty()) // Eliminate empty images .filter(|row| !row.element.is_empty()) // Eliminate empty images
.flat_map(|instance| { .flat_map(|row| {
let transform = transform * *instance.transform; let transform = transform * *row.transform;
(transform.matrix2.determinant() != 0.).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box()) (transform.matrix2.determinant() != 0.).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box())
}) })
.reduce(Quad::combine_bounds) .reduce(Quad::combine_bounds)
+6 -6
View File
@@ -56,20 +56,20 @@ pub static NODE_REGISTRY: NodeRegistry = LazyLock::new(|| Mutex::new(HashMap::ne
pub static NODE_METADATA: LazyLock<Mutex<HashMap<ProtoNodeIdentifier, NodeMetadata>>> = LazyLock::new(|| Mutex::new(HashMap::new())); pub static NODE_METADATA: LazyLock<Mutex<HashMap<ProtoNodeIdentifier, NodeMetadata>>> = LazyLock::new(|| Mutex::new(HashMap::new()));
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
pub type DynFuture<'n, T> = Pin<Box<dyn Future<Output = T> + 'n + Send>>; pub type DynFuture<'n, T> = Pin<Box<dyn Future<Output = T> + 'n + Send>>;
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
pub type DynFuture<'n, T> = Pin<Box<dyn std::future::Future<Output = T> + 'n>>; pub type DynFuture<'n, T> = Pin<Box<dyn std::future::Future<Output = T> + 'n>>;
pub type LocalFuture<'n, T> = Pin<Box<dyn Future<Output = T> + 'n>>; pub type LocalFuture<'n, T> = Pin<Box<dyn Future<Output = T> + 'n>>;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
pub type Any<'n> = Box<dyn DynAny<'n> + 'n + Send>; pub type Any<'n> = Box<dyn DynAny<'n> + 'n + Send>;
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
pub type Any<'n> = Box<dyn DynAny<'n> + 'n>; pub type Any<'n> = Box<dyn DynAny<'n> + 'n>;
pub type FutureAny<'n> = DynFuture<'n, Any<'n>>; pub type FutureAny<'n> = DynFuture<'n, Any<'n>>;
// TODO: is this safe? This is assumed to be send+sync. // TODO: is this safe? This is assumed to be send+sync.
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
pub type TypeErasedNode<'n> = dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n + Send + Sync; pub type TypeErasedNode<'n> = dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n + Send + Sync;
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
pub type TypeErasedNode<'n> = dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n; pub type TypeErasedNode<'n> = dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n;
pub type TypeErasedPinnedRef<'n> = Pin<&'n TypeErasedNode<'n>>; pub type TypeErasedPinnedRef<'n> = Pin<&'n TypeErasedNode<'n>>;
pub type TypeErasedRef<'n> = &'n TypeErasedNode<'n>; pub type TypeErasedRef<'n> = &'n TypeErasedNode<'n>;
+9 -9
View File
@@ -1,7 +1,7 @@
use crate::instances::Instances;
use crate::raster_types::{CPU, GPU, Raster}; use crate::raster_types::{CPU, GPU, Raster};
use crate::table::Table;
use crate::vector::VectorData; use crate::vector::VectorData;
use crate::{Artboard, Color, GraphicElement}; use crate::{Artboard, Color, Graphic};
use glam::DVec2; use glam::DVec2;
pub trait RenderComplexity { pub trait RenderComplexity {
@@ -10,9 +10,9 @@ pub trait RenderComplexity {
} }
} }
impl<T: RenderComplexity> RenderComplexity for Instances<T> { impl<T: RenderComplexity> RenderComplexity for Table<T> {
fn render_complexity(&self) -> usize { fn render_complexity(&self) -> usize {
self.instance_ref_iter().map(|instance| instance.instance.render_complexity()).fold(0, usize::saturating_add) self.iter_ref().map(|row| row.element.render_complexity()).fold(0, usize::saturating_add)
} }
} }
@@ -22,13 +22,13 @@ impl RenderComplexity for Artboard {
} }
} }
impl RenderComplexity for GraphicElement { impl RenderComplexity for Graphic {
fn render_complexity(&self) -> usize { fn render_complexity(&self) -> usize {
match self { match self {
Self::GraphicGroup(instances) => instances.render_complexity(), Self::GraphicGroup(table) => table.render_complexity(),
Self::VectorData(instances) => instances.render_complexity(), Self::VectorData(table) => table.render_complexity(),
Self::RasterDataCPU(instances) => instances.render_complexity(), Self::RasterDataCPU(table) => table.render_complexity(),
Self::RasterDataGPU(instances) => instances.render_complexity(), Self::RasterDataGPU(table) => table.render_complexity(),
} }
} }
} }
+252
View File
@@ -0,0 +1,252 @@
use crate::AlphaBlending;
use crate::transform::ApplyTransform;
use crate::uuid::NodeId;
use dyn_any::StaticType;
use glam::DAffine2;
use std::hash::Hash;
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct Table<T> {
#[serde(alias = "instances", alias = "instance")]
element: Vec<T>,
transform: Vec<DAffine2>,
alpha_blending: Vec<AlphaBlending>,
source_node_id: Vec<Option<NodeId>>,
}
impl<T> Table<T> {
pub fn new() -> Self {
Self::default()
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
element: Vec::with_capacity(capacity),
transform: Vec::with_capacity(capacity),
alpha_blending: Vec::with_capacity(capacity),
source_node_id: Vec::with_capacity(capacity),
}
}
pub fn new_from_element(element: T) -> Self {
Self {
element: vec![element],
transform: vec![DAffine2::IDENTITY],
alpha_blending: vec![AlphaBlending::default()],
source_node_id: vec![None],
}
}
pub fn new_from_row(row: TableRow<T>) -> Self {
Self {
element: vec![row.element],
transform: vec![row.transform],
alpha_blending: vec![row.alpha_blending],
source_node_id: vec![row.source_node_id],
}
}
pub fn push(&mut self, row: TableRow<T>) {
self.element.push(row.element);
self.transform.push(row.transform);
self.alpha_blending.push(row.alpha_blending);
self.source_node_id.push(row.source_node_id);
}
pub fn extend(&mut self, table: Table<T>) {
self.element.extend(table.element);
self.transform.extend(table.transform);
self.alpha_blending.extend(table.alpha_blending);
self.source_node_id.extend(table.source_node_id);
}
pub fn iter(self) -> impl DoubleEndedIterator<Item = TableRow<T>> {
self.element
.into_iter()
.zip(self.transform)
.zip(self.alpha_blending)
.zip(self.source_node_id)
.map(|(((element, transform), alpha_blending), source_node_id)| TableRow {
element,
transform,
alpha_blending,
source_node_id,
})
}
pub fn iter_ref(&self) -> impl DoubleEndedIterator<Item = TableRowRef<'_, T>> + Clone {
self.element
.iter()
.zip(self.transform.iter())
.zip(self.alpha_blending.iter())
.zip(self.source_node_id.iter())
.map(|(((element, transform), alpha_blending), source_node_id)| TableRowRef {
element,
transform,
alpha_blending,
source_node_id,
})
}
pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = TableRowMut<'_, T>> {
self.element
.iter_mut()
.zip(self.transform.iter_mut())
.zip(self.alpha_blending.iter_mut())
.zip(self.source_node_id.iter_mut())
.map(|(((element, transform), alpha_blending), source_node_id)| TableRowMut {
element,
transform,
alpha_blending,
source_node_id,
})
}
pub fn get(&self, index: usize) -> Option<TableRowRef<'_, T>> {
if index >= self.element.len() {
return None;
}
Some(TableRowRef {
element: &self.element[index],
transform: &self.transform[index],
alpha_blending: &self.alpha_blending[index],
source_node_id: &self.source_node_id[index],
})
}
pub fn get_mut(&mut self, index: usize) -> Option<TableRowMut<'_, T>> {
if index >= self.element.len() {
return None;
}
Some(TableRowMut {
element: &mut self.element[index],
transform: &mut self.transform[index],
alpha_blending: &mut self.alpha_blending[index],
source_node_id: &mut self.source_node_id[index],
})
}
pub fn len(&self) -> usize {
self.element.len()
}
pub fn is_empty(&self) -> bool {
self.element.is_empty()
}
}
impl<T> Default for Table<T> {
fn default() -> Self {
Self {
element: Vec::new(),
transform: Vec::new(),
alpha_blending: Vec::new(),
source_node_id: Vec::new(),
}
}
}
impl<T: Hash> Hash for Table<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
for element in &self.element {
element.hash(state);
}
}
}
impl<T> ApplyTransform for Table<T> {
fn apply_transform(&mut self, modification: &DAffine2) {
for transform in &mut self.transform {
*transform *= *modification;
}
}
fn left_apply_transform(&mut self, modification: &DAffine2) {
for transform in &mut self.transform {
*transform = *modification * *transform;
}
}
}
impl<T: PartialEq> PartialEq for Table<T> {
fn eq(&self, other: &Self) -> bool {
self.element.len() == other.element.len() && { self.element.iter().zip(other.element.iter()).all(|(a, b)| a == b) }
}
}
unsafe impl<T: StaticType + 'static> StaticType for Table<T> {
type Static = Table<T>;
}
impl<T> FromIterator<TableRow<T>> for Table<T> {
fn from_iter<I: IntoIterator<Item = TableRow<T>>>(iter: I) -> Self {
let iter = iter.into_iter();
let (lower, _) = iter.size_hint();
let mut table = Self::with_capacity(lower);
for row in iter {
table.push(row);
}
table
}
}
#[derive(Copy, Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct TableRow<T> {
#[serde(alias = "instance")]
pub element: T,
pub transform: DAffine2,
pub alpha_blending: AlphaBlending,
pub source_node_id: Option<NodeId>,
}
impl<T> TableRow<T> {
pub fn as_ref(&self) -> TableRowRef<'_, T> {
TableRowRef {
element: &self.element,
transform: &self.transform,
alpha_blending: &self.alpha_blending,
source_node_id: &self.source_node_id,
}
}
pub fn as_mut(&mut self) -> TableRowMut<'_, T> {
TableRowMut {
element: &mut self.element,
transform: &mut self.transform,
alpha_blending: &mut self.alpha_blending,
source_node_id: &mut self.source_node_id,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct TableRowRef<'a, T> {
pub element: &'a T,
pub transform: &'a DAffine2,
pub alpha_blending: &'a AlphaBlending,
pub source_node_id: &'a Option<NodeId>,
}
impl<T> TableRowRef<'_, T> {
pub fn into_cloned(self) -> TableRow<T>
where
T: Clone,
{
TableRow {
element: self.element.clone(),
transform: *self.transform,
alpha_blending: *self.alpha_blending,
source_node_id: *self.source_node_id,
}
}
}
#[derive(Debug)]
pub struct TableRowMut<'a, T> {
pub element: &'a mut T,
pub transform: &'a mut DAffine2,
pub alpha_blending: &'a mut AlphaBlending,
pub source_node_id: &'a mut Option<NodeId>,
}
+10 -14
View File
@@ -1,6 +1,6 @@
use super::TextAlign; use super::TextAlign;
use crate::instances::Instance; use crate::table::{Table, TableRow};
use crate::vector::{PointId, VectorData, VectorDataTable}; use crate::vector::{PointId, VectorData};
use bezier_rs::{ManipulatorGroup, Subpath}; use bezier_rs::{ManipulatorGroup, Subpath};
use core::cell::RefCell; use core::cell::RefCell;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
@@ -24,7 +24,7 @@ struct PathBuilder {
current_subpath: Subpath<PointId>, current_subpath: Subpath<PointId>,
origin: DVec2, origin: DVec2,
glyph_subpaths: Vec<Subpath<PointId>>, glyph_subpaths: Vec<Subpath<PointId>>,
vector_table: VectorDataTable, vector_table: Table<VectorData>,
scale: f64, scale: f64,
id: PointId, id: PointId,
} }
@@ -51,15 +51,15 @@ impl PathBuilder {
} }
if per_glyph_instances { if per_glyph_instances {
self.vector_table.push(Instance { self.vector_table.push(TableRow {
instance: VectorData::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false), element: VectorData::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false),
transform: DAffine2::from_translation(glyph_offset), transform: DAffine2::from_translation(glyph_offset),
..Default::default() ..Default::default()
}); });
} else { } else {
for subpath in self.glyph_subpaths.drain(..) { for subpath in self.glyph_subpaths.drain(..) {
// Unwrapping here is ok because `self.vector_table` is initialized with a single `VectorData` // Unwrapping here is ok because `self.vector_table` is initialized with a single `VectorData`
self.vector_table.get_mut(0).unwrap().instance.append_subpath(subpath, false); self.vector_table.get_mut(0).unwrap().element.append_subpath(subpath, false);
} }
} }
} }
@@ -205,19 +205,15 @@ fn layout_text(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingC
Some(layout) Some(layout)
} }
pub fn to_path(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingConfig, per_glyph_instances: bool) -> VectorDataTable { pub fn to_path(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingConfig, per_glyph_instances: bool) -> Table<VectorData> {
let Some(layout) = layout_text(str, font_data, typesetting) else { let Some(layout) = layout_text(str, font_data, typesetting) else {
return VectorDataTable::new(VectorData::default()); return Table::new_from_element(VectorData::default());
}; };
let mut path_builder = PathBuilder { let mut path_builder = PathBuilder {
current_subpath: Subpath::new(Vec::new(), false), current_subpath: Subpath::new(Vec::new(), false),
glyph_subpaths: Vec::new(), glyph_subpaths: Vec::new(),
vector_table: if per_glyph_instances { vector_table: if per_glyph_instances { Table::new() } else { Table::new_from_element(VectorData::default()) },
VectorDataTable::default()
} else {
VectorDataTable::new(VectorData::default())
},
scale: layout.scale() as f64, scale: layout.scale() as f64,
id: PointId::ZERO, id: PointId::ZERO,
origin: DVec2::default(), origin: DVec2::default(),
@@ -232,7 +228,7 @@ pub fn to_path(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingC
} }
if path_builder.vector_table.is_empty() { if path_builder.vector_table.is_empty() {
path_builder.vector_table = VectorDataTable::new(VectorData::default()); path_builder.vector_table = Table::new_from_element(VectorData::default());
} }
path_builder.vector_table path_builder.vector_table
+25 -25
View File
@@ -1,8 +1,8 @@
use crate::instances::Instances; use crate::raster_types::{CPU, GPU, Raster};
use crate::raster_types::{CPU, GPU, RasterDataTable}; use crate::table::Table;
use crate::transform::{ApplyTransform, Footprint, Transform}; use crate::transform::{ApplyTransform, Footprint, Transform};
use crate::vector::VectorDataTable; use crate::vector::VectorData;
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, GraphicGroupTable, OwnedContextImpl}; use crate::{CloneVarArgs, Context, Ctx, ExtractAll, Graphic, OwnedContextImpl};
use core::f64; use core::f64;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
@@ -12,10 +12,10 @@ async fn transform<T: ApplyTransform + 'n + 'static>(
#[implementations( #[implementations(
Context -> DAffine2, Context -> DAffine2,
Context -> DVec2, Context -> DVec2,
Context -> VectorDataTable, Context -> Table<VectorData>,
Context -> GraphicGroupTable, Context -> Table<Graphic>,
Context -> RasterDataTable<CPU>, Context -> Table<Raster<CPU>>,
Context -> RasterDataTable<GPU>, Context -> Table<Raster<GPU>>,
)] )]
value: impl Node<Context<'static>, Output = T>, value: impl Node<Context<'static>, Output = T>,
translate: DVec2, translate: DVec2,
@@ -43,10 +43,10 @@ async fn transform<T: ApplyTransform + 'n + 'static>(
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
fn replace_transform<Data, TransformInput: Transform>( fn replace_transform<Data, TransformInput: Transform>(
_: impl Ctx, _: impl Ctx,
#[implementations(VectorDataTable, RasterDataTable<CPU>, GraphicGroupTable)] mut data: Instances<Data>, #[implementations(Table<VectorData>, Table<Raster<CPU>>, Table<Graphic>)] mut data: Table<Data>,
#[implementations(DAffine2)] transform: TransformInput, #[implementations(DAffine2)] transform: TransformInput,
) -> Instances<Data> { ) -> Table<Data> {
for data_transform in data.instance_mut_iter() { for data_transform in data.iter_mut() {
*data_transform.transform = transform.transform(); *data_transform.transform = transform.transform();
} }
data data
@@ -56,14 +56,14 @@ fn replace_transform<Data, TransformInput: Transform>(
async fn extract_transform<T>( async fn extract_transform<T>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
GraphicGroupTable, Table<Graphic>,
VectorDataTable, Table<VectorData>,
RasterDataTable<CPU>, Table<Raster<CPU>>,
RasterDataTable<GPU>, Table<Raster<GPU>>,
)] )]
vector_data: Instances<T>, vector_data: Table<T>,
) -> DAffine2 { ) -> DAffine2 {
vector_data.instance_ref_iter().next().map(|vector_data| *vector_data.transform).unwrap_or_default() vector_data.iter_ref().next().map(|vector_data| *vector_data.transform).unwrap_or_default()
} }
#[node_macro::node(category("Math: Transform"))] #[node_macro::node(category("Math: Transform"))]
@@ -90,10 +90,10 @@ fn decompose_scale(_: impl Ctx, transform: DAffine2) -> DVec2 {
async fn boundless_footprint<T: 'n + 'static>( async fn boundless_footprint<T: 'n + 'static>(
ctx: impl Ctx + CloneVarArgs + ExtractAll, ctx: impl Ctx + CloneVarArgs + ExtractAll,
#[implementations( #[implementations(
Context -> VectorDataTable, Context -> Table<VectorData>,
Context -> GraphicGroupTable, Context -> Table<Graphic>,
Context -> RasterDataTable<CPU>, Context -> Table<Raster<CPU>>,
Context -> RasterDataTable<GPU>, Context -> Table<Raster<GPU>>,
Context -> String, Context -> String,
Context -> f64, Context -> f64,
)] )]
@@ -108,10 +108,10 @@ async fn boundless_footprint<T: 'n + 'static>(
async fn freeze_real_time<T: 'n + 'static>( async fn freeze_real_time<T: 'n + 'static>(
ctx: impl Ctx + CloneVarArgs + ExtractAll, ctx: impl Ctx + CloneVarArgs + ExtractAll,
#[implementations( #[implementations(
Context -> VectorDataTable, Context -> Table<VectorData>,
Context -> GraphicGroupTable, Context -> Table<Graphic>,
Context -> RasterDataTable<CPU>, Context -> Table<Raster<CPU>>,
Context -> RasterDataTable<GPU>, Context -> Table<Raster<GPU>>,
Context -> String, Context -> String,
Context -> f64, Context -> f64,
)] )]
+7 -4
View File
@@ -167,14 +167,17 @@ fn migrate_type_descriptor_names<'de, D: serde::Deserializer<'de>>(deserializer:
let name = match name.as_str() { let name = match name.as_str() {
"f32" => "f64".to_string(), "f32" => "f64".to_string(),
"graphene_core::transform::Footprint" => "std::option::Option<std::sync::Arc<graphene_core::context::OwnedContextImpl>>".to_string(), "graphene_core::transform::Footprint" => "std::option::Option<std::sync::Arc<graphene_core::context::OwnedContextImpl>>".to_string(),
"graphene_core::graphic_element::GraphicGroup" => "graphene_core::instances::Instances<graphene_core::graphic_element::GraphicGroup>".to_string(), "graphene_core::graphic_element::GraphicGroup" => "graphene_core::table::Table<graphene_core::graphic_element::GraphicGroup>".to_string(),
"graphene_core::vector::vector_data::VectorData" => "graphene_core::instances::Instances<graphene_core::vector::vector_data::VectorData>".to_string(), "graphene_core::vector::vector_data::VectorData" => "graphene_core::table::Table<graphene_core::vector::vector_data::VectorData>".to_string(),
"graphene_core::raster::image::ImageFrame<Color>" "graphene_core::raster::image::ImageFrame<Color>"
| "graphene_core::raster::image::ImageFrame<graphene_core::raster::color::Color>" | "graphene_core::raster::image::ImageFrame<graphene_core::raster::color::Color>"
| "graphene_core::instances::Instances<graphene_core::raster::image::ImageFrame<Color>>" | "graphene_core::instances::Instances<graphene_core::raster::image::ImageFrame<Color>>"
| "graphene_core::instances::Instances<graphene_core::raster::image::ImageFrame<graphene_core::raster::color::Color>>" => { | "graphene_core::instances::Instances<graphene_core::raster::image::ImageFrame<graphene_core::raster::color::Color>>"
"graphene_core::instances::Instances<graphene_core::raster::image::Image<graphene_core::raster::color::Color>>".to_string() | "graphene_core::instances::Instances<graphene_core::raster::image::Image<graphene_core::raster::color::Color>>" => {
"graphene_core::table::Table<graphene_core::raster::image::Image<graphene_core::raster::color::Color>>".to_string()
} }
"graphene_core::instances::Instances<graphene_core::vector::vector_data::VectorData>" => "graphene_core::table::Table<graphene_core::vector::vector_data::VectorData>".to_string(),
"graphene_core::instances::Instances<graphene_core::graphic_element::Artboard>" => "graphene_core::table::Table<graphene_core::artboard::Artboard>".to_string(),
_ => name, _ => name,
}; };
@@ -1,33 +1,33 @@
use crate::instances::{InstanceRef, Instances}; use crate::raster_types::{CPU, Raster};
use crate::raster_types::{CPU, RasterDataTable}; use crate::table::{Table, TableRowRef};
use crate::vector::VectorDataTable; use crate::vector::VectorData;
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractIndex, ExtractVarArgs, GraphicElement, GraphicGroupTable, OwnedContextImpl}; use crate::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractIndex, ExtractVarArgs, Graphic, OwnedContextImpl};
use glam::DVec2; use glam::DVec2;
#[node_macro::node(name("Instance on Points"), category("Instancing"), path(graphene_core::vector))] #[node_macro::node(name("Instance on Points"), category("Instancing"), path(graphene_core::vector))]
async fn instance_on_points<T: Into<GraphicElement> + Default + Send + Clone + 'static>( async fn instance_on_points<T: Into<Graphic> + Default + Send + Clone + 'static>(
ctx: impl ExtractAll + CloneVarArgs + Sync + Ctx, ctx: impl ExtractAll + CloneVarArgs + Sync + Ctx,
points: VectorDataTable, points: Table<VectorData>,
#[implementations( #[implementations(
Context -> GraphicGroupTable, Context -> Table<Graphic>,
Context -> VectorDataTable, Context -> Table<VectorData>,
Context -> RasterDataTable<CPU> Context -> Table<Raster<CPU>>
)] )]
instance: impl Node<'n, Context<'static>, Output = Instances<T>>, instance: impl Node<'n, Context<'static>, Output = Table<T>>,
reverse: bool, reverse: bool,
) -> Instances<T> { ) -> Table<T> {
let mut result_table = Instances::<T>::default(); let mut result_table = Table::new();
for InstanceRef { instance: points, transform, .. } in points.instance_ref_iter() { for TableRowRef { element: points, transform, .. } in points.iter_ref() {
let mut iteration = async |index, point| { let mut iteration = async |index, point| {
let transformed_point = transform.transform_point2(point); let transformed_point = transform.transform_point2(point);
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index).with_vararg(Box::new(transformed_point)); let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index).with_vararg(Box::new(transformed_point));
let generated_instance = instance.eval(new_ctx.into_context()).await; let generated_instance = instance.eval(new_ctx.into_context()).await;
for mut instanced in generated_instance.instance_iter() { for mut generated_row in generated_instance.iter() {
instanced.transform.translation = transformed_point; generated_row.transform.translation = transformed_point;
result_table.push(instanced); result_table.push(generated_row);
} }
}; };
@@ -47,20 +47,20 @@ async fn instance_on_points<T: Into<GraphicElement> + Default + Send + Clone + '
} }
#[node_macro::node(category("Instancing"), path(graphene_core::vector))] #[node_macro::node(category("Instancing"), path(graphene_core::vector))]
async fn instance_repeat<T: Into<GraphicElement> + Default + Send + Clone + 'static>( async fn instance_repeat<T: Into<Graphic> + Default + Send + Clone + 'static>(
ctx: impl ExtractAll + CloneVarArgs + Ctx, ctx: impl ExtractAll + CloneVarArgs + Ctx,
#[implementations( #[implementations(
Context -> GraphicGroupTable, Context -> Table<Graphic>,
Context -> VectorDataTable, Context -> Table<VectorData>,
Context -> RasterDataTable<CPU> Context -> Table<Raster<CPU>>
)] )]
instance: impl Node<'n, Context<'static>, Output = Instances<T>>, instance: impl Node<'n, Context<'static>, Output = Table<T>>,
#[default(1)] count: u64, #[default(1)] count: u64,
reverse: bool, reverse: bool,
) -> Instances<T> { ) -> Table<T> {
let count = count.max(1) as usize; let count = count.max(1) as usize;
let mut result_table = Instances::<T>::default(); let mut result_table = Table::new();
for index in 0..count { for index in 0..count {
let index = if reverse { count - index - 1 } else { index }; let index = if reverse { count - index - 1 } else { index };
@@ -68,8 +68,8 @@ async fn instance_repeat<T: Into<GraphicElement> + Default + Send + Clone + 'sta
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index); let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index);
let generated_instance = instance.eval(new_ctx.into_context()).await; let generated_instance = instance.eval(new_ctx.into_context()).await;
for instanced in generated_instance.instance_iter() { for generated_row in generated_instance.iter() {
result_table.push(instanced); result_table.push(generated_row);
} }
} }
@@ -128,11 +128,11 @@ mod test {
); );
let positions = [DVec2::new(40., 20.), DVec2::ONE, DVec2::new(-42., 9.), DVec2::new(10., 345.)]; let positions = [DVec2::new(40., 20.), DVec2::ONE, DVec2::new(-42., 9.), DVec2::new(10., 345.)];
let points = VectorDataTable::new(VectorData::from_subpath(Subpath::from_anchors_linear(positions, false))); let points = Table::new_from_element(VectorData::from_subpath(Subpath::from_anchors_linear(positions, false)));
let repeated = super::instance_on_points(owned, points, &rect, false).await; let generated = super::instance_on_points(owned, points, &rect, false).await;
assert_eq!(repeated.len(), positions.len()); assert_eq!(generated.len(), positions.len());
for (position, instanced) in positions.into_iter().zip(repeated.instance_ref_iter()) { for (position, generated_row) in positions.into_iter().zip(generated.iter_ref()) {
let bounds = instanced.instance.bounding_box_with_transform(*instanced.transform).unwrap(); let bounds = generated_row.element.bounding_box_with_transform(*generated_row.transform).unwrap();
assert!(position.abs_diff_eq((bounds[0] + bounds[1]) / 2., 1e-10)); assert!(position.abs_diff_eq((bounds[0] + bounds[1]) / 2., 1e-10));
assert_eq!((bounds[1] - bounds[0]).x, position.y); assert_eq!((bounds[1] - bounds[0]).x, position.y);
} }
+28 -27
View File
@@ -2,21 +2,22 @@ use super::misc::{ArcType, AsU64, GridType};
use super::{PointId, SegmentId, StrokeId}; use super::{PointId, SegmentId, StrokeId};
use crate::Ctx; use crate::Ctx;
use crate::registry::types::{Angle, PixelSize}; use crate::registry::types::{Angle, PixelSize};
use crate::vector::{HandleId, VectorData, VectorDataTable}; use crate::table::Table;
use crate::vector::{HandleId, VectorData};
use bezier_rs::Subpath; use bezier_rs::Subpath;
use glam::DVec2; use glam::DVec2;
trait CornerRadius { trait CornerRadius {
fn generate(self, size: DVec2, clamped: bool) -> VectorDataTable; fn generate(self, size: DVec2, clamped: bool) -> Table<VectorData>;
} }
impl CornerRadius for f64 { impl CornerRadius for f64 {
fn generate(self, size: DVec2, clamped: bool) -> VectorDataTable { fn generate(self, size: DVec2, clamped: bool) -> Table<VectorData> {
let clamped_radius = if clamped { self.clamp(0., size.x.min(size.y).max(0.) / 2.) } else { self }; let clamped_radius = if clamped { self.clamp(0., size.x.min(size.y).max(0.) / 2.) } else { self };
VectorDataTable::new(VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4]))) Table::new_from_element(VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4])))
} }
} }
impl CornerRadius for [f64; 4] { impl CornerRadius for [f64; 4] {
fn generate(self, size: DVec2, clamped: bool) -> VectorDataTable { fn generate(self, size: DVec2, clamped: bool) -> Table<VectorData> {
let clamped_radius = if clamped { let clamped_radius = if clamped {
// Algorithm follows the CSS spec: <https://drafts.csswg.org/css-backgrounds/#corner-overlap> // Algorithm follows the CSS spec: <https://drafts.csswg.org/css-backgrounds/#corner-overlap>
@@ -32,7 +33,7 @@ impl CornerRadius for [f64; 4] {
} else { } else {
self self
}; };
VectorDataTable::new(VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius))) Table::new_from_element(VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius)))
} }
} }
@@ -43,9 +44,9 @@ fn circle(
#[unit(" px")] #[unit(" px")]
#[default(50.)] #[default(50.)]
radius: f64, radius: f64,
) -> VectorDataTable { ) -> Table<VectorData> {
let radius = radius.abs(); let radius = radius.abs();
VectorDataTable::new(VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius)))) Table::new_from_element(VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))
} }
#[node_macro::node(category("Vector: Shape"))] #[node_macro::node(category("Vector: Shape"))]
@@ -60,8 +61,8 @@ fn arc(
#[range((0., 360.))] #[range((0., 360.))]
sweep_angle: Angle, sweep_angle: Angle,
arc_type: ArcType, arc_type: ArcType,
) -> VectorDataTable { ) -> Table<VectorData> {
VectorDataTable::new(VectorData::from_subpath(Subpath::new_arc( Table::new_from_element(VectorData::from_subpath(Subpath::new_arc(
radius, radius,
start_angle / 360. * std::f64::consts::TAU, start_angle / 360. * std::f64::consts::TAU,
sweep_angle / 360. * std::f64::consts::TAU, sweep_angle / 360. * std::f64::consts::TAU,
@@ -83,7 +84,7 @@ fn ellipse(
#[unit(" px")] #[unit(" px")]
#[default(25)] #[default(25)]
radius_y: f64, radius_y: f64,
) -> VectorDataTable { ) -> Table<VectorData> {
let radius = DVec2::new(radius_x, radius_y); let radius = DVec2::new(radius_x, radius_y);
let corner1 = -radius; let corner1 = -radius;
let corner2 = radius; let corner2 = radius;
@@ -97,7 +98,7 @@ fn ellipse(
.push([HandleId::end(ellipse.segment_domain.ids()[i]), HandleId::primary(ellipse.segment_domain.ids()[(i + 1) % len])]); .push([HandleId::end(ellipse.segment_domain.ids()[i]), HandleId::primary(ellipse.segment_domain.ids()[(i + 1) % len])]);
} }
VectorDataTable::new(ellipse) Table::new_from_element(ellipse)
} }
#[node_macro::node(category("Vector: Shape"), properties("rectangle_properties"))] #[node_macro::node(category("Vector: Shape"), properties("rectangle_properties"))]
@@ -113,7 +114,7 @@ fn rectangle<T: CornerRadius>(
_individual_corner_radii: bool, // TODO: Move this to the bottom once we have a migration capability _individual_corner_radii: bool, // TODO: Move this to the bottom once we have a migration capability
#[implementations(f64, [f64; 4])] corner_radius: T, #[implementations(f64, [f64; 4])] corner_radius: T,
#[default(true)] clamped: bool, #[default(true)] clamped: bool,
) -> VectorDataTable { ) -> Table<VectorData> {
corner_radius.generate(DVec2::new(width, height), clamped) corner_radius.generate(DVec2::new(width, height), clamped)
} }
@@ -128,10 +129,10 @@ fn regular_polygon<T: AsU64>(
#[unit(" px")] #[unit(" px")]
#[default(50)] #[default(50)]
radius: f64, radius: f64,
) -> VectorDataTable { ) -> Table<VectorData> {
let points = sides.as_u64(); let points = sides.as_u64();
let radius: f64 = radius * 2.; let radius: f64 = radius * 2.;
VectorDataTable::new(VectorData::from_subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius))) Table::new_from_element(VectorData::from_subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius)))
} }
#[node_macro::node(category("Vector: Shape"))] #[node_macro::node(category("Vector: Shape"))]
@@ -148,17 +149,17 @@ fn star<T: AsU64>(
#[unit(" px")] #[unit(" px")]
#[default(25)] #[default(25)]
radius_2: f64, radius_2: f64,
) -> VectorDataTable { ) -> Table<VectorData> {
let points = sides.as_u64(); let points = sides.as_u64();
let diameter: f64 = radius_1 * 2.; let diameter: f64 = radius_1 * 2.;
let inner_diameter = radius_2 * 2.; let inner_diameter = radius_2 * 2.;
VectorDataTable::new(VectorData::from_subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter))) Table::new_from_element(VectorData::from_subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter)))
} }
#[node_macro::node(category("Vector: Shape"))] #[node_macro::node(category("Vector: Shape"))]
fn line(_: impl Ctx, _primary: (), #[default(0., 0.)] start: PixelSize, #[default(100., 100.)] end: PixelSize) -> VectorDataTable { fn line(_: impl Ctx, _primary: (), #[default(0., 0.)] start: PixelSize, #[default(100., 100.)] end: PixelSize) -> Table<VectorData> {
VectorDataTable::new(VectorData::from_subpath(Subpath::new_line(start, end))) Table::new_from_element(VectorData::from_subpath(Subpath::new_line(start, end)))
} }
trait GridSpacing { trait GridSpacing {
@@ -188,7 +189,7 @@ fn grid<T: GridSpacing>(
#[default(10)] columns: u32, #[default(10)] columns: u32,
#[default(10)] rows: u32, #[default(10)] rows: u32,
#[default(30., 30.)] angles: DVec2, #[default(30., 30.)] angles: DVec2,
) -> VectorDataTable { ) -> Table<VectorData> {
let (x_spacing, y_spacing) = spacing.as_dvec2().into(); let (x_spacing, y_spacing) = spacing.as_dvec2().into();
let (angle_a, angle_b) = angles.into(); let (angle_a, angle_b) = angles.into();
@@ -270,7 +271,7 @@ fn grid<T: GridSpacing>(
} }
} }
VectorDataTable::new(vector_data) Table::new_from_element(vector_data)
} }
#[cfg(test)] #[cfg(test)]
@@ -284,9 +285,9 @@ mod tests {
// Works properly // Works properly
let grid = grid((), (), GridType::Isometric, 10., 5, 5, (30., 30.).into()); let grid = grid((), (), GridType::Isometric, 10., 5, 5, (30., 30.).into());
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.point_domain.ids().len(), 5 * 5); assert_eq!(grid.iter_ref().next().unwrap().element.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9); assert_eq!(grid.iter_ref().next().unwrap().element.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter() { for (_, bezier, _, _) in grid.iter_ref().next().unwrap().element.segment_bezier_iter() {
assert_eq!(bezier.handles, bezier_rs::BezierHandles::Linear); assert_eq!(bezier.handles, bezier_rs::BezierHandles::Linear);
assert!( assert!(
((bezier.start - bezier.end).length() - 10.).abs() < 1e-5, ((bezier.start - bezier.end).length() - 10.).abs() < 1e-5,
@@ -299,9 +300,9 @@ mod tests {
#[test] #[test]
fn skew_isometric_grid_test() { fn skew_isometric_grid_test() {
let grid = grid((), (), GridType::Isometric, 10., 5, 5, (40., 30.).into()); let grid = grid((), (), GridType::Isometric, 10., 5, 5, (40., 30.).into());
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.point_domain.ids().len(), 5 * 5); assert_eq!(grid.iter_ref().next().unwrap().element.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9); assert_eq!(grid.iter_ref().next().unwrap().element.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter() { for (_, bezier, _, _) in grid.iter_ref().next().unwrap().element.segment_bezier_iter() {
assert_eq!(bezier.handles, bezier_rs::BezierHandles::Linear); assert_eq!(bezier.handles, bezier_rs::BezierHandles::Linear);
let vector = bezier.start - bezier.end; let vector = bezier.start - bezier.end;
let angle = (vector.angle_to(DVec2::X).to_degrees() + 180.) % 180.; let angle = (vector.angle_to(DVec2::X).to_degrees() + 180.) % 180.;
+17 -19
View File
@@ -5,11 +5,11 @@ mod modification;
use super::misc::{dvec2_to_point, point_to_dvec2}; use super::misc::{dvec2_to_point, point_to_dvec2};
use super::style::{PathStyle, Stroke}; use super::style::{PathStyle, Stroke};
use crate::bounds::BoundingBox; use crate::bounds::BoundingBox;
use crate::instances::Instances;
use crate::math::quad::Quad; use crate::math::quad::Quad;
use crate::table::Table;
use crate::transform::Transform; use crate::transform::Transform;
use crate::vector::click_target::{ClickTargetType, FreePoint}; use crate::vector::click_target::{ClickTargetType, FreePoint};
use crate::{AlphaBlending, Color, GraphicGroupTable}; use crate::{AlphaBlending, Color, Graphic};
pub use attributes::*; pub use attributes::*;
use bezier_rs::{BezierHandles, ManipulatorGroup}; use bezier_rs::{BezierHandles, ManipulatorGroup};
use core::borrow::Borrow; use core::borrow::Borrow;
@@ -22,7 +22,7 @@ pub use modification::*;
use std::collections::HashMap; use std::collections::HashMap;
// TODO: Eventually remove this migration document upgrade code // TODO: Eventually remove this migration document upgrade code
pub fn migrate_vector_data<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<VectorDataTable, D::Error> { pub fn migrate_vector_data<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<VectorData>, D::Error> {
use serde::Deserialize; use serde::Deserialize;
#[derive(Clone, Debug, PartialEq, DynAny, serde::Serialize, serde::Deserialize)] #[derive(Clone, Debug, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
@@ -41,7 +41,7 @@ pub fn migrate_vector_data<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
pub region_domain: RegionDomain, pub region_domain: RegionDomain,
// Used to store the upstream graphic group during destructive Boolean Operations (and other nodes with a similar effect) so that click targets can be preserved. // Used to store the upstream graphic group during destructive Boolean Operations (and other nodes with a similar effect) so that click targets can be preserved.
pub upstream_graphic_group: Option<GraphicGroupTable>, pub upstream_graphic_group: Option<Table<Graphic>>,
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize)]
@@ -50,13 +50,13 @@ pub fn migrate_vector_data<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
enum EitherFormat { enum EitherFormat {
VectorData(VectorData), VectorData(VectorData),
OldVectorData(OldVectorData), OldVectorData(OldVectorData),
VectorDataTable(VectorDataTable), VectorDataTable(Table<VectorData>),
} }
Ok(match EitherFormat::deserialize(deserializer)? { Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::VectorData(vector_data) => VectorDataTable::new(vector_data), EitherFormat::VectorData(vector_data) => Table::new_from_element(vector_data),
EitherFormat::OldVectorData(old) => { EitherFormat::OldVectorData(old) => {
let mut vector_data_table = VectorDataTable::new(VectorData { let mut vector_data_table = Table::new_from_element(VectorData {
style: old.style, style: old.style,
colinear_manipulators: old.colinear_manipulators, colinear_manipulators: old.colinear_manipulators,
point_domain: old.point_domain, point_domain: old.point_domain,
@@ -64,16 +64,14 @@ pub fn migrate_vector_data<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
region_domain: old.region_domain, region_domain: old.region_domain,
upstream_graphic_group: old.upstream_graphic_group, upstream_graphic_group: old.upstream_graphic_group,
}); });
*vector_data_table.instance_mut_iter().next().unwrap().transform = old.transform; *vector_data_table.iter_mut().next().unwrap().transform = old.transform;
*vector_data_table.instance_mut_iter().next().unwrap().alpha_blending = old.alpha_blending; *vector_data_table.iter_mut().next().unwrap().alpha_blending = old.alpha_blending;
vector_data_table vector_data_table
} }
EitherFormat::VectorDataTable(vector_data_table) => vector_data_table, EitherFormat::VectorDataTable(vector_data_table) => vector_data_table,
}) })
} }
pub type VectorDataTable = Instances<VectorData>;
/// [VectorData] is passed between nodes. /// [VectorData] is passed between nodes.
/// It contains a list of subpaths (that may be open or closed), a transform, and some style information. /// It contains a list of subpaths (that may be open or closed), a transform, and some style information.
/// ///
@@ -91,7 +89,7 @@ pub struct VectorData {
pub region_domain: RegionDomain, pub region_domain: RegionDomain,
// Used to store the upstream graphic group during destructive Boolean Operations (and other nodes with a similar effect) so that click targets can be preserved. // Used to store the upstream graphic group during destructive Boolean Operations (and other nodes with a similar effect) so that click targets can be preserved.
pub upstream_graphic_group: Option<GraphicGroupTable>, pub upstream_graphic_group: Option<Table<Graphic>>,
} }
impl Default for VectorData { impl Default for VectorData {
@@ -495,24 +493,24 @@ impl VectorData {
} }
} }
impl BoundingBox for VectorDataTable { impl BoundingBox for Table<VectorData> {
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> { fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> {
self.instance_ref_iter() self.iter_ref()
.flat_map(|instance| { .flat_map(|row| {
if !include_stroke { if !include_stroke {
return instance.instance.bounding_box_with_transform(transform * *instance.transform); return row.element.bounding_box_with_transform(transform * *row.transform);
} }
let stroke_width = instance.instance.style.stroke().map(|s| s.weight()).unwrap_or_default(); let stroke_width = row.element.style.stroke().map(|s| s.weight()).unwrap_or_default();
let miter_limit = instance.instance.style.stroke().map(|s| s.join_miter_limit).unwrap_or(1.); let miter_limit = row.element.style.stroke().map(|s| s.join_miter_limit).unwrap_or(1.);
let scale = transform.decompose_scale(); let scale = transform.decompose_scale();
// We use the full line width here to account for different styles of stroke caps // We use the full line width here to account for different styles of stroke caps
let offset = DVec2::splat(stroke_width * scale.x.max(scale.y) * miter_limit); let offset = DVec2::splat(stroke_width * scale.x.max(scale.y) * miter_limit);
instance.instance.bounding_box_with_transform(transform * *instance.transform).map(|[a, b]| [a - offset, b + offset]) row.element.bounding_box_with_transform(transform * *row.transform).map(|[a, b]| [a - offset, b + offset])
}) })
.reduce(Quad::combine_bounds) .reduce(Quad::combine_bounds)
} }
@@ -48,7 +48,7 @@ macro_rules! create_ids {
}; };
} }
create_ids! { InstanceId, PointId, SegmentId, RegionId, StrokeId, FillId } create_ids! { PointId, SegmentId, RegionId, StrokeId, FillId }
/// A no-op hasher that allows writing u64s (the id type). /// A no-op hasher that allows writing u64s (the id type).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
@@ -305,7 +305,7 @@ impl SegmentDomain {
&self.stroke &self.stroke
} }
pub(crate) fn push(&mut self, id: SegmentId, start: usize, end: usize, handles: BezierHandles, stroke: StrokeId) { pub fn push(&mut self, id: SegmentId, start: usize, end: usize, handles: BezierHandles, stroke: StrokeId) {
debug_assert!(!self.id.contains(&id), "Tried to push an existing point to a point domain"); debug_assert!(!self.id.contains(&id), "Tried to push an existing point to a point domain");
self.id.push(id); self.id.push(id);
@@ -1,6 +1,6 @@
use super::*; use super::*;
use crate::Ctx; use crate::Ctx;
use crate::instances::Instance; use crate::table::TableRow;
use crate::uuid::{NodeId, generate_uuid}; use crate::uuid::{NodeId, generate_uuid};
use bezier_rs::BezierHandles; use bezier_rs::BezierHandles;
use dyn_any::DynAny; use dyn_any::DynAny;
@@ -420,35 +420,35 @@ impl Hash for VectorModification {
/// Applies a diff modification to a vector path. /// Applies a diff modification to a vector path.
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modification: Box<VectorModification>, node_path: Vec<NodeId>) -> VectorDataTable { async fn path_modify(_ctx: impl Ctx, mut vector_data: Table<VectorData>, modification: Box<VectorModification>, node_path: Vec<NodeId>) -> Table<VectorData> {
if vector_data.is_empty() { if vector_data.is_empty() {
vector_data.push(Instance::default()); vector_data.push(TableRow::default());
} }
let vector_data_instance = vector_data.get_mut(0).expect("push should give one item"); let row = vector_data.get_mut(0).expect("push should give one item");
modification.apply(vector_data_instance.instance); modification.apply(row.element);
// Update the source node id // Update the source node id
let this_node_path = node_path.iter().rev().nth(1).copied(); let this_node_path = node_path.iter().rev().nth(1).copied();
*vector_data_instance.source_node_id = vector_data_instance.source_node_id.or(this_node_path); *row.source_node_id = row.source_node_id.or(this_node_path);
if vector_data.len() > 1 { if vector_data.len() > 1 {
warn!("The path modify ran on {} instances of vector data. Only the first can be modified.", vector_data.len()); warn!("The path modify ran on {} rows of vector data. Only the first can be modified.", vector_data.len());
} }
vector_data vector_data
} }
/// Applies the vector path's local transformation to its geometry and resets it to the identity. /// Applies the vector path's local transformation to its geometry and resets it to the identity.
#[node_macro::node(category("Vector"))] #[node_macro::node(category("Vector"))]
async fn apply_transform(_ctx: impl Ctx, mut vector_data: VectorDataTable) -> VectorDataTable { async fn apply_transform(_ctx: impl Ctx, mut vector_data: Table<VectorData>) -> Table<VectorData> {
for vector_data_instance in vector_data.instance_mut_iter() { for row in vector_data.iter_mut() {
let vector_data = vector_data_instance.instance; let vector_data = row.element;
let transform = *vector_data_instance.transform; let transform = *row.transform;
for (_, point) in vector_data.point_domain.positions_mut() { for (_, point) in vector_data.point_domain.positions_mut() {
*point = transform.transform_point2(*point); *point = transform.transform_point2(*point);
} }
*vector_data_instance.transform = DAffine2::IDENTITY; *row.transform = DAffine2::IDENTITY;
} }
vector_data vector_data
File diff suppressed because it is too large Load Diff
+76 -76
View File
@@ -1,11 +1,11 @@
use bezier_rs::{ManipulatorGroup, Subpath}; use bezier_rs::{ManipulatorGroup, Subpath};
use dyn_any::DynAny; use dyn_any::DynAny;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use graphene_core::instances::{Instance, InstanceRef}; use graphene_core::table::{Table, TableRow, TableRowRef};
use graphene_core::vector::algorithms::merge_by_distance::MergeByDistanceExt; use graphene_core::vector::algorithms::merge_by_distance::MergeByDistanceExt;
use graphene_core::vector::style::Fill; use graphene_core::vector::style::Fill;
use graphene_core::vector::{PointId, VectorData, VectorDataTable}; use graphene_core::vector::{PointId, VectorData};
use graphene_core::{Color, Ctx, GraphicElement, GraphicGroupTable}; use graphene_core::{Color, Ctx, Graphic};
pub use path_bool as path_bool_lib; pub use path_bool as path_bool_lib;
use path_bool::{FillRule, PathBooleanOperation}; use path_bool::{FillRule, PathBooleanOperation};
use std::ops::Mul; use std::ops::Mul;
@@ -32,10 +32,10 @@ pub enum BooleanOperation {
/// Combines the geometric forms of one or more closed paths into a new vector path that results from cutting or joining the paths by the chosen method. /// Combines the geometric forms of one or more closed paths into a new vector path that results from cutting or joining the paths by the chosen method.
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
async fn boolean_operation<I: Into<GraphicGroupTable> + 'n + Send + Clone>( async fn boolean_operation<I: Into<Table<Graphic>> + 'n + Send + Clone>(
_: impl Ctx, _: impl Ctx,
/// The group of paths to perform the boolean operation on. Nested groups are automatically flattened. /// The group of paths to perform the boolean operation on. Nested groups are automatically flattened.
#[implementations(GraphicGroupTable, VectorDataTable)] #[implementations(Table<Graphic>, Table<VectorData>)]
group_of_paths: I, group_of_paths: I,
/// Which boolean operation to perform on the paths. /// Which boolean operation to perform on the paths.
/// ///
@@ -44,29 +44,29 @@ async fn boolean_operation<I: Into<GraphicGroupTable> + 'n + Send + Clone>(
/// Intersection cuts away all but the overlapping areas shared by every path. /// Intersection cuts away all but the overlapping areas shared by every path.
/// Difference cuts away the overlapping areas shared by every path, leaving only the non-overlapping areas. /// Difference cuts away the overlapping areas shared by every path, leaving only the non-overlapping areas.
operation: BooleanOperation, operation: BooleanOperation,
) -> VectorDataTable { ) -> Table<VectorData> {
let group_of_paths = group_of_paths.into(); let group_of_paths = group_of_paths.into();
// The first index is the bottom of the stack // The first index is the bottom of the stack
let mut result_vector_data_table = boolean_operation_on_vector_data_table(flatten_vector_data(&group_of_paths).instance_ref_iter(), operation); let mut result_vector_data_table = boolean_operation_on_vector_data_table(flatten_vector_data(&group_of_paths).iter_ref(), operation);
// Replace the transformation matrix with a mutation of the vector points themselves // Replace the transformation matrix with a mutation of the vector points themselves
if let Some(result_vector_data) = result_vector_data_table.instance_mut_iter().next() { if let Some(result_vector_data) = result_vector_data_table.iter_mut().next() {
let transform = *result_vector_data.transform; let transform = *result_vector_data.transform;
*result_vector_data.transform = DAffine2::IDENTITY; *result_vector_data.transform = DAffine2::IDENTITY;
VectorData::transform(result_vector_data.instance, transform); VectorData::transform(result_vector_data.element, transform);
result_vector_data.instance.style.set_stroke_transform(DAffine2::IDENTITY); result_vector_data.element.style.set_stroke_transform(DAffine2::IDENTITY);
result_vector_data.instance.upstream_graphic_group = Some(group_of_paths.clone()); result_vector_data.element.upstream_graphic_group = Some(group_of_paths.clone());
// Clean up the boolean operation result by merging duplicated points // Clean up the boolean operation result by merging duplicated points
result_vector_data.instance.merge_by_distance_spatial(*result_vector_data.transform, 0.0001); result_vector_data.element.merge_by_distance_spatial(*result_vector_data.transform, 0.0001);
} }
result_vector_data_table result_vector_data_table
} }
fn boolean_operation_on_vector_data_table<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, VectorData>> + Clone, boolean_operation: BooleanOperation) -> VectorDataTable { fn boolean_operation_on_vector_data_table<'a>(vector_data: impl DoubleEndedIterator<Item = TableRowRef<'a, VectorData>> + Clone, boolean_operation: BooleanOperation) -> Table<VectorData> {
match boolean_operation { match boolean_operation {
BooleanOperation::Union => union(vector_data), BooleanOperation::Union => union(vector_data),
BooleanOperation::SubtractFront => subtract(vector_data), BooleanOperation::SubtractFront => subtract(vector_data),
@@ -76,23 +76,23 @@ fn boolean_operation_on_vector_data_table<'a>(vector_data: impl DoubleEndedItera
} }
} }
fn union<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, VectorData>>) -> VectorDataTable { fn union<'a>(vector_data: impl DoubleEndedIterator<Item = TableRowRef<'a, VectorData>>) -> Table<VectorData> {
// Reverse vector data so that the result style is the style of the first vector data // Reverse vector data so that the result style is the style of the first vector data
let mut vector_data_reversed = vector_data.rev(); let mut vector_data_reversed = vector_data.rev();
let mut result_vector_data_table = VectorDataTable::new_instance(vector_data_reversed.next().map(|x| x.to_instance_cloned()).unwrap_or_default()); let mut result_vector_data_table = Table::new_from_row(vector_data_reversed.next().map(|x| x.into_cloned()).unwrap_or_default());
let mut first_instance = result_vector_data_table.instance_mut_iter().next().expect("Expected the one instance we just pushed"); let mut first_row = result_vector_data_table.iter_mut().next().expect("Expected the one row we just pushed");
// Loop over all vector data and union it with the result // Loop over all vector data and union it with the result
let default = Instance::default(); let default = TableRow::default();
let mut second_vector_data = Some(vector_data_reversed.next().unwrap_or(default.to_instance_ref())); let mut second_vector_data = Some(vector_data_reversed.next().unwrap_or(default.as_ref()));
while let Some(lower_vector_data) = second_vector_data { while let Some(lower_vector_data) = second_vector_data {
let transform_of_lower_into_space_of_upper = first_instance.transform.inverse() * *lower_vector_data.transform; let transform_of_lower_into_space_of_upper = first_row.transform.inverse() * *lower_vector_data.transform;
let result = &mut first_instance.instance; let result = &mut first_row.element;
let upper_path_string = to_path(result, DAffine2::IDENTITY); let upper_path_string = to_path(result, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.instance, transform_of_lower_into_space_of_upper); let lower_path_string = to_path(lower_vector_data.element, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)] #[allow(unused_unsafe)]
let boolean_operation_string = unsafe { boolean_union(upper_path_string, lower_path_string) }; let boolean_operation_string = unsafe { boolean_union(upper_path_string, lower_path_string) };
@@ -109,21 +109,21 @@ fn union<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, Vector
result_vector_data_table result_vector_data_table
} }
fn subtract<'a>(vector_data: impl Iterator<Item = InstanceRef<'a, VectorData>>) -> VectorDataTable { fn subtract<'a>(vector_data: impl Iterator<Item = TableRowRef<'a, VectorData>>) -> Table<VectorData> {
let mut vector_data = vector_data.into_iter(); let mut vector_data = vector_data.into_iter();
let mut result_vector_data_table = VectorDataTable::new_instance(vector_data.next().map(|x| x.to_instance_cloned()).unwrap_or_default()); let mut result_vector_data_table = Table::new_from_row(vector_data.next().map(|x| x.into_cloned()).unwrap_or_default());
let mut first_instance = result_vector_data_table.instance_mut_iter().next().expect("Expected the one instance we just pushed"); let mut first_row = result_vector_data_table.iter_mut().next().expect("Expected the one row we just pushed");
let mut next_vector_data = vector_data.next(); let mut next_vector_data = vector_data.next();
while let Some(lower_vector_data) = next_vector_data { while let Some(lower_vector_data) = next_vector_data {
let transform_of_lower_into_space_of_upper = first_instance.transform.inverse() * *lower_vector_data.transform; let transform_of_lower_into_space_of_upper = first_row.transform.inverse() * *lower_vector_data.transform;
let result = &mut first_instance.instance; let result = &mut first_row.element;
let upper_path_string = to_path(result, DAffine2::IDENTITY); let upper_path_string = to_path(result, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.instance, transform_of_lower_into_space_of_upper); let lower_path_string = to_path(lower_vector_data.element, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)] #[allow(unused_unsafe)]
let boolean_operation_string = unsafe { boolean_subtract(upper_path_string, lower_path_string) }; let boolean_operation_string = unsafe { boolean_subtract(upper_path_string, lower_path_string) };
@@ -140,23 +140,23 @@ fn subtract<'a>(vector_data: impl Iterator<Item = InstanceRef<'a, VectorData>>)
result_vector_data_table result_vector_data_table
} }
fn intersect<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, VectorData>>) -> VectorDataTable { fn intersect<'a>(vector_data: impl DoubleEndedIterator<Item = TableRowRef<'a, VectorData>>) -> Table<VectorData> {
let mut vector_data = vector_data.rev(); let mut vector_data = vector_data.rev();
let mut result_vector_data_table = VectorDataTable::new_instance(vector_data.next().map(|x| x.to_instance_cloned()).unwrap_or_default()); let mut result_vector_data_table = Table::new_from_row(vector_data.next().map(|x| x.into_cloned()).unwrap_or_default());
let mut first_instance = result_vector_data_table.instance_mut_iter().next().expect("Expected the one instance we just pushed"); let mut first_row = result_vector_data_table.iter_mut().next().expect("Expected the one row we just pushed");
let default = Instance::default(); let default = TableRow::default();
let mut second_vector_data = Some(vector_data.next().unwrap_or(default.to_instance_ref())); let mut second_vector_data = Some(vector_data.next().unwrap_or(default.as_ref()));
// For each vector data, set the result to the intersection of that data and the result // For each vector data, set the result to the intersection of that data and the result
while let Some(lower_vector_data) = second_vector_data { while let Some(lower_vector_data) = second_vector_data {
let transform_of_lower_into_space_of_upper = first_instance.transform.inverse() * *lower_vector_data.transform; let transform_of_lower_into_space_of_upper = first_row.transform.inverse() * *lower_vector_data.transform;
let result = &mut first_instance.instance; let result = &mut first_row.element;
let upper_path_string = to_path(result, DAffine2::IDENTITY); let upper_path_string = to_path(result, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.instance, transform_of_lower_into_space_of_upper); let lower_path_string = to_path(lower_vector_data.element, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)] #[allow(unused_unsafe)]
let boolean_operation_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) }; let boolean_operation_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) };
@@ -172,45 +172,45 @@ fn intersect<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, Ve
result_vector_data_table result_vector_data_table
} }
fn difference<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, VectorData>> + Clone) -> VectorDataTable { fn difference<'a>(vector_data: impl DoubleEndedIterator<Item = TableRowRef<'a, VectorData>> + Clone) -> Table<VectorData> {
let mut vector_data_iter = vector_data.clone().rev(); let mut vector_data_iter = vector_data.clone().rev();
let mut any_intersection = Instance::default(); let mut any_intersection = TableRow::default();
let default = Instance::default(); let default = TableRow::default();
let mut second_vector_data = Some(vector_data_iter.next().unwrap_or(default.to_instance_ref())); let mut second_vector_data = Some(vector_data_iter.next().unwrap_or(default.as_ref()));
// Find where all vector data intersect at least once // Find where all vector data intersect at least once
while let Some(lower_vector_data) = second_vector_data { while let Some(lower_vector_data) = second_vector_data {
let filtered_vector_data = vector_data.clone().filter(|v| *v != lower_vector_data).collect::<Vec<_>>().into_iter(); let filtered_vector_data = vector_data.clone().filter(|v| *v != lower_vector_data).collect::<Vec<_>>().into_iter();
let unioned = boolean_operation_on_vector_data_table(filtered_vector_data, BooleanOperation::Union); let unioned = boolean_operation_on_vector_data_table(filtered_vector_data, BooleanOperation::Union);
let first_instance = unioned.instance_ref_iter().next().expect("Expected at least one instance after the boolean union"); let first_row = unioned.iter_ref().next().expect("Expected at least one row after the boolean union");
let transform_of_lower_into_space_of_upper = first_instance.transform.inverse() * *lower_vector_data.transform; let transform_of_lower_into_space_of_upper = first_row.transform.inverse() * *lower_vector_data.transform;
let upper_path_string = to_path(first_instance.instance, DAffine2::IDENTITY); let upper_path_string = to_path(first_row.element, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.instance, transform_of_lower_into_space_of_upper); let lower_path_string = to_path(lower_vector_data.element, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)] #[allow(unused_unsafe)]
let boolean_intersection_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) }; let boolean_intersection_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) };
let mut instance = from_path(&boolean_intersection_string); let mut element = from_path(&boolean_intersection_string);
instance.style = first_instance.instance.style.clone(); element.style = first_row.element.style.clone();
let boolean_intersection_result = Instance { let boolean_intersection_result = TableRow {
instance, element,
transform: *first_instance.transform, transform: *first_row.transform,
alpha_blending: *first_instance.alpha_blending, alpha_blending: *first_row.alpha_blending,
source_node_id: *first_instance.source_node_id, source_node_id: *first_row.source_node_id,
}; };
let transform_of_lower_into_space_of_upper = boolean_intersection_result.transform.inverse() * any_intersection.transform; let transform_of_lower_into_space_of_upper = boolean_intersection_result.transform.inverse() * any_intersection.transform;
let upper_path_string = to_path(&boolean_intersection_result.instance, DAffine2::IDENTITY); let upper_path_string = to_path(&boolean_intersection_result.element, DAffine2::IDENTITY);
let lower_path_string = to_path(&any_intersection.instance, transform_of_lower_into_space_of_upper); let lower_path_string = to_path(&any_intersection.element, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)] #[allow(unused_unsafe)]
let union_result = from_path(&unsafe { boolean_union(upper_path_string, lower_path_string) }); let union_result = from_path(&unsafe { boolean_union(upper_path_string, lower_path_string) });
any_intersection.instance = union_result; any_intersection.element = union_result;
any_intersection.transform = boolean_intersection_result.transform; any_intersection.transform = boolean_intersection_result.transform;
any_intersection.instance.style = boolean_intersection_result.instance.style.clone(); any_intersection.element.style = boolean_intersection_result.element.style.clone();
any_intersection.alpha_blending = boolean_intersection_result.alpha_blending; any_intersection.alpha_blending = boolean_intersection_result.alpha_blending;
second_vector_data = vector_data_iter.next(); second_vector_data = vector_data_iter.next();
@@ -218,18 +218,18 @@ fn difference<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, V
// Subtract the area where they intersect at least once from the union of all vector data // Subtract the area where they intersect at least once from the union of all vector data
let union = boolean_operation_on_vector_data_table(vector_data, BooleanOperation::Union); let union = boolean_operation_on_vector_data_table(vector_data, BooleanOperation::Union);
boolean_operation_on_vector_data_table(union.instance_ref_iter().chain(std::iter::once(any_intersection.to_instance_ref())), BooleanOperation::SubtractFront) boolean_operation_on_vector_data_table(union.iter_ref().chain(std::iter::once(any_intersection.as_ref())), BooleanOperation::SubtractFront)
} }
fn flatten_vector_data(graphic_group_table: &GraphicGroupTable) -> VectorDataTable { fn flatten_vector_data(graphic_group_table: &Table<Graphic>) -> Table<VectorData> {
graphic_group_table graphic_group_table
.instance_ref_iter() .iter_ref()
.flat_map(|element| { .flat_map(|element| {
match element.instance.clone() { match element.element.clone() {
GraphicElement::VectorData(vector_data) => { Graphic::VectorData(vector_data) => {
// Apply the parent group's transform to each element of vector data // Apply the parent group's transform to each element of vector data
vector_data vector_data
.instance_iter() .iter()
.map(|mut sub_vector_data| { .map(|mut sub_vector_data| {
sub_vector_data.transform = *element.transform * sub_vector_data.transform; sub_vector_data.transform = *element.transform * sub_vector_data.transform;
@@ -237,48 +237,48 @@ fn flatten_vector_data(graphic_group_table: &GraphicGroupTable) -> VectorDataTab
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
} }
GraphicElement::RasterDataCPU(image) => { Graphic::RasterDataCPU(image) => {
let make_instance = |transform| { let make_row = |transform| {
// Convert the image frame into a rectangular subpath with the image's transform // Convert the image frame into a rectangular subpath with the image's transform
let mut subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE); let mut subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
subpath.apply_transform(transform); subpath.apply_transform(transform);
// Create a vector data table row from the rectangular subpath, with a default black fill // Create a vector data table row from the rectangular subpath, with a default black fill
let mut instance = VectorData::from_subpath(subpath); let mut element = VectorData::from_subpath(subpath);
instance.style.set_fill(Fill::Solid(Color::BLACK)); element.style.set_fill(Fill::Solid(Color::BLACK));
Instance { instance, ..Default::default() } TableRow { element, ..Default::default() }
}; };
// Apply the parent group's transform to each element of raster data // Apply the parent group's transform to each element of raster data
image.instance_ref_iter().map(|instance| make_instance(*element.transform * *instance.transform)).collect::<Vec<_>>() image.iter_ref().map(|row| make_row(*element.transform * *row.transform)).collect::<Vec<_>>()
} }
GraphicElement::RasterDataGPU(image) => { Graphic::RasterDataGPU(image) => {
let make_instance = |transform| { let make_row = |transform| {
// Convert the image frame into a rectangular subpath with the image's transform // Convert the image frame into a rectangular subpath with the image's transform
let mut subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE); let mut subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
subpath.apply_transform(transform); subpath.apply_transform(transform);
// Create a vector data table row from the rectangular subpath, with a default black fill // Create a vector data table row from the rectangular subpath, with a default black fill
let mut instance = VectorData::from_subpath(subpath); let mut element = VectorData::from_subpath(subpath);
instance.style.set_fill(Fill::Solid(Color::BLACK)); element.style.set_fill(Fill::Solid(Color::BLACK));
Instance { instance, ..Default::default() } TableRow { element, ..Default::default() }
}; };
// Apply the parent group's transform to each element of raster data // Apply the parent group's transform to each element of raster data
image.instance_ref_iter().map(|instance| make_instance(*element.transform * *instance.transform)).collect::<Vec<_>>() image.iter_ref().map(|row| make_row(*element.transform * *row.transform)).collect::<Vec<_>>()
} }
GraphicElement::GraphicGroup(mut graphic_group) => { Graphic::GraphicGroup(mut graphic_group) => {
// Apply the parent group's transform to each element of inner group // Apply the parent group's transform to each element of inner group
for sub_element in graphic_group.instance_mut_iter() { for sub_element in graphic_group.iter_mut() {
*sub_element.transform = *element.transform * *sub_element.transform; *sub_element.transform = *element.transform * *sub_element.transform;
} }
// Recursively flatten the inner group into vector data // Recursively flatten the inner group into vector data
let unioned = boolean_operation_on_vector_data_table(flatten_vector_data(&graphic_group).instance_ref_iter(), BooleanOperation::Union); let unioned = boolean_operation_on_vector_data_table(flatten_vector_data(&graphic_group).iter_ref(), BooleanOperation::Union);
unioned.instance_iter().collect::<Vec<_>>() unioned.iter().collect::<Vec<_>>()
} }
} }
}) })
+2 -2
View File
@@ -38,7 +38,7 @@ tokio = { workspace = true, optional = true }
serde_json = { workspace = true, optional = true } serde_json = { workspace = true, optional = true }
# Workspace dependencies # Workspace dependencies
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_family = "wasm")'.dependencies]
web-sys = { workspace = true, features = [ web-sys = { workspace = true, features = [
"Navigator", "Navigator",
"Gpu", "Gpu",
@@ -46,7 +46,7 @@ web-sys = { workspace = true, features = [
js-sys = { workspace = true } js-sys = { workspace = true }
wasm-bindgen = { workspace = true } wasm-bindgen = { workspace = true }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_family = "wasm"))'.dependencies]
winit = { workspace = true } winit = { workspace = true }
[dev-dependencies] [dev-dependencies]
+3 -3
View File
@@ -42,7 +42,7 @@ pub struct DocumentNode {
/// In the root network, it is resolved when evaluating the borrow tree. /// In the root network, it is resolved when evaluating the borrow tree.
/// Ensure the click target in the encapsulating network is updated when the inputs cause the node shape to change (currently only when exposing/hiding an input) /// Ensure the click target in the encapsulating network is updated when the inputs cause the node shape to change (currently only when exposing/hiding an input)
/// by using network.update_click_target(node_id). /// by using network.update_click_target(node_id).
#[cfg_attr(target_arch = "wasm32", serde(alias = "outputs"))] #[cfg_attr(target_family = "wasm", serde(alias = "outputs"))]
pub inputs: Vec<NodeInput>, pub inputs: Vec<NodeInput>,
/// Manual composition is the methodology by which most nodes are implemented, involving a call argument and upstream inputs. /// Manual composition is the methodology by which most nodes are implemented, involving a call argument and upstream inputs.
/// By contrast, automatic composition is an alternative way to handle the composition of nodes as they execute in the graph. /// By contrast, automatic composition is an alternative way to handle the composition of nodes as they execute in the graph.
@@ -552,7 +552,7 @@ pub struct OldDocumentNode {
/// ///
/// In the root network, it is resolved when evaluating the borrow tree. /// In the root network, it is resolved when evaluating the borrow tree.
/// Ensure the click target in the encapsulating network is updated when the inputs cause the node shape to change (currently only when exposing/hiding an input) by using network.update_click_target(node_id). /// Ensure the click target in the encapsulating network is updated when the inputs cause the node shape to change (currently only when exposing/hiding an input) by using network.update_click_target(node_id).
#[cfg_attr(target_arch = "wasm32", serde(alias = "outputs"))] #[cfg_attr(target_family = "wasm", serde(alias = "outputs"))]
pub inputs: Vec<NodeInput>, pub inputs: Vec<NodeInput>,
pub manual_composition: Option<Type>, pub manual_composition: Option<Type>,
// TODO: Remove once this references its definition instead (see above TODO). // TODO: Remove once this references its definition instead (see above TODO).
@@ -657,7 +657,7 @@ pub struct NodeNetwork {
/// The list of data outputs that are exported from this network to the parent network. /// The list of data outputs that are exported from this network to the parent network.
/// Each export is a reference to a node within this network, paired with its output index, that is the source of the network's exported data. /// Each export is a reference to a node within this network, paired with its output index, that is the source of the network's exported data.
// TODO: Eventually remove this alias document upgrade code // TODO: Eventually remove this alias document upgrade code
#[cfg_attr(target_arch = "wasm32", serde(alias = "outputs", deserialize_with = "deserialize_exports"))] #[cfg_attr(target_family = "wasm", serde(alias = "outputs", deserialize_with = "deserialize_exports"))]
pub exports: Vec<NodeInput>, pub exports: Vec<NodeInput>,
// TODO: Instead of storing import types in each NodeInput::Network connection, the types are stored here. This is similar to how types need to be defined for parameters when creating a function in Rust. // TODO: Instead of storing import types in each NodeInput::Network connection, the types are stored here. This is similar to how types need to be defined for parameters when creating a function in Rust.
// pub import_types: Vec<Type>, // pub import_types: Vec<Type>,
+27 -32
View File
@@ -8,11 +8,13 @@ use graphene_application_io::{ImageTexture, SurfaceFrame};
use graphene_brush::brush_cache::BrushCache; use graphene_brush::brush_cache::BrushCache;
use graphene_brush::brush_stroke::BrushStroke; use graphene_brush::brush_stroke::BrushStroke;
use graphene_core::raster::Image; use graphene_core::raster::Image;
use graphene_core::raster_types::CPU; use graphene_core::raster_types::{CPU, Raster};
use graphene_core::table::Table;
use graphene_core::transform::ReferencePoint; use graphene_core::transform::ReferencePoint;
use graphene_core::uuid::NodeId; use graphene_core::uuid::NodeId;
use graphene_core::vector::VectorData;
use graphene_core::vector::style::Fill; use graphene_core::vector::style::Fill;
use graphene_core::{Color, MemoHash, Node, Type}; use graphene_core::{Artboard, Color, Graphic, MemoHash, Node, Type};
use graphene_svg_renderer::RenderMetadata; use graphene_svg_renderer::RenderMetadata;
use std::fmt::Display; use std::fmt::Display;
use std::hash::Hash; use std::hash::Hash;
@@ -166,44 +168,38 @@ tagged_value! {
U64(u64), U64(u64),
Bool(bool), Bool(bool),
String(String), String(String),
#[serde(alias = "IVec2", alias = "UVec2")]
DVec2(DVec2),
DAffine2(DAffine2),
OptionalF64(Option<f64>), OptionalF64(Option<f64>),
OptionalDVec2(Option<DVec2>), // ========================
// ========================== // LISTS OF PRIMITIVE TYPES
// PRIMITIVE COLLECTION TYPES // ========================
// ==========================
#[serde(alias = "VecF32")] // TODO: Eventually remove this alias document upgrade code #[serde(alias = "VecF32")] // TODO: Eventually remove this alias document upgrade code
VecF64(Vec<f64>), VecF64(Vec<f64>),
VecU64(Vec<u64>),
VecDVec2(Vec<DVec2>), VecDVec2(Vec<DVec2>),
F64Array4([f64; 4]), F64Array4([f64; 4]),
NodePath(Vec<NodeId>), NodePath(Vec<NodeId>),
#[serde(alias = "ManipulatorGroupIds")] // TODO: Eventually remove this alias document upgrade code // ===========
PointIds(Vec<graphene_core::vector::PointId>), // TABLE TYPES
// ==================== // ===========
// GRAPHICAL DATA TYPES #[serde(alias = "GraphicElement")]
// ==================== Graphic(Graphic),
GraphicElement(graphene_core::GraphicElement), #[cfg_attr(target_family = "wasm", serde(deserialize_with = "graphene_core::vector::migrate_vector_data"))] // TODO: Eventually remove this migration document upgrade code
#[cfg_attr(target_arch = "wasm32", serde(deserialize_with = "graphene_core::vector::migrate_vector_data"))] // TODO: Eventually remove this migration document upgrade code VectorData(Table<VectorData>),
VectorData(graphene_core::vector::VectorDataTable), #[cfg_attr(target_family = "wasm", serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame"))] // TODO: Eventually remove this migration document upgrade code
#[cfg_attr(target_arch = "wasm32", serde(alias = "ImageFrame", deserialize_with = "graphene_core::raster::image::migrate_image_frame"))] // TODO: Eventually remove this migration document upgrade code #[serde(alias = "ImageFrame")]
RasterData(graphene_core::raster_types::RasterDataTable<CPU>), RasterData(Table<Raster<CPU>>),
#[cfg_attr(target_arch = "wasm32", serde(deserialize_with = "graphene_core::graphic_element::migrate_graphic_group"))] // TODO: Eventually remove this migration document upgrade code #[cfg_attr(target_family = "wasm", serde(deserialize_with = "graphene_core::graphic_element::migrate_graphic_group"))] // TODO: Eventually remove this migration document upgrade code
GraphicGroup(graphene_core::GraphicGroupTable), GraphicGroup(Table<Graphic>),
#[cfg_attr(target_arch = "wasm32", serde(deserialize_with = "graphene_core::graphic_element::migrate_artboard_group"))] // TODO: Eventually remove this migration document upgrade code #[cfg_attr(target_family = "wasm", serde(deserialize_with = "graphene_core::artboard::migrate_artboard_group"))] // TODO: Eventually remove this migration document upgrade code
ArtboardGroup(graphene_core::ArtboardGroupTable), ArtboardGroup(Table<Artboard>),
// ============ // ============
// STRUCT TYPES // STRUCT TYPES
// ============ // ============
Artboard(graphene_core::Artboard), #[serde(alias = "IVec2", alias = "UVec2")]
Image(graphene_core::raster::Image<Color>), DVec2(DVec2),
Color(graphene_core::raster::color::Color), DAffine2(DAffine2),
OptionalColor(Option<graphene_core::raster::color::Color>), Color(Color),
OptionalColor(Option<Color>),
Palette(Vec<Color>), Palette(Vec<Color>),
Subpaths(Vec<bezier_rs::Subpath<graphene_core::vector::PointId>>),
Fill(graphene_core::vector::style::Fill),
Stroke(graphene_core::vector::style::Stroke), Stroke(graphene_core::vector::style::Stroke),
Gradient(graphene_core::vector::style::Gradient), Gradient(graphene_core::vector::style::Gradient),
#[serde(alias = "GradientPositions")] // TODO: Eventually remove this alias document upgrade code #[serde(alias = "GradientPositions")] // TODO: Eventually remove this alias document upgrade code
@@ -215,10 +211,10 @@ tagged_value! {
Curve(graphene_raster_nodes::curve::Curve), Curve(graphene_raster_nodes::curve::Curve),
Footprint(graphene_core::transform::Footprint), Footprint(graphene_core::transform::Footprint),
VectorModification(Box<graphene_core::vector::VectorModification>), VectorModification(Box<graphene_core::vector::VectorModification>),
FontCache(Arc<graphene_core::text::FontCache>),
// ========== // ==========
// ENUM TYPES // ENUM TYPES
// ========== // ==========
Fill(graphene_core::vector::style::Fill),
BlendMode(graphene_core::blending::BlendMode), BlendMode(graphene_core::blending::BlendMode),
LuminanceCalculation(graphene_raster_nodes::adjustments::LuminanceCalculation), LuminanceCalculation(graphene_raster_nodes::adjustments::LuminanceCalculation),
XY(graphene_core::extract_xy::XY), XY(graphene_core::extract_xy::XY),
@@ -243,7 +239,6 @@ tagged_value! {
StrokeAlign(graphene_core::vector::style::StrokeAlign), StrokeAlign(graphene_core::vector::style::StrokeAlign),
PaintOrder(graphene_core::vector::style::PaintOrder), PaintOrder(graphene_core::vector::style::PaintOrder),
FillType(graphene_core::vector::style::FillType), FillType(graphene_core::vector::style::FillType),
FillChoice(graphene_core::vector::style::FillChoice),
GradientType(graphene_core::vector::style::GradientType), GradientType(graphene_core::vector::style::GradientType),
ReferencePoint(graphene_core::transform::ReferencePoint), ReferencePoint(graphene_core::transform::ReferencePoint),
CentroidType(graphene_core::vector::misc::CentroidType), CentroidType(graphene_core::vector::misc::CentroidType),
@@ -1,35 +1,35 @@
use dyn_any::StaticType; use dyn_any::StaticType;
use graphene_application_io::{ApplicationError, ApplicationIo, ResourceFuture, SurfaceHandle, SurfaceId}; use graphene_application_io::{ApplicationError, ApplicationIo, ResourceFuture, SurfaceHandle, SurfaceId};
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
use js_sys::{Object, Reflect}; use js_sys::{Object, Reflect};
use std::collections::HashMap; use std::collections::HashMap;
use std::hash::Hash; use std::hash::Hash;
use std::sync::Arc; use std::sync::Arc;
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
use std::sync::atomic::AtomicU64; use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
#[cfg(feature = "tokio")] #[cfg(feature = "tokio")]
use tokio::io::AsyncReadExt; use tokio::io::AsyncReadExt;
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
use wasm_bindgen::JsValue; use wasm_bindgen::JsValue;
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
use web_sys::HtmlCanvasElement; use web_sys::HtmlCanvasElement;
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
use web_sys::window; use web_sys::window;
#[cfg(feature = "wgpu")] #[cfg(feature = "wgpu")]
use wgpu_executor::WgpuExecutor; use wgpu_executor::WgpuExecutor;
#[derive(Debug)] #[derive(Debug)]
struct WindowWrapper { struct WindowWrapper {
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
window: SurfaceHandle<HtmlCanvasElement>, window: SurfaceHandle<HtmlCanvasElement>,
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
window: SurfaceHandle<Arc<winit::window::Window>>, window: SurfaceHandle<Arc<winit::window::Window>>,
} }
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
impl Drop for WindowWrapper { impl Drop for WindowWrapper {
fn drop(&mut self) { fn drop(&mut self) {
let window = window().expect("should have a window in this context"); let window = window().expect("should have a window in this context");
@@ -52,14 +52,14 @@ impl Drop for WindowWrapper {
} }
} }
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
unsafe impl Sync for WindowWrapper {} unsafe impl Sync for WindowWrapper {}
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
unsafe impl Send for WindowWrapper {} unsafe impl Send for WindowWrapper {}
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct WasmApplicationIo { pub struct WasmApplicationIo {
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
ids: AtomicU64, ids: AtomicU64,
#[cfg(feature = "wgpu")] #[cfg(feature = "wgpu")]
pub(crate) gpu_executor: Option<WgpuExecutor>, pub(crate) gpu_executor: Option<WgpuExecutor>,
@@ -79,7 +79,7 @@ pub fn wgpu_available() -> Option<bool> {
impl WasmApplicationIo { impl WasmApplicationIo {
pub async fn new() -> Self { pub async fn new() -> Self {
#[cfg(all(feature = "wgpu", target_arch = "wasm32"))] #[cfg(all(feature = "wgpu", target_family = "wasm"))]
let executor = if let Some(gpu) = web_sys::window().map(|w| w.navigator().gpu()) { let executor = if let Some(gpu) = web_sys::window().map(|w| w.navigator().gpu()) {
let request_adapter = || { let request_adapter = || {
let request_adapter = js_sys::Reflect::get(&gpu, &wasm_bindgen::JsValue::from_str("requestAdapter")).ok()?; let request_adapter = js_sys::Reflect::get(&gpu, &wasm_bindgen::JsValue::from_str("requestAdapter")).ok()?;
@@ -95,7 +95,7 @@ impl WasmApplicationIo {
None None
}; };
#[cfg(all(feature = "wgpu", not(target_arch = "wasm32")))] #[cfg(all(feature = "wgpu", not(target_family = "wasm")))]
let executor = WgpuExecutor::new().await; let executor = WgpuExecutor::new().await;
#[cfg(not(feature = "wgpu"))] #[cfg(not(feature = "wgpu"))]
@@ -105,7 +105,7 @@ impl WasmApplicationIo {
WGPU_AVAILABLE.store(wgpu_available as i8, Ordering::SeqCst); WGPU_AVAILABLE.store(wgpu_available as i8, Ordering::SeqCst);
let mut io = Self { let mut io = Self {
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
ids: AtomicU64::new(0), ids: AtomicU64::new(0),
#[cfg(feature = "wgpu")] #[cfg(feature = "wgpu")]
gpu_executor: executor, gpu_executor: executor,
@@ -130,7 +130,7 @@ impl WasmApplicationIo {
WGPU_AVAILABLE.store(wgpu_available as i8, Ordering::SeqCst); WGPU_AVAILABLE.store(wgpu_available as i8, Ordering::SeqCst);
let mut io = Self { let mut io = Self {
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
ids: AtomicU64::new(0), ids: AtomicU64::new(0),
#[cfg(feature = "wgpu")] #[cfg(feature = "wgpu")]
gpu_executor: executor, gpu_executor: executor,
@@ -142,7 +142,7 @@ impl WasmApplicationIo {
io io
} }
#[cfg(all(not(target_arch = "wasm32"), feature = "wgpu"))] #[cfg(all(not(target_family = "wasm"), feature = "wgpu"))]
pub fn new_with_context(context: wgpu_executor::Context) -> Self { pub fn new_with_context(context: wgpu_executor::Context) -> Self {
#[cfg(feature = "wgpu")] #[cfg(feature = "wgpu")]
let executor = WgpuExecutor::with_context(context); let executor = WgpuExecutor::with_context(context);
@@ -184,16 +184,16 @@ impl<'a> From<&'a WasmApplicationIo> for &'a WgpuExecutor {
pub type WasmEditorApi = graphene_application_io::EditorApi<WasmApplicationIo>; pub type WasmEditorApi = graphene_application_io::EditorApi<WasmApplicationIo>;
impl ApplicationIo for WasmApplicationIo { impl ApplicationIo for WasmApplicationIo {
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
type Surface = HtmlCanvasElement; type Surface = HtmlCanvasElement;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
type Surface = Arc<winit::window::Window>; type Surface = Arc<winit::window::Window>;
#[cfg(feature = "wgpu")] #[cfg(feature = "wgpu")]
type Executor = WgpuExecutor; type Executor = WgpuExecutor;
#[cfg(not(feature = "wgpu"))] #[cfg(not(feature = "wgpu"))]
type Executor = (); type Executor = ();
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
fn create_window(&self) -> SurfaceHandle<Self::Surface> { fn create_window(&self) -> SurfaceHandle<Self::Surface> {
let wrapper = || { let wrapper = || {
let document = window().expect("should have a window in this context").document().expect("window should have a document"); let document = window().expect("should have a window in this context").document().expect("window should have a document");
@@ -228,7 +228,7 @@ impl ApplicationIo for WasmApplicationIo {
wrapper().expect("should be able to set canvas in global scope") wrapper().expect("should be able to set canvas in global scope")
} }
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
fn create_window(&self) -> SurfaceHandle<Self::Surface> { fn create_window(&self) -> SurfaceHandle<Self::Surface> {
todo!("winit api changed, calling create_window on EventLoop is deprecated"); todo!("winit api changed, calling create_window on EventLoop is deprecated");
@@ -256,7 +256,7 @@ impl ApplicationIo for WasmApplicationIo {
// } // }
} }
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
fn destroy_window(&self, surface_id: SurfaceId) { fn destroy_window(&self, surface_id: SurfaceId) {
let window = window().expect("should have a window in this context"); let window = window().expect("should have a window in this context");
let window = Object::from(window); let window = Object::from(window);
@@ -277,7 +277,7 @@ impl ApplicationIo for WasmApplicationIo {
wrapper().expect("should be able to set canvas in global scope") wrapper().expect("should be able to set canvas in global scope")
} }
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
fn destroy_window(&self, _surface_id: SurfaceId) {} fn destroy_window(&self, _surface_id: SurfaceId) {}
#[cfg(feature = "wgpu")] #[cfg(feature = "wgpu")]
@@ -346,9 +346,9 @@ impl graphene_application_io::GetEditorPreferences for EditorPreferences {
impl Default for EditorPreferences { impl Default for EditorPreferences {
fn default() -> Self { fn default() -> Self {
Self { Self {
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
use_vello: false, use_vello: false,
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
use_vello: true, use_vello: true,
} }
} }
+11 -9
View File
@@ -10,8 +10,8 @@ impl Adjust<Color> for Color {
} }
impl Adjust<Color> for Option<Color> { impl Adjust<Color> for Option<Color> {
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) { fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
if let Some(v) = self { if let Some(color) = self {
*v = map_fn(v) *color = map_fn(color)
} }
} }
} }
@@ -20,19 +20,21 @@ impl Adjust<Color> for Option<Color> {
mod adjust_std { mod adjust_std {
use super::*; use super::*;
use graphene_core::gradient::GradientStops; use graphene_core::gradient::GradientStops;
use graphene_core::raster_types::{CPU, RasterDataTable}; use graphene_core::raster_types::{CPU, Raster};
use graphene_core::table::Table;
impl Adjust<Color> for GradientStops { impl Adjust<Color> for GradientStops {
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) { fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
for (_pos, c) in self.iter_mut() { for (_, color) in self.iter_mut() {
*c = map_fn(c); *color = map_fn(color);
} }
} }
} }
impl Adjust<Color> for RasterDataTable<CPU> { impl Adjust<Color> for Table<Raster<CPU>> {
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) { fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
for instance in self.instance_mut_iter() { for row in self.iter_mut() {
for c in instance.instance.data_mut().data.iter_mut() { for color in row.element.data_mut().data.iter_mut() {
*c = map_fn(c); *color = map_fn(color);
} }
} }
} }
+17 -16
View File
@@ -6,7 +6,8 @@ use core::fmt::Debug;
#[cfg(feature = "std")] #[cfg(feature = "std")]
use graphene_core::gradient::GradientStops; use graphene_core::gradient::GradientStops;
#[cfg(feature = "std")] #[cfg(feature = "std")]
use graphene_core::raster_types::{CPU, RasterDataTable}; use graphene_core::raster_types::{CPU, Raster};
use graphene_core::table::Table;
use graphene_core_shaders::color::Color; use graphene_core_shaders::color::Color;
use graphene_core_shaders::context::Ctx; use graphene_core_shaders::context::Ctx;
use graphene_core_shaders::registry::types::{Angle, Percentage, SignedPercentage}; use graphene_core_shaders::registry::types::{Angle, Percentage, SignedPercentage};
@@ -44,7 +45,7 @@ fn luminance<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Color, Color,
RasterDataTable<CPU>, Table<Raster<CPU>>,
GradientStops, GradientStops,
)] )]
mut input: T, mut input: T,
@@ -68,7 +69,7 @@ fn gamma_correction<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Color, Color,
RasterDataTable<CPU>, Table<Raster<CPU>>,
GradientStops, GradientStops,
)] )]
mut input: T, mut input: T,
@@ -88,7 +89,7 @@ fn extract_channel<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Color, Color,
RasterDataTable<CPU>, Table<Raster<CPU>>,
GradientStops, GradientStops,
)] )]
mut input: T, mut input: T,
@@ -111,7 +112,7 @@ fn make_opaque<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Color, Color,
RasterDataTable<CPU>, Table<Raster<CPU>>,
GradientStops, GradientStops,
)] )]
mut input: T, mut input: T,
@@ -136,7 +137,7 @@ fn brightness_contrast<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Color, Color,
RasterDataTable<CPU>, Table<Raster<CPU>>,
GradientStops, GradientStops,
)] )]
mut input: T, mut input: T,
@@ -225,7 +226,7 @@ fn levels<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Color, Color,
RasterDataTable<CPU>, Table<Raster<CPU>>,
GradientStops, GradientStops,
)] )]
mut image: T, mut image: T,
@@ -292,7 +293,7 @@ async fn black_and_white<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Color, Color,
RasterDataTable<CPU>, Table<Raster<CPU>>,
GradientStops, GradientStops,
)] )]
mut image: T, mut image: T,
@@ -364,7 +365,7 @@ async fn hue_saturation<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Color, Color,
RasterDataTable<CPU>, Table<Raster<CPU>>,
GradientStops, GradientStops,
)] )]
mut input: T, mut input: T,
@@ -398,7 +399,7 @@ async fn invert<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Color, Color,
RasterDataTable<CPU>, Table<Raster<CPU>>,
GradientStops, GradientStops,
)] )]
mut input: T, mut input: T,
@@ -420,7 +421,7 @@ async fn threshold<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Color, Color,
RasterDataTable<CPU>, Table<Raster<CPU>>,
GradientStops, GradientStops,
)] )]
mut image: T, mut image: T,
@@ -465,7 +466,7 @@ async fn vibrance<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Color, Color,
RasterDataTable<CPU>, Table<Raster<CPU>>,
GradientStops, GradientStops,
)] )]
mut image: T, mut image: T,
@@ -630,7 +631,7 @@ async fn channel_mixer<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Color, Color,
RasterDataTable<CPU>, Table<Raster<CPU>>,
GradientStops, GradientStops,
)] )]
mut image: T, mut image: T,
@@ -758,7 +759,7 @@ async fn selective_color<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Color, Color,
RasterDataTable<CPU>, Table<Raster<CPU>>,
GradientStops, GradientStops,
)] )]
mut image: T, mut image: T,
@@ -900,7 +901,7 @@ async fn posterize<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Color, Color,
RasterDataTable<CPU>, Table<Raster<CPU>>,
GradientStops, GradientStops,
)] )]
mut input: T, mut input: T,
@@ -933,7 +934,7 @@ async fn exposure<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Color, Color,
RasterDataTable<CPU>, Table<Raster<CPU>>,
GradientStops, GradientStops,
)] )]
mut input: T, mut input: T,
+17 -13
View File
@@ -2,7 +2,8 @@ use crate::adjust::Adjust;
#[cfg(feature = "std")] #[cfg(feature = "std")]
use graphene_core::gradient::GradientStops; use graphene_core::gradient::GradientStops;
#[cfg(feature = "std")] #[cfg(feature = "std")]
use graphene_core::raster_types::{CPU, RasterDataTable}; use graphene_core::raster_types::{CPU, Raster};
use graphene_core::table::Table;
use graphene_core_shaders::Ctx; use graphene_core_shaders::Ctx;
use graphene_core_shaders::blending::BlendMode; use graphene_core_shaders::blending::BlendMode;
use graphene_core_shaders::color::{Color, Pixel}; use graphene_core_shaders::color::{Color, Pixel};
@@ -32,16 +33,18 @@ mod blend_std {
use core::cmp::Ordering; use core::cmp::Ordering;
use graphene_core::raster::Image; use graphene_core::raster::Image;
use graphene_core::raster_types::Raster; use graphene_core::raster_types::Raster;
impl Blend<Color> for RasterDataTable<CPU> { use graphene_core::table::Table;
impl Blend<Color> for Table<Raster<CPU>> {
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self { fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
let mut result_table = self.clone(); let mut result_table = self.clone();
for (over, under) in result_table.instance_mut_iter().zip(under.instance_ref_iter()) { for (over, under) in result_table.iter_mut().zip(under.iter_ref()) {
let data = over.instance.data.iter().zip(under.instance.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect(); let data = over.element.data.iter().zip(under.element.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect();
*over.instance = Raster::new_cpu(Image { *over.element = Raster::new_cpu(Image {
data, data,
width: over.instance.width, width: over.element.width,
height: over.instance.height, height: over.element.height,
base64_string: None, base64_string: None,
}); });
} }
@@ -124,14 +127,14 @@ async fn blend<T: Blend<Color> + Send>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Color, Color,
RasterDataTable<CPU>, Table<Raster<CPU>>,
GradientStops, GradientStops,
)] )]
over: T, over: T,
#[expose] #[expose]
#[implementations( #[implementations(
Color, Color,
RasterDataTable<CPU>, Table<Raster<CPU>>,
GradientStops, GradientStops,
)] )]
under: T, under: T,
@@ -146,7 +149,7 @@ fn color_overlay<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Color, Color,
RasterDataTable<CPU>, Table<Raster<CPU>>,
GradientStops, GradientStops,
)] )]
mut image: T, mut image: T,
@@ -185,7 +188,8 @@ mod test {
use graphene_core::blending::BlendMode; use graphene_core::blending::BlendMode;
use graphene_core::color::Color; use graphene_core::color::Color;
use graphene_core::raster::image::Image; use graphene_core::raster::image::Image;
use graphene_core::raster_types::{Raster, RasterDataTable}; use graphene_core::raster_types::Raster;
use graphene_core::table::Table;
#[tokio::test] #[tokio::test]
async fn color_overlay_multiply() { async fn color_overlay_multiply() {
@@ -198,8 +202,8 @@ mod test {
// 100% of the output should come from the multiplied value // 100% of the output should come from the multiplied value
let opacity = 100_f64; let opacity = 100_f64;
let result = super::color_overlay((), RasterDataTable::new(Raster::new_cpu(image.clone())), overlay_color, BlendMode::Multiply, opacity); let result = super::color_overlay((), Table::new_from_element(Raster::new_cpu(image.clone())), overlay_color, BlendMode::Multiply, opacity);
let result = result.instance_ref_iter().next().unwrap().instance; let result = result.iter_ref().next().unwrap().element;
// The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0) // The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0)
assert_eq!(result.data[0], Color::from_rgbaf32_unchecked(0., image_color.g(), 0., image_color.a())); assert_eq!(result.data[0], Color::from_rgbaf32_unchecked(0., image_color.g(), 0., image_color.a()));
+8 -7
View File
@@ -1,17 +1,18 @@
use graphene_core::context::Ctx; use graphene_core::context::Ctx;
use graphene_core::raster::image::Image; use graphene_core::raster::image::Image;
use graphene_core::raster_types::{CPU, Raster, RasterDataTable}; use graphene_core::raster_types::{CPU, Raster};
use graphene_core::registry::types::Percentage; use graphene_core::registry::types::Percentage;
use graphene_core::table::Table;
use image::{DynamicImage, GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma, Rgba, RgbaImage}; use image::{DynamicImage, GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma, Rgba, RgbaImage};
use ndarray::{Array2, ArrayBase, Dim, OwnedRepr}; use ndarray::{Array2, ArrayBase, Dim, OwnedRepr};
use std::cmp::{max, min}; use std::cmp::{max, min};
#[node_macro::node(category("Raster: Filter"))] #[node_macro::node(category("Raster: Filter"))]
async fn dehaze(_: impl Ctx, image_frame: RasterDataTable<CPU>, strength: Percentage) -> RasterDataTable<CPU> { async fn dehaze(_: impl Ctx, image_frame: Table<Raster<CPU>>, strength: Percentage) -> Table<Raster<CPU>> {
image_frame image_frame
.instance_iter() .iter()
.map(|mut image_frame_instance| { .map(|mut row| {
let image = image_frame_instance.instance; let image = row.element;
// Prepare the image data for processing // Prepare the image data for processing
let image_data = bytemuck::cast_vec(image.data.clone()); let image_data = bytemuck::cast_vec(image.data.clone());
let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, image_data).expect("Failed to convert internal image format into image-rs data type."); let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, image_data).expect("Failed to convert internal image format into image-rs data type.");
@@ -30,8 +31,8 @@ async fn dehaze(_: impl Ctx, image_frame: RasterDataTable<CPU>, strength: Percen
base64_string: None, base64_string: None,
}; };
image_frame_instance.instance = Raster::new_cpu(dehazed_image); row.element = Raster::new_cpu(dehazed_image);
image_frame_instance row
}) })
.collect() .collect()
} }
+9 -8
View File
@@ -2,15 +2,16 @@ use graphene_core::color::Color;
use graphene_core::context::Ctx; use graphene_core::context::Ctx;
use graphene_core::raster::image::Image; use graphene_core::raster::image::Image;
use graphene_core::raster::{Bitmap, BitmapMut}; use graphene_core::raster::{Bitmap, BitmapMut};
use graphene_core::raster_types::{CPU, Raster, RasterDataTable}; use graphene_core::raster_types::{CPU, Raster};
use graphene_core::registry::types::PixelLength; use graphene_core::registry::types::PixelLength;
use graphene_core::table::Table;
/// Blurs the image with a Gaussian or blur kernel filter. /// Blurs the image with a Gaussian or blur kernel filter.
#[node_macro::node(category("Raster: Filter"))] #[node_macro::node(category("Raster: Filter"))]
async fn blur( async fn blur(
_: impl Ctx, _: impl Ctx,
/// The image to be blurred. /// The image to be blurred.
image_frame: RasterDataTable<CPU>, image_frame: Table<Raster<CPU>>,
/// The radius of the blur kernel. /// The radius of the blur kernel.
#[range((0., 100.))] #[range((0., 100.))]
#[hard_min(0.)] #[hard_min(0.)]
@@ -19,11 +20,11 @@ async fn blur(
box_blur: bool, box_blur: bool,
/// Opt to incorrectly apply the filter with color calculations in gamma space for compatibility with the results from other software. /// Opt to incorrectly apply the filter with color calculations in gamma space for compatibility with the results from other software.
gamma: bool, gamma: bool,
) -> RasterDataTable<CPU> { ) -> Table<Raster<CPU>> {
image_frame image_frame
.instance_iter() .iter()
.map(|mut image_instance| { .map(|mut row| {
let image = image_instance.instance.clone(); let image = row.element.clone();
// Run blur algorithm // Run blur algorithm
let blurred_image = if radius < 0.1 { let blurred_image = if radius < 0.1 {
@@ -35,8 +36,8 @@ async fn blur(
Raster::new_cpu(gaussian_blur_algorithm(image.into_data(), radius, gamma)) Raster::new_cpu(gaussian_blur_algorithm(image.into_data(), radius, gamma))
}; };
image_instance.instance = blurred_image; row.element = blurred_image;
image_instance row
}) })
.collect() .collect()
} }
+3 -2
View File
@@ -2,7 +2,8 @@
use crate::adjust::Adjust; use crate::adjust::Adjust;
use graphene_core::gradient::GradientStops; use graphene_core::gradient::GradientStops;
use graphene_core::raster_types::{CPU, RasterDataTable}; use graphene_core::raster_types::{CPU, Raster};
use graphene_core::table::Table;
use graphene_core::{Color, Ctx}; use graphene_core::{Color, Ctx};
// Aims for interoperable compatibility with: // Aims for interoperable compatibility with:
@@ -13,7 +14,7 @@ async fn gradient_map<T: Adjust<Color>>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
Color, Color,
RasterDataTable<CPU>, Table<Raster<CPU>>,
GradientStops, GradientStops,
)] )]
mut image: T, mut image: T,
@@ -1,11 +1,12 @@
use graphene_core::color::Color; use graphene_core::color::Color;
use graphene_core::context::Ctx; use graphene_core::context::Ctx;
use graphene_core::raster_types::{CPU, RasterDataTable}; use graphene_core::raster_types::{CPU, Raster};
use graphene_core::table::Table;
#[node_macro::node(category("Color"))] #[node_macro::node(category("Color"))]
async fn image_color_palette( async fn image_color_palette(
_: impl Ctx, _: impl Ctx,
image: RasterDataTable<CPU>, image: Table<Raster<CPU>>,
#[hard_min(1.)] #[hard_min(1.)]
#[soft_max(28.)] #[soft_max(28.)]
max_size: u32, max_size: u32,
@@ -17,8 +18,8 @@ async fn image_color_palette(
let mut histogram: Vec<usize> = vec![0; (bins + 1.) as usize]; let mut histogram: Vec<usize> = vec![0; (bins + 1.) as usize];
let mut colors: Vec<Vec<Color>> = vec![vec![]; (bins + 1.) as usize]; let mut colors: Vec<Vec<Color>> = vec![vec![]; (bins + 1.) as usize];
for image_instance in image.instance_ref_iter() { for row in image.iter_ref() {
for pixel in image_instance.instance.data.iter() { for pixel in row.element.data.iter() {
let r = pixel.r() * GRID; let r = pixel.r() * GRID;
let g = pixel.g() * GRID; let g = pixel.g() * GRID;
let b = pixel.b() * GRID; let b = pixel.b() * GRID;
@@ -66,13 +67,13 @@ async fn image_color_palette(
mod test { mod test {
use super::*; use super::*;
use graphene_core::raster::image::Image; use graphene_core::raster::image::Image;
use graphene_core::raster_types::{Raster, RasterDataTable}; use graphene_core::raster_types::Raster;
#[test] #[test]
fn test_image_color_palette() { fn test_image_color_palette() {
let result = image_color_palette( let result = image_color_palette(
(), (),
RasterDataTable::new(Raster::new_cpu(Image { Table::new_from_element(Raster::new_cpu(Image {
width: 100, width: 100,
height: 100, height: 100,
data: vec![Color::from_rgbaf32(0., 0., 0., 1.).unwrap(); 10000], data: vec![Color::from_rgbaf32(0., 0., 0., 1.).unwrap(); 10000],
+84 -84
View File
@@ -6,11 +6,11 @@ use graphene_core::blending::AlphaBlending;
use graphene_core::color::Color; use graphene_core::color::Color;
use graphene_core::color::{Alpha, AlphaMut, Channel, LinearChannel, Luminance, RGBMut}; use graphene_core::color::{Alpha, AlphaMut, Channel, LinearChannel, Luminance, RGBMut};
use graphene_core::context::{Ctx, ExtractFootprint}; use graphene_core::context::{Ctx, ExtractFootprint};
use graphene_core::instances::Instance;
use graphene_core::math::bbox::Bbox; use graphene_core::math::bbox::Bbox;
use graphene_core::raster::image::Image; use graphene_core::raster::image::Image;
use graphene_core::raster::{Bitmap, BitmapMut}; use graphene_core::raster::{Bitmap, BitmapMut};
use graphene_core::raster_types::{CPU, Raster, RasterDataTable}; use graphene_core::raster_types::{CPU, Raster};
use graphene_core::table::{Table, TableRow};
use graphene_core::transform::Transform; use graphene_core::transform::Transform;
use rand::prelude::*; use rand::prelude::*;
use rand_chacha::ChaCha8Rng; use rand_chacha::ChaCha8Rng;
@@ -30,12 +30,12 @@ impl From<std::io::Error> for Error {
} }
#[node_macro::node(category("Debug: Raster"))] #[node_macro::node(category("Debug: Raster"))]
pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: RasterDataTable<CPU>) -> RasterDataTable<CPU> { pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: Table<Raster<CPU>>) -> Table<Raster<CPU>> {
image_frame image_frame
.instance_iter() .iter()
.filter_map(|mut image_frame_instance| { .filter_map(|mut row| {
let image_frame_transform = image_frame_instance.transform; let image_frame_transform = row.transform;
let image = image_frame_instance.instance; let image = row.element;
// Resize the image using the image crate // Resize the image using the image crate
let data = bytemuck::cast_vec(image.data.clone()); let data = bytemuck::cast_vec(image.data.clone());
@@ -87,9 +87,9 @@ pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: Rast
let new_transform = image_frame_transform * DAffine2::from_translation(offset) * DAffine2::from_scale(size); let new_transform = image_frame_transform * DAffine2::from_translation(offset) * DAffine2::from_scale(size);
image_frame_instance.transform = new_transform; row.transform = new_transform;
image_frame_instance.instance = Raster::new_cpu(image); row.element = Raster::new_cpu(image);
Some(image_frame_instance) Some(row)
}) })
.collect() .collect()
} }
@@ -98,28 +98,28 @@ pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: Rast
pub fn combine_channels( pub fn combine_channels(
_: impl Ctx, _: impl Ctx,
_primary: (), _primary: (),
#[expose] red: RasterDataTable<CPU>, #[expose] red: Table<Raster<CPU>>,
#[expose] green: RasterDataTable<CPU>, #[expose] green: Table<Raster<CPU>>,
#[expose] blue: RasterDataTable<CPU>, #[expose] blue: Table<Raster<CPU>>,
#[expose] alpha: RasterDataTable<CPU>, #[expose] alpha: Table<Raster<CPU>>,
) -> RasterDataTable<CPU> { ) -> Table<Raster<CPU>> {
let max_len = red.len().max(green.len()).max(blue.len()).max(alpha.len()); let max_len = red.len().max(green.len()).max(blue.len()).max(alpha.len());
let red = red.instance_iter().map(Some).chain(std::iter::repeat(None)).take(max_len); let red = red.iter().map(Some).chain(std::iter::repeat(None)).take(max_len);
let green = green.instance_iter().map(Some).chain(std::iter::repeat(None)).take(max_len); let green = green.iter().map(Some).chain(std::iter::repeat(None)).take(max_len);
let blue = blue.instance_iter().map(Some).chain(std::iter::repeat(None)).take(max_len); let blue = blue.iter().map(Some).chain(std::iter::repeat(None)).take(max_len);
let alpha = alpha.instance_iter().map(Some).chain(std::iter::repeat(None)).take(max_len); let alpha = alpha.iter().map(Some).chain(std::iter::repeat(None)).take(max_len);
red.zip(green) red.zip(green)
.zip(blue) .zip(blue)
.zip(alpha) .zip(alpha)
.filter_map(|(((red, green), blue), alpha)| { .filter_map(|(((red, green), blue), alpha)| {
// Turn any default zero-sized image instances into None // Turn any default zero-sized image rows into None
let red = red.filter(|i| i.instance.width > 0 && i.instance.height > 0); let red = red.filter(|i| i.element.width > 0 && i.element.height > 0);
let green = green.filter(|i| i.instance.width > 0 && i.instance.height > 0); let green = green.filter(|i| i.element.width > 0 && i.element.height > 0);
let blue = blue.filter(|i| i.instance.width > 0 && i.instance.height > 0); let blue = blue.filter(|i| i.element.width > 0 && i.element.height > 0);
let alpha = alpha.filter(|i| i.instance.width > 0 && i.instance.height > 0); let alpha = alpha.filter(|i| i.element.width > 0 && i.element.height > 0);
// Get this instance's transform and alpha blending mode from the first non-empty channel // Get this row's transform and alpha blending mode from the first non-empty channel
let (transform, alpha_blending, source_node_id) = [&red, &green, &blue, &alpha] let (transform, alpha_blending, source_node_id) = [&red, &green, &blue, &alpha]
.iter() .iter()
.find_map(|i| i.as_ref()) .find_map(|i| i.as_ref())
@@ -127,10 +127,10 @@ pub fn combine_channels(
// Get the common width and height of the channels, which must have equal dimensions // Get the common width and height of the channels, which must have equal dimensions
let channel_dimensions = [ let channel_dimensions = [
red.as_ref().map(|r| (r.instance.width, r.instance.height)), red.as_ref().map(|r| (r.element.width, r.element.height)),
green.as_ref().map(|g| (g.instance.width, g.instance.height)), green.as_ref().map(|g| (g.element.width, g.element.height)),
blue.as_ref().map(|b| (b.instance.width, b.instance.height)), blue.as_ref().map(|b| (b.element.width, b.element.height)),
alpha.as_ref().map(|a| (a.instance.width, a.instance.height)), alpha.as_ref().map(|a| (a.element.width, a.element.height)),
]; ];
if channel_dimensions.iter().all(Option::is_none) if channel_dimensions.iter().all(Option::is_none)
|| channel_dimensions || channel_dimensions
@@ -142,7 +142,7 @@ pub fn combine_channels(
} }
let &(width, height) = channel_dimensions.iter().flatten().next()?; let &(width, height) = channel_dimensions.iter().flatten().next()?;
// Create a new image for this instance output // Create a new image for the output element
let mut image = Image::new(width, height, Color::TRANSPARENT); let mut image = Image::new(width, height, Color::TRANSPARENT);
// Iterate over all pixels in the image and set the color channels // Iterate over all pixels in the image and set the color channels
@@ -150,22 +150,22 @@ pub fn combine_channels(
for x in 0..image.width() { for x in 0..image.width() {
let image_pixel = image.get_pixel_mut(x, y).unwrap(); let image_pixel = image.get_pixel_mut(x, y).unwrap();
if let Some(r) = red.as_ref().and_then(|r| r.instance.get_pixel(x, y)) { if let Some(r) = red.as_ref().and_then(|r| r.element.get_pixel(x, y)) {
image_pixel.set_red(r.l().cast_linear_channel()); image_pixel.set_red(r.l().cast_linear_channel());
} else { } else {
image_pixel.set_red(Channel::from_linear(0.)); image_pixel.set_red(Channel::from_linear(0.));
} }
if let Some(g) = green.as_ref().and_then(|g| g.instance.get_pixel(x, y)) { if let Some(g) = green.as_ref().and_then(|g| g.element.get_pixel(x, y)) {
image_pixel.set_green(g.l().cast_linear_channel()); image_pixel.set_green(g.l().cast_linear_channel());
} else { } else {
image_pixel.set_green(Channel::from_linear(0.)); image_pixel.set_green(Channel::from_linear(0.));
} }
if let Some(b) = blue.as_ref().and_then(|b| b.instance.get_pixel(x, y)) { if let Some(b) = blue.as_ref().and_then(|b| b.element.get_pixel(x, y)) {
image_pixel.set_blue(b.l().cast_linear_channel()); image_pixel.set_blue(b.l().cast_linear_channel());
} else { } else {
image_pixel.set_blue(Channel::from_linear(0.)); image_pixel.set_blue(Channel::from_linear(0.));
} }
if let Some(a) = alpha.as_ref().and_then(|a| a.instance.get_pixel(x, y)) { if let Some(a) = alpha.as_ref().and_then(|a| a.element.get_pixel(x, y)) {
image_pixel.set_alpha(a.l().cast_linear_channel()); image_pixel.set_alpha(a.l().cast_linear_channel());
} else { } else {
image_pixel.set_alpha(Channel::from_linear(1.)); image_pixel.set_alpha(Channel::from_linear(1.));
@@ -173,8 +173,8 @@ pub fn combine_channels(
} }
} }
Some(Instance { Some(TableRow {
instance: Raster::new_cpu(image), element: Raster::new_cpu(image),
transform, transform,
alpha_blending, alpha_blending,
source_node_id, source_node_id,
@@ -187,70 +187,70 @@ pub fn combine_channels(
pub fn mask( pub fn mask(
_: impl Ctx, _: impl Ctx,
/// The image to be masked. /// The image to be masked.
image: RasterDataTable<CPU>, image: Table<Raster<CPU>>,
/// The stencil to be used for masking. /// The stencil to be used for masking.
#[expose] #[expose]
stencil: RasterDataTable<CPU>, stencil: Table<Raster<CPU>>,
) -> RasterDataTable<CPU> { ) -> Table<Raster<CPU>> {
// TODO: Support multiple stencil instances // TODO: Figure out what it means to support multiple stencil rows?
let Some(stencil_instance) = stencil.instance_iter().next() else { let Some(stencil) = stencil.iter().next() else {
// No stencil provided so we return the original image // No stencil provided so we return the original image
return image; return image;
}; };
let stencil_size = DVec2::new(stencil_instance.instance.width as f64, stencil_instance.instance.height as f64); let stencil_size = DVec2::new(stencil.element.width as f64, stencil.element.height as f64);
image image
.instance_iter() .iter()
.filter_map(|mut image_instance| { .filter_map(|mut row| {
let image_size = DVec2::new(image_instance.instance.width as f64, image_instance.instance.height as f64); let image_size = DVec2::new(row.element.width as f64, row.element.height as f64);
let mask_size = stencil_instance.transform.decompose_scale(); let mask_size = stencil.transform.decompose_scale();
if mask_size == DVec2::ZERO { if mask_size == DVec2::ZERO {
return None; return None;
} }
// Transforms a point from the background image to the foreground image // Transforms a point from the background image to the foreground image
let bg_to_fg = image_instance.transform * DAffine2::from_scale(1. / image_size); let bg_to_fg = row.transform * DAffine2::from_scale(1. / image_size);
let stencil_transform_inverse = stencil_instance.transform.inverse(); let stencil_transform_inverse = stencil.transform.inverse();
for y in 0..image_instance.instance.height { for y in 0..row.element.height {
for x in 0..image_instance.instance.width { for x in 0..row.element.width {
let image_point = DVec2::new(x as f64, y as f64); let image_point = DVec2::new(x as f64, y as f64);
let mask_point = bg_to_fg.transform_point2(image_point); let mask_point = bg_to_fg.transform_point2(image_point);
let local_mask_point = stencil_transform_inverse.transform_point2(mask_point); let local_mask_point = stencil_transform_inverse.transform_point2(mask_point);
let mask_point = stencil_instance.transform.transform_point2(local_mask_point.clamp(DVec2::ZERO, DVec2::ONE)); let mask_point = stencil.transform.transform_point2(local_mask_point.clamp(DVec2::ZERO, DVec2::ONE));
let mask_point = (DAffine2::from_scale(stencil_size) * stencil_instance.transform.inverse()).transform_point2(mask_point); let mask_point = (DAffine2::from_scale(stencil_size) * stencil.transform.inverse()).transform_point2(mask_point);
let image_pixel = image_instance.instance.data_mut().get_pixel_mut(x, y).unwrap(); let image_pixel = row.element.data_mut().get_pixel_mut(x, y).unwrap();
let mask_pixel = stencil_instance.instance.sample(mask_point); let mask_pixel = stencil.element.sample(mask_point);
*image_pixel = image_pixel.multiplied_alpha(mask_pixel.l().cast_linear_channel()); *image_pixel = image_pixel.multiplied_alpha(mask_pixel.l().cast_linear_channel());
} }
} }
Some(image_instance) Some(row)
}) })
.collect() .collect()
} }
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
pub fn extend_image_to_bounds(_: impl Ctx, image: RasterDataTable<CPU>, bounds: DAffine2) -> RasterDataTable<CPU> { pub fn extend_image_to_bounds(_: impl Ctx, image: Table<Raster<CPU>>, bounds: DAffine2) -> Table<Raster<CPU>> {
image image
.instance_iter() .iter()
.map(|mut image_instance| { .map(|mut row| {
let image_aabb = Bbox::unit().affine_transform(image_instance.transform).to_axis_aligned_bbox(); let image_aabb = Bbox::unit().affine_transform(row.transform).to_axis_aligned_bbox();
let bounds_aabb = Bbox::unit().affine_transform(bounds.transform()).to_axis_aligned_bbox(); let bounds_aabb = Bbox::unit().affine_transform(bounds.transform()).to_axis_aligned_bbox();
if image_aabb.contains(bounds_aabb.start) && image_aabb.contains(bounds_aabb.end) { if image_aabb.contains(bounds_aabb.start) && image_aabb.contains(bounds_aabb.end) {
return image_instance; return row;
} }
let image_data = &image_instance.instance.data; let image_data = &row.element.data;
let (image_width, image_height) = (image_instance.instance.width, image_instance.instance.height); let (image_width, image_height) = (row.element.width, row.element.height);
if image_width == 0 || image_height == 0 { if image_width == 0 || image_height == 0 {
return empty_image((), bounds, Color::TRANSPARENT).instance_iter().next().unwrap(); return empty_image((), bounds, Color::TRANSPARENT).iter().next().unwrap();
} }
let orig_image_scale = DVec2::new(image_width as f64, image_height as f64); let orig_image_scale = DVec2::new(image_width as f64, image_height as f64);
let layer_to_image_space = DAffine2::from_scale(orig_image_scale) * image_instance.transform.inverse(); let layer_to_image_space = DAffine2::from_scale(orig_image_scale) * row.transform.inverse();
let bounds_in_image_space = Bbox::unit().affine_transform(layer_to_image_space * bounds).to_axis_aligned_bbox(); let bounds_in_image_space = Bbox::unit().affine_transform(layer_to_image_space * bounds).to_axis_aligned_bbox();
let new_start = bounds_in_image_space.start.floor().min(DVec2::ZERO); let new_start = bounds_in_image_space.start.floor().min(DVec2::ZERO);
@@ -270,26 +270,26 @@ pub fn extend_image_to_bounds(_: impl Ctx, image: RasterDataTable<CPU>, bounds:
// Compute new transform. // Compute new transform.
// let layer_to_new_texture_space = (DAffine2::from_scale(1. / new_scale) * DAffine2::from_translation(new_start) * layer_to_image_space).inverse(); // let layer_to_new_texture_space = (DAffine2::from_scale(1. / new_scale) * DAffine2::from_translation(new_start) * layer_to_image_space).inverse();
let new_texture_to_layer_space = image_instance.transform * DAffine2::from_scale(1. / orig_image_scale) * DAffine2::from_translation(new_start) * DAffine2::from_scale(new_scale); let new_texture_to_layer_space = row.transform * DAffine2::from_scale(1. / orig_image_scale) * DAffine2::from_translation(new_start) * DAffine2::from_scale(new_scale);
image_instance.instance = Raster::new_cpu(new_image); row.element = Raster::new_cpu(new_image);
image_instance.transform = new_texture_to_layer_space; row.transform = new_texture_to_layer_space;
image_instance row
}) })
.collect() .collect()
} }
#[node_macro::node(category("Debug: Raster"))] #[node_macro::node(category("Debug: Raster"))]
pub fn empty_image(_: impl Ctx, transform: DAffine2, color: Color) -> RasterDataTable<CPU> { pub fn empty_image(_: impl Ctx, transform: DAffine2, color: Color) -> Table<Raster<CPU>> {
let width = transform.transform_vector2(DVec2::new(1., 0.)).length() as u32; let width = transform.transform_vector2(DVec2::new(1., 0.)).length() as u32;
let height = transform.transform_vector2(DVec2::new(0., 1.)).length() as u32; let height = transform.transform_vector2(DVec2::new(0., 1.)).length() as u32;
let image = Image::new(width, height, color); let image = Image::new(width, height, color);
let mut result_table = RasterDataTable::new(Raster::new_cpu(image)); let mut result_table = Table::new_from_element(Raster::new_cpu(image));
let image_instance = result_table.get_mut(0).unwrap(); let row = result_table.get_mut(0).unwrap();
*image_instance.transform = transform; *row.transform = transform;
*image_instance.alpha_blending = AlphaBlending::default(); *row.alpha_blending = AlphaBlending::default();
// Callers of empty_image can safely unwrap on returned table // Callers of empty_image can safely unwrap on returned table
result_table result_table
@@ -297,7 +297,7 @@ pub fn empty_image(_: impl Ctx, transform: DAffine2, color: Color) -> RasterData
/// Constructs a raster image. /// Constructs a raster image.
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
pub fn image_value(_: impl Ctx, _primary: (), image: RasterDataTable<CPU>) -> RasterDataTable<CPU> { pub fn image_value(_: impl Ctx, _primary: (), image: Table<Raster<CPU>>) -> Table<Raster<CPU>> {
image image
} }
@@ -321,7 +321,7 @@ pub fn noise_pattern(
cellular_distance_function: CellularDistanceFunction, cellular_distance_function: CellularDistanceFunction,
cellular_return_type: CellularReturnType, cellular_return_type: CellularReturnType,
cellular_jitter: f64, cellular_jitter: f64,
) -> RasterDataTable<CPU> { ) -> Table<Raster<CPU>> {
let footprint = ctx.footprint(); let footprint = ctx.footprint();
let viewport_bounds = footprint.viewport_bounds_in_local_space(); let viewport_bounds = footprint.viewport_bounds_in_local_space();
@@ -339,7 +339,7 @@ pub fn noise_pattern(
// If the image would not be visible, return an empty image // If the image would not be visible, return an empty image
if size.x <= 0. || size.y <= 0. { if size.x <= 0. || size.y <= 0. {
return RasterDataTable::default(); return Table::new();
} }
let footprint_scale = footprint.scale(); let footprint_scale = footprint.scale();
@@ -383,8 +383,8 @@ pub fn noise_pattern(
} }
} }
return RasterDataTable::new_instance(Instance { return Table::new_from_row(TableRow {
instance: Raster::new_cpu(image), element: Raster::new_cpu(image),
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size), transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),
..Default::default() ..Default::default()
}); });
@@ -445,15 +445,15 @@ pub fn noise_pattern(
} }
} }
RasterDataTable::new_instance(Instance { Table::new_from_row(TableRow {
instance: Raster::new_cpu(image), element: Raster::new_cpu(image),
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size), transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),
..Default::default() ..Default::default()
}) })
} }
#[node_macro::node(category("Raster: Pattern"))] #[node_macro::node(category("Raster: Pattern"))]
pub fn mandelbrot(ctx: impl ExtractFootprint + Send) -> RasterDataTable<CPU> { pub fn mandelbrot(ctx: impl ExtractFootprint + Send) -> Table<Raster<CPU>> {
let footprint = ctx.footprint(); let footprint = ctx.footprint();
let viewport_bounds = footprint.viewport_bounds_in_local_space(); let viewport_bounds = footprint.viewport_bounds_in_local_space();
@@ -465,7 +465,7 @@ pub fn mandelbrot(ctx: impl ExtractFootprint + Send) -> RasterDataTable<CPU> {
// If the image would not be visible, return an empty image // If the image would not be visible, return an empty image
if size.x <= 0. || size.y <= 0. { if size.x <= 0. || size.y <= 0. {
return RasterDataTable::default(); return Table::new();
} }
let scale = footprint.scale(); let scale = footprint.scale();
@@ -487,8 +487,8 @@ pub fn mandelbrot(ctx: impl ExtractFootprint + Send) -> RasterDataTable<CPU> {
} }
} }
RasterDataTable::new_instance(Instance { Table::new_from_row(TableRow {
instance: Raster::new_cpu(Image { element: Raster::new_cpu(Image {
width, width,
height, height,
data, data,
+3 -4
View File
@@ -1,7 +1,6 @@
use crate::vector::VectorDataTable;
use graph_craft::wasm_application_io::WasmEditorApi; use graph_craft::wasm_application_io::WasmEditorApi;
use graphene_core::Ctx;
pub use graphene_core::text::*; pub use graphene_core::text::*;
use graphene_core::{Ctx, table::Table, vector::VectorData};
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
fn text<'i: 'n>( fn text<'i: 'n>(
@@ -29,10 +28,10 @@ fn text<'i: 'n>(
#[default(0.)] #[default(0.)]
tilt: f64, tilt: f64,
align: TextAlign, align: TextAlign,
/// Splits each text glyph into its own instance, i.e. row in the table of vector data. /// Splits each text glyph into its own row in the table of vector data.
#[default(false)] #[default(false)]
per_glyph_instances: bool, per_glyph_instances: bool,
) -> VectorDataTable { ) -> Table<VectorData> {
let typesetting = TypesettingConfig { let typesetting = TypesettingConfig {
font_size, font_size,
line_height_ratio, line_height_ratio,
+46 -52
View File
@@ -2,26 +2,26 @@ use graph_craft::document::value::RenderOutput;
pub use graph_craft::document::value::RenderOutputType; pub use graph_craft::document::value::RenderOutputType;
pub use graph_craft::wasm_application_io::*; pub use graph_craft::wasm_application_io::*;
use graphene_application_io::{ApplicationIo, ExportFormat, RenderConfig}; use graphene_application_io::{ApplicationIo, ExportFormat, RenderConfig};
#[cfg(target_arch = "wasm32")] use graphene_core::Artboard;
use graphene_core::instances::Instances; #[cfg(target_family = "wasm")]
#[cfg(target_arch = "wasm32")]
use graphene_core::math::bbox::Bbox; use graphene_core::math::bbox::Bbox;
use graphene_core::raster::image::Image; use graphene_core::raster::image::Image;
use graphene_core::raster_types::{CPU, Raster, RasterDataTable}; use graphene_core::raster_types::{CPU, Raster};
use graphene_core::table::Table;
use graphene_core::transform::Footprint; use graphene_core::transform::Footprint;
use graphene_core::vector::VectorDataTable; use graphene_core::vector::VectorData;
use graphene_core::{Color, Context, Ctx, ExtractFootprint, GraphicGroupTable, OwnedContextImpl, WasmNotSend}; use graphene_core::{Color, Context, Ctx, ExtractFootprint, Graphic, OwnedContextImpl, WasmNotSend};
use graphene_svg_renderer::RenderMetadata; use graphene_svg_renderer::RenderMetadata;
use graphene_svg_renderer::{GraphicElementRendered, RenderParams, RenderSvgSegmentList, SvgRender, format_transform_matrix}; use graphene_svg_renderer::{Render, RenderParams, RenderSvgSegmentList, SvgRender, format_transform_matrix};
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
use base64::Engine; use base64::Engine;
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
use glam::DAffine2; use glam::DAffine2;
use std::sync::Arc; use std::sync::Arc;
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement}; use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
#[cfg(feature = "wgpu")] #[cfg(feature = "wgpu")]
@@ -32,7 +32,7 @@ async fn create_surface<'a: 'n>(_: impl Ctx, editor: &'a WasmEditorApi) -> Arc<W
#[node_macro::node(category("Web Request"))] #[node_macro::node(category("Web Request"))]
async fn get_request(_: impl Ctx, _primary: (), #[name("URL")] url: String, discard_result: bool) -> String { async fn get_request(_: impl Ctx, _primary: (), #[name("URL")] url: String, discard_result: bool) -> String {
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
{ {
if discard_result { if discard_result {
wasm_bindgen_futures::spawn_local(async move { wasm_bindgen_futures::spawn_local(async move {
@@ -41,7 +41,7 @@ async fn get_request(_: impl Ctx, _primary: (), #[name("URL")] url: String, disc
return String::new(); return String::new();
} }
} }
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
{ {
#[cfg(feature = "tokio")] #[cfg(feature = "tokio")]
if discard_result { if discard_result {
@@ -62,7 +62,7 @@ async fn get_request(_: impl Ctx, _primary: (), #[name("URL")] url: String, disc
#[node_macro::node(category("Web Request"))] #[node_macro::node(category("Web Request"))]
async fn post_request(_: impl Ctx, _primary: (), #[name("URL")] url: String, body: Vec<u8>, discard_result: bool) -> String { async fn post_request(_: impl Ctx, _primary: (), #[name("URL")] url: String, body: Vec<u8>, discard_result: bool) -> String {
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
{ {
if discard_result { if discard_result {
wasm_bindgen_futures::spawn_local(async move { wasm_bindgen_futures::spawn_local(async move {
@@ -71,7 +71,7 @@ async fn post_request(_: impl Ctx, _primary: (), #[name("URL")] url: String, bod
return String::new(); return String::new();
} }
} }
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
{ {
#[cfg(feature = "tokio")] #[cfg(feature = "tokio")]
if discard_result { if discard_result {
@@ -100,9 +100,9 @@ fn string_to_bytes(_: impl Ctx, string: String) -> Vec<u8> {
} }
#[node_macro::node(category("Web Request"), name("Image to Bytes"))] #[node_macro::node(category("Web Request"), name("Image to Bytes"))]
fn image_to_bytes(_: impl Ctx, image: RasterDataTable<CPU>) -> Vec<u8> { fn image_to_bytes(_: impl Ctx, image: Table<Raster<CPU>>) -> Vec<u8> {
let Some(image) = image.instance_ref_iter().next() else { return vec![] }; let Some(image) = image.iter_ref().next() else { return vec![] };
image.instance.data.iter().flat_map(|color| color.to_rgb8_srgb().into_iter()).collect::<Vec<u8>>() image.element.data.iter().flat_map(|color| color.to_rgb8_srgb().into_iter()).collect::<Vec<u8>>()
} }
#[node_macro::node(category("Web Request"))] #[node_macro::node(category("Web Request"))]
@@ -121,9 +121,9 @@ async fn load_resource<'a: 'n>(_: impl Ctx, _primary: (), #[scope("editor-api")]
} }
#[node_macro::node(category("Web Request"))] #[node_macro::node(category("Web Request"))]
fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> RasterDataTable<CPU> { fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> Table<Raster<CPU>> {
let Some(image) = image::load_from_memory(data.as_ref()).ok() else { let Some(image) = image::load_from_memory(data.as_ref()).ok() else {
return RasterDataTable::default(); return Table::new();
}; };
let image = image.to_rgba32f(); let image = image.to_rgba32f();
let image = Image { let image = Image {
@@ -136,10 +136,10 @@ fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> RasterDataTable<CPU> {
..Default::default() ..Default::default()
}; };
RasterDataTable::new(Raster::new_cpu(image)) Table::new_from_element(Raster::new_cpu(image))
} }
fn render_svg(data: impl GraphicElementRendered, mut render: SvgRender, render_params: RenderParams, footprint: Footprint) -> RenderOutputType { fn render_svg(data: impl Render, mut render: SvgRender, render_params: RenderParams, footprint: Footprint) -> RenderOutputType {
if !data.contains_artboard() && !render_params.hide_artboards { if !data.contains_artboard() && !render_params.hide_artboards {
render.leaf_tag("rect", |attributes| { render.leaf_tag("rect", |attributes| {
attributes.push("x", "0"); attributes.push("x", "0");
@@ -165,14 +165,8 @@ fn render_svg(data: impl GraphicElementRendered, mut render: SvgRender, render_p
} }
#[cfg(feature = "vello")] #[cfg(feature = "vello")]
#[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))] #[cfg_attr(not(target_family = "wasm"), allow(dead_code))]
async fn render_canvas( async fn render_canvas(render_config: RenderConfig, data: impl Render, editor: &WasmEditorApi, surface_handle: Option<wgpu_executor::WgpuSurface>, render_params: RenderParams) -> RenderOutputType {
render_config: RenderConfig,
data: impl GraphicElementRendered,
editor: &WasmEditorApi,
surface_handle: Option<wgpu_executor::WgpuSurface>,
render_params: RenderParams,
) -> RenderOutputType {
use graphene_application_io::{ImageTexture, SurfaceFrame}; use graphene_application_io::{ImageTexture, SurfaceFrame};
let footprint = render_config.viewport; let footprint = render_config.viewport;
@@ -216,27 +210,27 @@ async fn render_canvas(
} }
} }
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
async fn rasterize<T: WasmNotSend + 'n>( async fn rasterize<T: WasmNotSend + 'n>(
_: impl Ctx, _: impl Ctx,
#[implementations( #[implementations(
VectorDataTable, Table<VectorData>,
RasterDataTable<CPU>, Table<Raster<CPU>>,
GraphicGroupTable, Table<Graphic>,
)] )]
mut data: Instances<T>, mut data: Table<T>,
footprint: Footprint, footprint: Footprint,
surface_handle: Arc<graphene_application_io::SurfaceHandle<HtmlCanvasElement>>, surface_handle: Arc<graphene_application_io::SurfaceHandle<HtmlCanvasElement>>,
) -> RasterDataTable<CPU> ) -> Table<Raster<CPU>>
where where
Instances<T>: GraphicElementRendered, Table<T>: Render,
{ {
use graphene_core::instances::Instance; use graphene_core::table::TableRow;
if footprint.transform.matrix2.determinant() == 0. { if footprint.transform.matrix2.determinant() == 0. {
log::trace!("Invalid footprint received for rasterization"); log::trace!("Invalid footprint received for rasterization");
return RasterDataTable::default(); return Table::new();
} }
let mut render = SvgRender::new(); let mut render = SvgRender::new();
@@ -249,8 +243,8 @@ where
..Default::default() ..Default::default()
}; };
for instance in data.instance_mut_iter() { for row in data.iter_mut() {
*instance.transform = DAffine2::from_translation(-aabb.start) * *instance.transform; *row.transform = DAffine2::from_translation(-aabb.start) * *row.transform;
} }
data.render_svg(&mut render, &render_params); data.render_svg(&mut render, &render_params);
render.format_svg(glam::DVec2::ZERO, size); render.format_svg(glam::DVec2::ZERO, size);
@@ -277,23 +271,23 @@ where
let rasterized = context.get_image_data(0., 0., resolution.x as f64, resolution.y as f64).unwrap(); let rasterized = context.get_image_data(0., 0., resolution.x as f64, resolution.y as f64).unwrap();
let image = Image::from_image_data(&rasterized.data().0, resolution.x as u32, resolution.y as u32); let image = Image::from_image_data(&rasterized.data().0, resolution.x as u32, resolution.y as u32);
RasterDataTable::new_instance(Instance { Table::new_from_row(TableRow {
instance: Raster::new_cpu(image), element: Raster::new_cpu(image),
transform: footprint.transform, transform: footprint.transform,
..Default::default() ..Default::default()
}) })
} }
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>( async fn render<'a: 'n, T: 'n + Render + WasmNotSend>(
render_config: RenderConfig, render_config: RenderConfig,
editor_api: impl Node<Context<'static>, Output = &'a WasmEditorApi>, editor_api: impl Node<Context<'static>, Output = &'a WasmEditorApi>,
#[implementations( #[implementations(
Context -> VectorDataTable, Context -> Table<VectorData>,
Context -> RasterDataTable<CPU>, Context -> Table<Raster<CPU>>,
Context -> GraphicGroupTable, Context -> Table<Graphic>,
Context -> graphene_core::Artboard, Context -> Table<Artboard>,
Context -> graphene_core::ArtboardGroupTable, Context -> Artboard,
Context -> Option<Color>, Context -> Option<Color>,
Context -> Vec<Color>, Context -> Vec<Color>,
Context -> bool, Context -> bool,
@@ -326,13 +320,13 @@ async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>(
let data = data.eval(ctx.clone()).await; let data = data.eval(ctx.clone()).await;
let editor_api = editor_api.eval(None).await; let editor_api = editor_api.eval(None).await;
#[cfg(all(feature = "vello", not(test), target_arch = "wasm32"))] #[cfg(all(feature = "vello", not(test), target_family = "wasm"))]
let _surface_handle = _surface_handle.eval(None).await; let _surface_handle = _surface_handle.eval(None).await;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
let _surface_handle: Option<wgpu_executor::WgpuSurface> = None; let _surface_handle: Option<wgpu_executor::WgpuSurface> = None;
let use_vello = editor_api.editor_preferences.use_vello(); let use_vello = editor_api.editor_preferences.use_vello();
#[cfg(all(feature = "vello", not(test), target_arch = "wasm32"))] #[cfg(all(feature = "vello", not(test), target_family = "wasm"))]
let use_vello = use_vello && _surface_handle.is_some(); let use_vello = use_vello && _surface_handle.is_some();
let mut metadata = RenderMetadata::default(); let mut metadata = RenderMetadata::default();
+184 -184
View File
@@ -6,17 +6,17 @@ use glam::{DAffine2, DVec2};
use graphene_core::blending::BlendMode; use graphene_core::blending::BlendMode;
use graphene_core::bounds::BoundingBox; use graphene_core::bounds::BoundingBox;
use graphene_core::color::Color; use graphene_core::color::Color;
use graphene_core::instances::Instance;
use graphene_core::math::quad::Quad; use graphene_core::math::quad::Quad;
use graphene_core::raster::Image; use graphene_core::raster::Image;
use graphene_core::raster_types::{CPU, GPU, RasterDataTable}; use graphene_core::raster_types::{CPU, GPU, Raster};
use graphene_core::render_complexity::RenderComplexity; use graphene_core::render_complexity::RenderComplexity;
use graphene_core::table::{Table, TableRow};
use graphene_core::transform::{Footprint, Transform}; use graphene_core::transform::{Footprint, Transform};
use graphene_core::uuid::{NodeId, generate_uuid}; use graphene_core::uuid::{NodeId, generate_uuid};
use graphene_core::vector::VectorDataTable; use graphene_core::vector::VectorData;
use graphene_core::vector::click_target::{ClickTarget, FreePoint}; use graphene_core::vector::click_target::{ClickTarget, FreePoint};
use graphene_core::vector::style::{Fill, Stroke, StrokeAlign, ViewMode}; use graphene_core::vector::style::{Fill, Stroke, StrokeAlign, ViewMode};
use graphene_core::{Artboard, ArtboardGroupTable, GraphicElement, GraphicGroupTable}; use graphene_core::{Artboard, Graphic};
use num_traits::Zero; use num_traits::Zero;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::fmt::Write; use std::fmt::Write;
@@ -206,13 +206,13 @@ pub fn to_transform(transform: DAffine2) -> usvg::Transform {
pub struct RenderMetadata { pub struct RenderMetadata {
pub upstream_footprints: HashMap<NodeId, Footprint>, pub upstream_footprints: HashMap<NodeId, Footprint>,
pub local_transforms: HashMap<NodeId, DAffine2>, pub local_transforms: HashMap<NodeId, DAffine2>,
pub first_instance_source_id: HashMap<NodeId, Option<NodeId>>, pub first_element_source_id: HashMap<NodeId, Option<NodeId>>,
pub click_targets: HashMap<NodeId, Vec<ClickTarget>>, pub click_targets: HashMap<NodeId, Vec<ClickTarget>>,
pub clip_targets: HashSet<NodeId>, pub clip_targets: HashSet<NodeId>,
} }
// TODO: Rename to "Graphical" // TODO: Rename to "Graphical"
pub trait GraphicElementRendered: BoundingBox + RenderComplexity { pub trait Render: BoundingBox + RenderComplexity {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams); fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams);
#[cfg(feature = "vello")] #[cfg(feature = "vello")]
@@ -234,37 +234,37 @@ pub trait GraphicElementRendered: BoundingBox + RenderComplexity {
fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {} fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {}
} }
impl GraphicElementRendered for GraphicGroupTable { impl Render for Table<Graphic> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
let mut iter = self.instance_ref_iter().peekable(); let mut iter = self.iter_ref().peekable();
let mut mask_state = None; let mut mask_state = None;
while let Some(instance) = iter.next() { while let Some(row) = iter.next() {
render.parent_tag( render.parent_tag(
"g", "g",
|attributes| { |attributes| {
let matrix = format_transform_matrix(*instance.transform); let matrix = format_transform_matrix(*row.transform);
if !matrix.is_empty() { if !matrix.is_empty() {
attributes.push("transform", matrix); attributes.push("transform", matrix);
} }
let opacity = instance.alpha_blending.opacity(render_params.for_mask); let opacity = row.alpha_blending.opacity(render_params.for_mask);
if opacity < 1. { if opacity < 1. {
attributes.push("opacity", opacity.to_string()); attributes.push("opacity", opacity.to_string());
} }
if instance.alpha_blending.blend_mode != BlendMode::default() { if row.alpha_blending.blend_mode != BlendMode::default() {
attributes.push("style", instance.alpha_blending.blend_mode.render()); attributes.push("style", row.alpha_blending.blend_mode.render());
} }
let next_clips = iter.peek().is_some_and(|next_instance| next_instance.instance.had_clip_enabled()); let next_clips = iter.peek().is_some_and(|next_row| next_row.element.had_clip_enabled());
if next_clips && mask_state.is_none() { if next_clips && mask_state.is_none() {
let uuid = generate_uuid(); let uuid = generate_uuid();
let mask_type = if instance.instance.can_reduce_to_clip_path() { MaskType::Clip } else { MaskType::Mask }; let mask_type = if row.element.can_reduce_to_clip_path() { MaskType::Clip } else { MaskType::Mask };
mask_state = Some((uuid, mask_type)); mask_state = Some((uuid, mask_type));
let mut svg = SvgRender::new(); let mut svg = SvgRender::new();
instance.instance.render_svg(&mut svg, &render_params.for_clipper()); row.element.render_svg(&mut svg, &render_params.for_clipper());
write!(&mut attributes.0.svg_defs, r##"{}"##, svg.svg_defs).unwrap(); write!(&mut attributes.0.svg_defs, r##"{}"##, svg.svg_defs).unwrap();
mask_type.write_to_defs(&mut attributes.0.svg_defs, uuid, svg.svg.to_svg_string()); mask_type.write_to_defs(&mut attributes.0.svg_defs, uuid, svg.svg.to_svg_string());
@@ -280,7 +280,7 @@ impl GraphicElementRendered for GraphicGroupTable {
} }
}, },
|render| { |render| {
instance.instance.render_svg(render, render_params); row.element.render_svg(render, render_params);
}, },
); );
} }
@@ -288,12 +288,12 @@ impl GraphicElementRendered for GraphicGroupTable {
#[cfg(feature = "vello")] #[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) { fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
let mut iter = self.instance_ref_iter().peekable(); let mut iter = self.iter_ref().peekable();
let mut mask_instance_state = None; let mut mask_element_and_transform = None;
while let Some(instance) = iter.next() { while let Some(row) = iter.next() {
let transform = transform * *instance.transform; let transform = transform * *row.transform;
let alpha_blending = *instance.alpha_blending; let alpha_blending = *row.alpha_blending;
let mut layer = false; let mut layer = false;
@@ -303,9 +303,9 @@ impl GraphicElementRendered for GraphicGroupTable {
}; };
let mut bounds = None; let mut bounds = None;
let opacity = instance.alpha_blending.opacity(render_params.for_mask); let opacity = row.alpha_blending.opacity(render_params.for_mask);
if opacity < 1. || (render_params.view_mode != ViewMode::Outline && alpha_blending.blend_mode != BlendMode::default()) { if opacity < 1. || (render_params.view_mode != ViewMode::Outline && alpha_blending.blend_mode != BlendMode::default()) {
bounds = instance.instance.bounding_box(transform, true); bounds = row.element.bounding_box(transform, true);
if let Some(bounds) = bounds { if let Some(bounds) = bounds {
scene.push_layer( scene.push_layer(
@@ -318,35 +318,35 @@ impl GraphicElementRendered for GraphicGroupTable {
} }
} }
let next_clips = iter.peek().is_some_and(|next_instance| next_instance.instance.had_clip_enabled()); let next_clips = iter.peek().is_some_and(|next_row| next_row.element.had_clip_enabled());
if next_clips && mask_instance_state.is_none() { if next_clips && mask_element_and_transform.is_none() {
mask_instance_state = Some((instance.instance, transform)); mask_element_and_transform = Some((row.element, transform));
instance.instance.render_to_vello(scene, transform, context, render_params); row.element.render_to_vello(scene, transform, context, render_params);
} else if let Some((instance_mask, transform_mask)) = mask_instance_state { } else if let Some((mask_element, transform_mask)) = mask_element_and_transform {
if !next_clips { if !next_clips {
mask_instance_state = None; mask_element_and_transform = None;
} }
if !layer { if !layer {
bounds = instance.instance.bounding_box(transform, true); bounds = row.element.bounding_box(transform, true);
} }
if let Some(bounds) = bounds { if let Some(bounds) = bounds {
let rect = kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y); let rect = kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);
scene.push_layer(peniko::Mix::Normal, 1., kurbo::Affine::IDENTITY, &rect); scene.push_layer(peniko::Mix::Normal, 1., kurbo::Affine::IDENTITY, &rect);
instance_mask.render_to_vello(scene, transform_mask, context, &render_params.for_clipper()); mask_element.render_to_vello(scene, transform_mask, context, &render_params.for_clipper());
scene.push_layer(peniko::BlendMode::new(peniko::Mix::Clip, peniko::Compose::SrcIn), 1., kurbo::Affine::IDENTITY, &rect); scene.push_layer(peniko::BlendMode::new(peniko::Mix::Clip, peniko::Compose::SrcIn), 1., kurbo::Affine::IDENTITY, &rect);
} }
instance.instance.render_to_vello(scene, transform, context, render_params); row.element.render_to_vello(scene, transform, context, render_params);
if bounds.is_some() { if bounds.is_some() {
scene.pop_layer(); scene.pop_layer();
scene.pop_layer(); scene.pop_layer();
} }
} else { } else {
instance.instance.render_to_vello(scene, transform, context, render_params); row.element.render_to_vello(scene, transform, context, render_params);
} }
if layer { if layer {
@@ -356,24 +356,24 @@ impl GraphicElementRendered for GraphicGroupTable {
} }
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) { fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
for instance in self.instance_ref_iter() { for row in self.iter_ref() {
if let Some(element_id) = instance.source_node_id { if let Some(element_id) = row.source_node_id {
let mut footprint = footprint; let mut footprint = footprint;
footprint.transform *= *instance.transform; footprint.transform *= *row.transform;
instance.instance.collect_metadata(metadata, footprint, Some(*element_id)); row.element.collect_metadata(metadata, footprint, Some(*element_id));
} }
} }
if let Some(graphic_group_id) = element_id { if let Some(graphic_group_id) = element_id {
let mut all_upstream_click_targets = Vec::new(); let mut all_upstream_click_targets = Vec::new();
for instance in self.instance_ref_iter() { for row in self.iter_ref() {
let mut new_click_targets = Vec::new(); let mut new_click_targets = Vec::new();
instance.instance.add_upstream_click_targets(&mut new_click_targets); row.element.add_upstream_click_targets(&mut new_click_targets);
for click_target in new_click_targets.iter_mut() { for click_target in new_click_targets.iter_mut() {
click_target.apply_transform(*instance.transform) click_target.apply_transform(*row.transform)
} }
all_upstream_click_targets.extend(new_click_targets); all_upstream_click_targets.extend(new_click_targets);
@@ -384,13 +384,13 @@ impl GraphicElementRendered for GraphicGroupTable {
} }
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) { fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
for instance in self.instance_ref_iter() { for row in self.iter_ref() {
let mut new_click_targets = Vec::new(); let mut new_click_targets = Vec::new();
instance.instance.add_upstream_click_targets(&mut new_click_targets); row.element.add_upstream_click_targets(&mut new_click_targets);
for click_target in new_click_targets.iter_mut() { for click_target in new_click_targets.iter_mut() {
click_target.apply_transform(*instance.transform) click_target.apply_transform(*row.transform)
} }
click_targets.extend(new_click_targets); click_targets.extend(new_click_targets);
@@ -398,25 +398,25 @@ impl GraphicElementRendered for GraphicGroupTable {
} }
fn contains_artboard(&self) -> bool { fn contains_artboard(&self) -> bool {
self.instance_ref_iter().any(|instance| instance.instance.contains_artboard()) self.iter_ref().any(|row| row.element.contains_artboard())
} }
fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) { fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {
for instance in self.instance_mut_iter() { for row in self.iter_mut() {
instance.instance.new_ids_from_hash(*instance.source_node_id); row.element.new_ids_from_hash(*row.source_node_id);
} }
} }
} }
impl GraphicElementRendered for VectorDataTable { impl Render for Table<VectorData> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for instance in self.instance_ref_iter() { for row in self.iter_ref() {
let multiplied_transform = *instance.transform; let multiplied_transform = *row.transform;
let vector_data = &instance.instance; let vector_data = &row.element;
// Only consider strokes with non-zero weight, since default strokes with zero weight would prevent assigning the correct stroke transform // Only consider strokes with non-zero weight, since default strokes with zero weight would prevent assigning the correct stroke transform
let has_real_stroke = vector_data.style.stroke().filter(|stroke| stroke.weight() > 0.); let has_real_stroke = vector_data.style.stroke().filter(|stroke| stroke.weight() > 0.);
let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.); let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
let applied_stroke_transform = set_stroke_transform.unwrap_or(*instance.transform); let applied_stroke_transform = set_stroke_transform.unwrap_or(*row.transform);
let applied_stroke_transform = render_params.alignment_parent_transform.unwrap_or(applied_stroke_transform); let applied_stroke_transform = render_params.alignment_parent_transform.unwrap_or(applied_stroke_transform);
let element_transform = set_stroke_transform.map(|stroke_transform| multiplied_transform * stroke_transform.inverse()); let element_transform = set_stroke_transform.map(|stroke_transform| multiplied_transform * stroke_transform.inverse());
let element_transform = element_transform.unwrap_or(DAffine2::IDENTITY); let element_transform = element_transform.unwrap_or(DAffine2::IDENTITY);
@@ -425,7 +425,7 @@ impl GraphicElementRendered for VectorDataTable {
let mut path = String::new(); let mut path = String::new();
for subpath in instance.instance.stroke_bezier_paths() { for subpath in row.element.stroke_bezier_paths() {
let _ = subpath.subpath_to_svg(&mut path, applied_stroke_transform); let _ = subpath.subpath_to_svg(&mut path, applied_stroke_transform);
} }
@@ -440,18 +440,18 @@ impl GraphicElementRendered for VectorDataTable {
MaskType::Mask MaskType::Mask
}; };
let can_use_order = !instance.instance.style.fill().is_none() && mask_type == MaskType::Mask; let can_use_order = !row.element.style.fill().is_none() && mask_type == MaskType::Mask;
if !can_use_order { if !can_use_order {
let id = format!("alignment-{}", generate_uuid()); let id = format!("alignment-{}", generate_uuid());
let mut fill_instance = instance.instance.clone(); let mut element = row.element.clone();
fill_instance.style.clear_stroke(); element.style.clear_stroke();
fill_instance.style.set_fill(Fill::solid(Color::BLACK)); element.style.set_fill(Fill::solid(Color::BLACK));
let vector_row = VectorDataTable::new_instance(Instance { let vector_row = Table::new_from_row(TableRow {
instance: fill_instance, element,
alpha_blending: *instance.alpha_blending, alpha_blending: *row.alpha_blending,
transform: *instance.transform, transform: *row.transform,
source_node_id: None, source_node_id: None,
}); });
@@ -471,7 +471,7 @@ impl GraphicElementRendered for VectorDataTable {
let mut svg = SvgRender::new(); let mut svg = SvgRender::new();
vector_row.render_svg(&mut svg, &render_params.for_alignment(applied_stroke_transform)); vector_row.render_svg(&mut svg, &render_params.for_alignment(applied_stroke_transform));
let weight = instance.instance.style.stroke().unwrap().weight * instance.transform.matrix2.determinant(); let weight = row.element.style.stroke().unwrap().weight * row.transform.matrix2.determinant();
let quad = Quad::from_box(transformed_bounds).inflate(weight); let quad = Quad::from_box(transformed_bounds).inflate(weight);
let (x, y) = quad.top_left().into(); let (x, y) = quad.top_left().into();
let (width, height) = (quad.bottom_right() - quad.top_left()).into(); let (width, height) = (quad.bottom_right() - quad.top_left()).into();
@@ -483,7 +483,7 @@ impl GraphicElementRendered for VectorDataTable {
} }
} }
let fill_and_stroke = instance.instance.style.render( let fill_and_stroke = row.element.style.render(
defs, defs,
element_transform, element_transform,
applied_stroke_transform, applied_stroke_transform,
@@ -500,13 +500,13 @@ impl GraphicElementRendered for VectorDataTable {
} }
attributes.push_val(fill_and_stroke); attributes.push_val(fill_and_stroke);
let opacity = instance.alpha_blending.opacity(render_params.for_mask); let opacity = row.alpha_blending.opacity(render_params.for_mask);
if opacity < 1. { if opacity < 1. {
attributes.push("opacity", opacity.to_string()); attributes.push("opacity", opacity.to_string());
} }
if instance.alpha_blending.blend_mode != BlendMode::default() { if row.alpha_blending.blend_mode != BlendMode::default() {
attributes.push("style", instance.alpha_blending.blend_mode.render()); attributes.push("style", row.alpha_blending.blend_mode.render());
} }
}); });
} }
@@ -519,33 +519,33 @@ impl GraphicElementRendered for VectorDataTable {
use vello::kurbo::{Cap, Join}; use vello::kurbo::{Cap, Join};
use vello::peniko; use vello::peniko;
for instance in self.instance_ref_iter() { for row in self.iter_ref() {
let multiplied_transform = parent_transform * *instance.transform; let multiplied_transform = parent_transform * *row.transform;
let has_real_stroke = instance.instance.style.stroke().filter(|stroke| stroke.weight() > 0.); let has_real_stroke = row.element.style.stroke().filter(|stroke| stroke.weight() > 0.);
let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.); let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
let applied_stroke_transform = set_stroke_transform.unwrap_or(multiplied_transform); let applied_stroke_transform = set_stroke_transform.unwrap_or(multiplied_transform);
let applied_stroke_transform = render_params.alignment_parent_transform.unwrap_or(applied_stroke_transform); let applied_stroke_transform = render_params.alignment_parent_transform.unwrap_or(applied_stroke_transform);
let element_transform = set_stroke_transform.map(|stroke_transform| multiplied_transform * stroke_transform.inverse()); let element_transform = set_stroke_transform.map(|stroke_transform| multiplied_transform * stroke_transform.inverse());
let element_transform = element_transform.unwrap_or(DAffine2::IDENTITY); let element_transform = element_transform.unwrap_or(DAffine2::IDENTITY);
let layer_bounds = instance.instance.bounding_box().unwrap_or_default(); let layer_bounds = row.element.bounding_box().unwrap_or_default();
let to_point = |p: DVec2| kurbo::Point::new(p.x, p.y); let to_point = |p: DVec2| kurbo::Point::new(p.x, p.y);
let mut path = kurbo::BezPath::new(); let mut path = kurbo::BezPath::new();
for subpath in instance.instance.stroke_bezier_paths() { for subpath in row.element.stroke_bezier_paths() {
subpath.to_vello_path(applied_stroke_transform, &mut path); subpath.to_vello_path(applied_stroke_transform, &mut path);
} }
// If we're using opacity or a blend mode, we need to push a layer // If we're using opacity or a blend mode, we need to push a layer
let blend_mode = match render_params.view_mode { let blend_mode = match render_params.view_mode {
ViewMode::Outline => peniko::Mix::Normal, ViewMode::Outline => peniko::Mix::Normal,
_ => instance.alpha_blending.blend_mode.to_peniko(), _ => row.alpha_blending.blend_mode.to_peniko(),
}; };
let mut layer = false; let mut layer = false;
let opacity = instance.alpha_blending.opacity(render_params.for_mask); let opacity = row.alpha_blending.opacity(render_params.for_mask);
if opacity < 1. || instance.alpha_blending.blend_mode != BlendMode::default() { if opacity < 1. || row.alpha_blending.blend_mode != BlendMode::default() {
layer = true; layer = true;
let weight = instance.instance.style.stroke().unwrap().weight; let weight = row.element.style.stroke().unwrap().weight;
let quad = Quad::from_box(layer_bounds).inflate(weight * element_transform.matrix2.determinant()); let quad = Quad::from_box(layer_bounds).inflate(weight * element_transform.matrix2.determinant());
let layer_bounds = quad.bounding_box(); let layer_bounds = quad.bounding_box();
scene.push_layer( scene.push_layer(
@@ -556,25 +556,25 @@ impl GraphicElementRendered for VectorDataTable {
); );
} }
let can_draw_aligned_stroke = instance.instance.style.stroke().is_some_and(|stroke| stroke.has_renderable_stroke() && stroke.align.is_not_centered()) let can_draw_aligned_stroke =
&& instance.instance.stroke_bezier_paths().all(|path| path.closed()); row.element.style.stroke().is_some_and(|stroke| stroke.has_renderable_stroke() && stroke.align.is_not_centered()) && row.element.stroke_bezier_paths().all(|path| path.closed());
let reorder_for_outside = instance.instance.style.stroke().is_some_and(|stroke| stroke.align == StrokeAlign::Outside) && !instance.instance.style.fill().is_none(); let reorder_for_outside = row.element.style.stroke().is_some_and(|stroke| stroke.align == StrokeAlign::Outside) && !row.element.style.fill().is_none();
let use_layer = can_draw_aligned_stroke && !reorder_for_outside; let use_layer = can_draw_aligned_stroke && !reorder_for_outside;
if use_layer { if use_layer {
let mut fill_instance = instance.instance.clone(); let mut element = row.element.clone();
fill_instance.style.clear_stroke(); element.style.clear_stroke();
fill_instance.style.set_fill(Fill::solid(Color::BLACK)); element.style.set_fill(Fill::solid(Color::BLACK));
let vector_data = VectorDataTable::new_instance(Instance { let vector_data = Table::new_from_row(TableRow {
instance: fill_instance, element,
alpha_blending: *instance.alpha_blending, alpha_blending: *row.alpha_blending,
transform: *instance.transform, transform: *row.transform,
source_node_id: None, source_node_id: None,
}); });
let bounds = instance.instance.bounding_box_with_transform(multiplied_transform).unwrap_or(layer_bounds); let bounds = row.element.bounding_box_with_transform(multiplied_transform).unwrap_or(layer_bounds);
let weight = instance.instance.style.stroke().unwrap().weight; let weight = row.element.style.stroke().unwrap().weight;
let quad = Quad::from_box(bounds).inflate(weight * element_transform.matrix2.determinant()); let quad = Quad::from_box(bounds).inflate(weight * element_transform.matrix2.determinant());
let bounds = quad.bounding_box(); let bounds = quad.bounding_box();
let rect = kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y); let rect = kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);
@@ -611,7 +611,7 @@ impl GraphicElementRendered for VectorDataTable {
Stroke, Stroke,
} }
let order = match instance.instance.style.stroke().is_some_and(|stroke| !stroke.paint_order.is_default()) || reorder_for_outside { let order = match row.element.style.stroke().is_some_and(|stroke| !stroke.paint_order.is_default()) || reorder_for_outside {
true => [Op::Stroke, Op::Fill], true => [Op::Stroke, Op::Fill],
false => [Op::Fill, Op::Stroke], // Default false => [Op::Fill, Op::Stroke], // Default
}; };
@@ -619,7 +619,7 @@ impl GraphicElementRendered for VectorDataTable {
for operation in order { for operation in order {
match operation { match operation {
Op::Fill => { Op::Fill => {
match instance.instance.style.fill() { match row.element.style.fill() {
Fill::Solid(color) => { Fill::Solid(color) => {
let fill = peniko::Brush::Solid(peniko::Color::new([color.r(), color.g(), color.b(), color.a()])); let fill = peniko::Brush::Solid(peniko::Color::new([color.r(), color.g(), color.b(), color.a()]));
scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, None, &path); scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, None, &path);
@@ -633,7 +633,7 @@ impl GraphicElementRendered for VectorDataTable {
}); });
} }
// Compute bounding box of the shape to determine the gradient start and end points // Compute bounding box of the shape to determine the gradient start and end points
let bounds = instance.instance.nonzero_bounding_box(); let bounds = row.element.nonzero_bounding_box();
let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]); let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
let inverse_parent_transform = if parent_transform.matrix2.determinant() != 0. { let inverse_parent_transform = if parent_transform.matrix2.determinant() != 0. {
@@ -679,7 +679,7 @@ impl GraphicElementRendered for VectorDataTable {
}; };
} }
Op::Stroke => { Op::Stroke => {
if let Some(stroke) = instance.instance.style.stroke() { if let Some(stroke) = row.element.style.stroke() {
let color = match stroke.color { let color = match stroke.color {
Some(color) => peniko::Color::new([color.r(), color.g(), color.b(), color.a()]), Some(color) => peniko::Color::new([color.r(), color.g(), color.b(), color.a()]),
None => peniko::Color::TRANSPARENT, None => peniko::Color::TRANSPARENT,
@@ -728,13 +728,13 @@ impl GraphicElementRendered for VectorDataTable {
} }
fn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option<NodeId>) { fn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option<NodeId>) {
for instance in self.instance_ref_iter() { for row in self.iter_ref() {
let instance_transform = *instance.transform; let transform = *row.transform;
let instance = instance.instance; let vector_data = row.element;
if let Some(element_id) = element_id { if let Some(element_id) = element_id {
let stroke_width = instance.style.stroke().as_ref().map_or(0., Stroke::weight); let stroke_width = vector_data.style.stroke().as_ref().map_or(0., Stroke::weight);
let filled = instance.style.fill() != &Fill::None; let filled = vector_data.style.fill() != &Fill::None;
let fill = |mut subpath: Subpath<_>| { let fill = |mut subpath: Subpath<_>| {
if filled { if filled {
subpath.set_closed(true); subpath.set_closed(true);
@@ -743,9 +743,9 @@ impl GraphicElementRendered for VectorDataTable {
}; };
// For free-floating anchors, we need to add a click target for each // For free-floating anchors, we need to add a click target for each
let single_anchors_targets = instance.point_domain.ids().iter().filter_map(|&point_id| { let single_anchors_targets = vector_data.point_domain.ids().iter().filter_map(|&point_id| {
if instance.connected_count(point_id) == 0 { if vector_data.connected_count(point_id) == 0 {
let anchor = instance.point_domain.position_from_id(point_id).unwrap_or_default(); let anchor = vector_data.point_domain.position_from_id(point_id).unwrap_or_default();
let point = FreePoint::new(point_id, anchor); let point = FreePoint::new(point_id, anchor);
Some(ClickTarget::new_with_free_point(point)) Some(ClickTarget::new_with_free_point(point))
@@ -754,7 +754,7 @@ impl GraphicElementRendered for VectorDataTable {
} }
}); });
let click_targets = instance let click_targets = vector_data
.stroke_bezier_paths() .stroke_bezier_paths()
.map(fill) .map(fill)
.map(|subpath| ClickTarget::new_with_subpath(subpath, stroke_width)) .map(|subpath| ClickTarget::new_with_subpath(subpath, stroke_width))
@@ -764,40 +764,40 @@ impl GraphicElementRendered for VectorDataTable {
metadata.click_targets.entry(element_id).or_insert(click_targets); metadata.click_targets.entry(element_id).or_insert(click_targets);
} }
if let Some(upstream_graphic_group) = &instance.upstream_graphic_group { if let Some(upstream_graphic_group) = &vector_data.upstream_graphic_group {
footprint.transform *= instance_transform; footprint.transform *= transform;
upstream_graphic_group.collect_metadata(metadata, footprint, None); upstream_graphic_group.collect_metadata(metadata, footprint, None);
} }
} }
} }
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) { fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
for instance in self.instance_ref_iter() { for row in self.iter_ref() {
let stroke_width = instance.instance.style.stroke().as_ref().map_or(0., Stroke::weight); let stroke_width = row.element.style.stroke().as_ref().map_or(0., Stroke::weight);
let filled = instance.instance.style.fill() != &Fill::None; let filled = row.element.style.fill() != &Fill::None;
let fill = |mut subpath: Subpath<_>| { let fill = |mut subpath: Subpath<_>| {
if filled { if filled {
subpath.set_closed(true); subpath.set_closed(true);
} }
subpath subpath
}; };
click_targets.extend(instance.instance.stroke_bezier_paths().map(fill).map(|subpath| { click_targets.extend(row.element.stroke_bezier_paths().map(fill).map(|subpath| {
let mut click_target = ClickTarget::new_with_subpath(subpath, stroke_width); let mut click_target = ClickTarget::new_with_subpath(subpath, stroke_width);
click_target.apply_transform(*instance.transform); click_target.apply_transform(*row.transform);
click_target click_target
})); }));
// For free-floating anchors, we need to add a click target for each // For free-floating anchors, we need to add a click target for each
let single_anchors_targets = instance.instance.point_domain.ids().iter().filter_map(|&point_id| { let single_anchors_targets = row.element.point_domain.ids().iter().filter_map(|&point_id| {
if instance.instance.connected_count(point_id) > 0 { if row.element.connected_count(point_id) > 0 {
return None; return None;
} }
let anchor = instance.instance.point_domain.position_from_id(point_id).unwrap_or_default(); let anchor = row.element.point_domain.position_from_id(point_id).unwrap_or_default();
let point = FreePoint::new(point_id, anchor); let point = FreePoint::new(point_id, anchor);
let mut click_target = ClickTarget::new_with_free_point(point); let mut click_target = ClickTarget::new_with_free_point(point);
click_target.apply_transform(*instance.transform); click_target.apply_transform(*row.transform);
Some(click_target) Some(click_target)
}); });
click_targets.extend(single_anchors_targets); click_targets.extend(single_anchors_targets);
@@ -805,13 +805,13 @@ impl GraphicElementRendered for VectorDataTable {
} }
fn new_ids_from_hash(&mut self, reference: Option<NodeId>) { fn new_ids_from_hash(&mut self, reference: Option<NodeId>) {
for instance in self.instance_mut_iter() { for row in self.iter_mut() {
instance.instance.vector_new_ids_from_hash(reference.map(|id| id.0).unwrap_or_default()); row.element.vector_new_ids_from_hash(reference.map(|id| id.0).unwrap_or_default());
} }
} }
} }
impl GraphicElementRendered for Artboard { impl Render for Artboard {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
if !render_params.hide_artboards { if !render_params.hide_artboards {
// Background // Background
@@ -907,49 +907,49 @@ impl GraphicElementRendered for Artboard {
} }
} }
impl GraphicElementRendered for ArtboardGroupTable { impl Render for Table<Artboard> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for artboard in self.instance_ref_iter() { for artboard in self.iter_ref() {
artboard.instance.render_svg(render, render_params); artboard.element.render_svg(render, render_params);
} }
} }
#[cfg(feature = "vello")] #[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) { fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
for instance in self.instance_ref_iter() { for row in self.iter_ref() {
instance.instance.render_to_vello(scene, transform, context, render_params); row.element.render_to_vello(scene, transform, context, render_params);
} }
} }
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, _element_id: Option<NodeId>) { fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, _element_id: Option<NodeId>) {
for instance in self.instance_ref_iter() { for row in self.iter_ref() {
instance.instance.collect_metadata(metadata, footprint, *instance.source_node_id); row.element.collect_metadata(metadata, footprint, *row.source_node_id);
} }
} }
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) { fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
for instance in self.instance_ref_iter() { for row in self.iter_ref() {
instance.instance.add_upstream_click_targets(click_targets); row.element.add_upstream_click_targets(click_targets);
} }
} }
fn contains_artboard(&self) -> bool { fn contains_artboard(&self) -> bool {
self.instance_ref_iter().count() > 0 self.iter_ref().count() > 0
} }
} }
impl GraphicElementRendered for RasterDataTable<CPU> { impl Render for Table<Raster<CPU>> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for instance in self.instance_ref_iter() { for row in self.iter_ref() {
let transform = *instance.transform; let image = row.element;
let image = &instance.instance; let transform = *row.transform;
if image.data.is_empty() { if image.data.is_empty() {
continue; continue;
} }
if render_params.to_canvas() { if render_params.to_canvas() {
let id = instance.source_node_id.map(|x| x.0).unwrap_or_else(|| { let id = row.source_node_id.map(|x| x.0).unwrap_or_else(|| {
let mut state = DefaultHasher::new(); let mut state = DefaultHasher::new();
image.data().hash(&mut state); image.data().hash(&mut state);
state.finish() state.finish()
@@ -973,13 +973,13 @@ impl GraphicElementRendered for RasterDataTable<CPU> {
attributes.push("width", size.x.to_string()); attributes.push("width", size.x.to_string());
attributes.push("height", size.y.to_string()); attributes.push("height", size.y.to_string());
let opacity = instance.alpha_blending.opacity(render_params.for_mask); let opacity = row.alpha_blending.opacity(render_params.for_mask);
if opacity < 1. { if opacity < 1. {
attributes.push("opacity", opacity.to_string()); attributes.push("opacity", opacity.to_string());
} }
if instance.alpha_blending.blend_mode != BlendMode::default() { if row.alpha_blending.blend_mode != BlendMode::default() {
attributes.push("style", instance.alpha_blending.blend_mode.render()); attributes.push("style", row.alpha_blending.blend_mode.render());
} }
}, },
|render| { |render| {
@@ -1013,12 +1013,12 @@ impl GraphicElementRendered for RasterDataTable<CPU> {
attributes.push("transform", matrix); attributes.push("transform", matrix);
} }
let opacity = instance.alpha_blending.opacity(render_params.for_mask); let opacity = row.alpha_blending.opacity(render_params.for_mask);
if opacity < 1. { if opacity < 1. {
attributes.push("opacity", opacity.to_string()); attributes.push("opacity", opacity.to_string());
} }
if instance.alpha_blending.blend_mode != BlendMode::default() { if row.alpha_blending.blend_mode != BlendMode::default() {
attributes.push("style", instance.alpha_blending.blend_mode.render()); attributes.push("style", row.alpha_blending.blend_mode.render());
} }
}); });
} }
@@ -1029,13 +1029,13 @@ impl GraphicElementRendered for RasterDataTable<CPU> {
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _: &mut RenderContext, render_params: &RenderParams) { fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _: &mut RenderContext, render_params: &RenderParams) {
use vello::peniko; use vello::peniko;
for instance in self.instance_ref_iter() { for row in self.iter_ref() {
let image = &instance.instance; let image = &row.element;
if image.data.is_empty() { if image.data.is_empty() {
continue; continue;
} }
let alpha_blending = *instance.alpha_blending; let alpha_blending = *row.alpha_blending;
let blend_mode = alpha_blending.blend_mode.to_peniko(); let blend_mode = alpha_blending.blend_mode.to_peniko();
let opacity = alpha_blending.opacity(render_params.for_mask); let opacity = alpha_blending.opacity(render_params.for_mask);
@@ -1051,7 +1051,7 @@ impl GraphicElementRendered for RasterDataTable<CPU> {
} }
let image = peniko::Image::new(image.to_flat_u8().0.into(), peniko::ImageFormat::Rgba8, image.width, image.height).with_extend(peniko::Extend::Repeat); let image = peniko::Image::new(image.to_flat_u8().0.into(), peniko::ImageFormat::Rgba8, image.width, image.height).with_extend(peniko::Extend::Repeat);
let image_transform = transform * *instance.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64)); let image_transform = transform * *row.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
scene.draw_image(&image, kurbo::Affine::new(image_transform.to_cols_array())); scene.draw_image(&image, kurbo::Affine::new(image_transform.to_cols_array()));
@@ -1068,7 +1068,7 @@ impl GraphicElementRendered for RasterDataTable<CPU> {
metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.)]); metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.)]);
metadata.upstream_footprints.insert(element_id, footprint); metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than one row of the graphical data table // TODO: Find a way to handle more than one row of the graphical data table
if let Some(image) = self.instance_ref_iter().next() { if let Some(image) = self.iter_ref().next() {
metadata.local_transforms.insert(element_id, *image.transform); metadata.local_transforms.insert(element_id, *image.transform);
} }
} }
@@ -1081,7 +1081,7 @@ impl GraphicElementRendered for RasterDataTable<CPU> {
const LAZY_ARC_VEC_ZERO_U8: LazyLock<Arc<Vec<u8>>> = LazyLock::new(|| Arc::new(Vec::new())); const LAZY_ARC_VEC_ZERO_U8: LazyLock<Arc<Vec<u8>>> = LazyLock::new(|| Arc::new(Vec::new()));
impl GraphicElementRendered for RasterDataTable<GPU> { impl Render for Table<Raster<GPU>> {
fn render_svg(&self, _render: &mut SvgRender, _render_params: &RenderParams) { fn render_svg(&self, _render: &mut SvgRender, _render_params: &RenderParams) {
log::warn!("tried to render texture as an svg"); log::warn!("tried to render texture as an svg");
} }
@@ -1090,8 +1090,8 @@ impl GraphicElementRendered for RasterDataTable<GPU> {
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, _render_params: &RenderParams) { fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, _render_params: &RenderParams) {
use vello::peniko; use vello::peniko;
for instance in self.instance_ref_iter() { for row in self.iter_ref() {
let blend_mode = *instance.alpha_blending; let blend_mode = *row.alpha_blending;
let mut layer = false; let mut layer = false;
if blend_mode != Default::default() { if blend_mode != Default::default() {
if let Some(bounds) = self.bounding_box(transform, true) { if let Some(bounds) = self.bounding_box(transform, true) {
@@ -1105,13 +1105,13 @@ impl GraphicElementRendered for RasterDataTable<GPU> {
let image = peniko::Image::new( let image = peniko::Image::new(
peniko::Blob::new(LAZY_ARC_VEC_ZERO_U8.deref().clone()), peniko::Blob::new(LAZY_ARC_VEC_ZERO_U8.deref().clone()),
peniko::ImageFormat::Rgba8, peniko::ImageFormat::Rgba8,
instance.instance.data().width(), row.element.data().width(),
instance.instance.data().height(), row.element.data().height(),
) )
.with_extend(peniko::Extend::Repeat); .with_extend(peniko::Extend::Repeat);
let image_transform = transform * *instance.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64)); let image_transform = transform * *row.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
scene.draw_image(&image, kurbo::Affine::new(image_transform.to_cols_array())); scene.draw_image(&image, kurbo::Affine::new(image_transform.to_cols_array()));
context.resource_overrides.push((image, instance.instance.data().clone())); context.resource_overrides.push((image, row.element.data().clone()));
if layer { if layer {
scene.pop_layer() scene.pop_layer()
@@ -1126,7 +1126,7 @@ impl GraphicElementRendered for RasterDataTable<GPU> {
metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.)]); metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.)]);
metadata.upstream_footprints.insert(element_id, footprint); metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than one row of the graphical data table // TODO: Find a way to handle more than one row of the graphical data table
if let Some(image) = self.instance_ref_iter().next() { if let Some(image) = self.iter_ref().next() {
metadata.local_transforms.insert(element_id, *image.transform); metadata.local_transforms.insert(element_id, *image.transform);
} }
} }
@@ -1137,53 +1137,53 @@ impl GraphicElementRendered for RasterDataTable<GPU> {
} }
} }
impl GraphicElementRendered for GraphicElement { impl Render for Graphic {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
match self { match self {
GraphicElement::VectorData(vector_data) => vector_data.render_svg(render, render_params), Graphic::VectorData(vector_data) => vector_data.render_svg(render, render_params),
GraphicElement::RasterDataCPU(raster) => raster.render_svg(render, render_params), Graphic::RasterDataCPU(raster) => raster.render_svg(render, render_params),
GraphicElement::RasterDataGPU(_raster) => (), Graphic::RasterDataGPU(_raster) => (),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.render_svg(render, render_params), Graphic::GraphicGroup(graphic_group) => graphic_group.render_svg(render, render_params),
} }
} }
#[cfg(feature = "vello")] #[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) { fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
match self { match self {
GraphicElement::VectorData(vector_data) => vector_data.render_to_vello(scene, transform, context, render_params), Graphic::VectorData(vector_data) => vector_data.render_to_vello(scene, transform, context, render_params),
GraphicElement::RasterDataCPU(raster) => raster.render_to_vello(scene, transform, context, render_params), Graphic::RasterDataCPU(raster) => raster.render_to_vello(scene, transform, context, render_params),
GraphicElement::RasterDataGPU(raster) => raster.render_to_vello(scene, transform, context, render_params), Graphic::RasterDataGPU(raster) => raster.render_to_vello(scene, transform, context, render_params),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.render_to_vello(scene, transform, context, render_params), Graphic::GraphicGroup(graphic_group) => graphic_group.render_to_vello(scene, transform, context, render_params),
} }
} }
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) { fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
if let Some(element_id) = element_id { if let Some(element_id) = element_id {
match self { match self {
GraphicElement::GraphicGroup(_) => { Graphic::GraphicGroup(_) => {
metadata.upstream_footprints.insert(element_id, footprint); metadata.upstream_footprints.insert(element_id, footprint);
} }
GraphicElement::VectorData(vector_data) => { Graphic::VectorData(vector_data) => {
metadata.upstream_footprints.insert(element_id, footprint); metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than one row of the graphical data table // TODO: Find a way to handle more than one row of the graphical data table
if let Some(vector_data) = vector_data.instance_ref_iter().next() { if let Some(vector_data) = vector_data.iter_ref().next() {
metadata.first_instance_source_id.insert(element_id, *vector_data.source_node_id); metadata.first_element_source_id.insert(element_id, *vector_data.source_node_id);
metadata.local_transforms.insert(element_id, *vector_data.transform); metadata.local_transforms.insert(element_id, *vector_data.transform);
} }
} }
GraphicElement::RasterDataCPU(raster_frame) => { Graphic::RasterDataCPU(raster_frame) => {
metadata.upstream_footprints.insert(element_id, footprint); metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than one row of images // TODO: Find a way to handle more than one row of images
if let Some(image) = raster_frame.instance_ref_iter().next() { if let Some(image) = raster_frame.iter_ref().next() {
metadata.local_transforms.insert(element_id, *image.transform); metadata.local_transforms.insert(element_id, *image.transform);
} }
} }
GraphicElement::RasterDataGPU(raster_frame) => { Graphic::RasterDataGPU(raster_frame) => {
metadata.upstream_footprints.insert(element_id, footprint); metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than one row of images // TODO: Find a way to handle more than one row of images
if let Some(image) = raster_frame.instance_ref_iter().next() { if let Some(image) = raster_frame.iter_ref().next() {
metadata.local_transforms.insert(element_id, *image.transform); metadata.local_transforms.insert(element_id, *image.transform);
} }
} }
@@ -1191,37 +1191,37 @@ impl GraphicElementRendered for GraphicElement {
} }
match self { match self {
GraphicElement::VectorData(vector_data) => vector_data.collect_metadata(metadata, footprint, element_id), Graphic::VectorData(vector_data) => vector_data.collect_metadata(metadata, footprint, element_id),
GraphicElement::RasterDataCPU(raster) => raster.collect_metadata(metadata, footprint, element_id), Graphic::RasterDataCPU(raster) => raster.collect_metadata(metadata, footprint, element_id),
GraphicElement::RasterDataGPU(raster) => raster.collect_metadata(metadata, footprint, element_id), Graphic::RasterDataGPU(raster) => raster.collect_metadata(metadata, footprint, element_id),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.collect_metadata(metadata, footprint, element_id), Graphic::GraphicGroup(graphic_group) => graphic_group.collect_metadata(metadata, footprint, element_id),
} }
} }
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) { fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
match self { match self {
GraphicElement::VectorData(vector_data) => vector_data.add_upstream_click_targets(click_targets), Graphic::VectorData(vector_data) => vector_data.add_upstream_click_targets(click_targets),
GraphicElement::RasterDataCPU(raster) => raster.add_upstream_click_targets(click_targets), Graphic::RasterDataCPU(raster) => raster.add_upstream_click_targets(click_targets),
GraphicElement::RasterDataGPU(raster) => raster.add_upstream_click_targets(click_targets), Graphic::RasterDataGPU(raster) => raster.add_upstream_click_targets(click_targets),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.add_upstream_click_targets(click_targets), Graphic::GraphicGroup(graphic_group) => graphic_group.add_upstream_click_targets(click_targets),
} }
} }
fn contains_artboard(&self) -> bool { fn contains_artboard(&self) -> bool {
match self { match self {
GraphicElement::VectorData(vector_data) => vector_data.contains_artboard(), Graphic::VectorData(vector_data) => vector_data.contains_artboard(),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.contains_artboard(), Graphic::GraphicGroup(graphic_group) => graphic_group.contains_artboard(),
GraphicElement::RasterDataCPU(raster) => raster.contains_artboard(), Graphic::RasterDataCPU(raster) => raster.contains_artboard(),
GraphicElement::RasterDataGPU(raster) => raster.contains_artboard(), Graphic::RasterDataGPU(raster) => raster.contains_artboard(),
} }
} }
fn new_ids_from_hash(&mut self, reference: Option<NodeId>) { fn new_ids_from_hash(&mut self, reference: Option<NodeId>) {
match self { match self {
GraphicElement::VectorData(vector_data) => vector_data.new_ids_from_hash(reference), Graphic::VectorData(vector_data) => vector_data.new_ids_from_hash(reference),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.new_ids_from_hash(reference), Graphic::GraphicGroup(graphic_group) => graphic_group.new_ids_from_hash(reference),
GraphicElement::RasterDataCPU(_) => (), Graphic::RasterDataCPU(_) => (),
GraphicElement::RasterDataGPU(_) => (), Graphic::RasterDataGPU(_) => (),
} }
} }
} }
@@ -1240,7 +1240,7 @@ fn text_attributes(attributes: &mut SvgRenderAttrs) {
attributes.push("font-size", "30"); attributes.push("font-size", "30");
} }
impl<P: Primitive> GraphicElementRendered for P { impl<P: Primitive> Render for P {
fn render_svg(&self, render: &mut SvgRender, _render_params: &RenderParams) { fn render_svg(&self, render: &mut SvgRender, _render_params: &RenderParams) {
render.parent_tag("text", text_attributes, |render| render.leaf_node(format!("{self}"))); render.parent_tag("text", text_attributes, |render| render.leaf_node(format!("{self}")));
} }
@@ -1249,7 +1249,7 @@ impl<P: Primitive> GraphicElementRendered for P {
fn render_to_vello(&self, _scene: &mut Scene, _transform: DAffine2, _context: &mut RenderContext, _render_params: &RenderParams) {} fn render_to_vello(&self, _scene: &mut Scene, _transform: DAffine2, _context: &mut RenderContext, _render_params: &RenderParams) {}
} }
impl GraphicElementRendered for Option<Color> { impl Render for Option<Color> {
fn render_svg(&self, render: &mut SvgRender, _render_params: &RenderParams) { fn render_svg(&self, render: &mut SvgRender, _render_params: &RenderParams) {
let Some(color) = self else { let Some(color) = self else {
render.parent_tag("text", |_| {}, |render| render.leaf_node("Empty color")); render.parent_tag("text", |_| {}, |render| render.leaf_node("Empty color"));
@@ -1273,7 +1273,7 @@ impl GraphicElementRendered for Option<Color> {
fn render_to_vello(&self, _scene: &mut Scene, _transform: DAffine2, _context: &mut RenderContext, _render_params: &RenderParams) {} fn render_to_vello(&self, _scene: &mut Scene, _transform: DAffine2, _context: &mut RenderContext, _render_params: &RenderParams) {}
} }
impl GraphicElementRendered for Vec<Color> { impl Render for Vec<Color> {
fn render_svg(&self, render: &mut SvgRender, _render_params: &RenderParams) { fn render_svg(&self, render: &mut SvgRender, _render_params: &RenderParams) {
for (index, &color) in self.iter().enumerate() { for (index, &color) in self.iter().enumerate() {
render.leaf_tag("rect", |attributes| { render.leaf_tag("rect", |attributes| {
@@ -4,18 +4,19 @@ use graph_craft::document::value::RenderOutput;
use graph_craft::proto::{NodeConstructor, TypeErasedBox}; use graph_craft::proto::{NodeConstructor, TypeErasedBox};
use graphene_core::raster::color::Color; use graphene_core::raster::color::Color;
use graphene_core::raster::*; use graphene_core::raster::*;
use graphene_core::raster_types::{CPU, GPU, RasterDataTable}; use graphene_core::raster_types::{CPU, GPU, Raster};
use graphene_core::vector::VectorDataTable; use graphene_core::{Artboard, concrete, generic};
use graphene_core::{Artboard, GraphicGroupTable, concrete, generic};
use graphene_core::{Cow, ProtoNodeIdentifier, Type}; use graphene_core::{Cow, ProtoNodeIdentifier, Type};
use graphene_core::{NodeIO, NodeIOTypes}; use graphene_core::{NodeIO, NodeIOTypes};
use graphene_core::{fn_type_fut, future}; use graphene_core::{fn_type_fut, future};
use graphene_std::Context; use graphene_std::Context;
use graphene_std::GraphicElement; use graphene_std::Graphic;
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
use graphene_std::any::DowncastBothNode; use graphene_std::any::DowncastBothNode;
use graphene_std::any::{ComposeTypeErased, DynAnyNode, IntoTypeErasedNode}; use graphene_std::any::{ComposeTypeErased, DynAnyNode, IntoTypeErasedNode};
use graphene_std::application_io::{ImageTexture, SurfaceFrame}; use graphene_std::application_io::{ImageTexture, SurfaceFrame};
use graphene_std::table::Table;
use graphene_std::vector::VectorData;
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
use graphene_std::wasm_application_io::{WasmEditorApi, WasmSurfaceHandle}; use graphene_std::wasm_application_io::{WasmEditorApi, WasmSurfaceHandle};
use node_registry_macros::{async_node, convert_node, into_node}; use node_registry_macros::{async_node, convert_node, into_node};
@@ -30,25 +31,25 @@ use wgpu_executor::{WgpuSurface, WindowHandle};
// TODO: turn into hashmap // TODO: turn into hashmap
fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> { fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> {
let mut node_types: Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)> = vec![ let mut node_types: Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)> = vec![
into_node!(from: VectorDataTable, to: VectorDataTable), into_node!(from: Table<VectorData>, to: Table<VectorData>),
into_node!(from: VectorDataTable, to: GraphicElement), into_node!(from: Table<VectorData>, to: Graphic),
into_node!(from: VectorDataTable, to: GraphicGroupTable), into_node!(from: Table<VectorData>, to: Table<Graphic>),
into_node!(from: GraphicGroupTable, to: GraphicGroupTable), into_node!(from: Table<Graphic>, to: Table<Graphic>),
into_node!(from: GraphicGroupTable, to: GraphicElement), into_node!(from: Table<Graphic>, to: Graphic),
into_node!(from: RasterDataTable<CPU>, to: RasterDataTable<CPU>), into_node!(from: Table<Raster<CPU>>, to: Table<Raster<CPU>>),
// into_node!(from: RasterDataTable<CPU>, to: RasterDataTable<SRGBA8>), // into_node!(from: Table<Raster<CPU>>, to: Table<Raster<SRGBA8>>),
into_node!(from: RasterDataTable<CPU>, to: GraphicElement), into_node!(from: Table<Raster<CPU>>, to: Graphic),
into_node!(from: RasterDataTable<GPU>, to: GraphicElement), into_node!(from: Table<Raster<GPU>>, to: Graphic),
into_node!(from: RasterDataTable<CPU>, to: GraphicGroupTable), into_node!(from: Table<Raster<CPU>>, to: Table<Graphic>),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => RasterDataTable<CPU>]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Raster<CPU>>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ImageTexture]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ImageTexture]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => VectorDataTable]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<VectorData>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroupTable]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Graphic>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => GraphicElement]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Graphic]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Artboard]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Artboard]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => RasterDataTable<CPU>]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Raster<CPU>>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => RasterDataTable<GPU>]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Raster<GPU>>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_core::instances::Instances<Artboard>]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_core::table::Table<Artboard>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => String]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => String]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => IVec2]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => IVec2]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => DVec2]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => DVec2]),
@@ -77,9 +78,9 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::PointSpacingType]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::PointSpacingType]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Image<Color>]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Image<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => VectorDataTable]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<VectorData>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RasterDataTable<CPU>]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Raster<CPU>>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => GraphicGroupTable]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Graphic>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<DVec2>]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<DVec2>]),
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Arc<WasmSurfaceHandle>]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Arc<WasmSurfaceHandle>]),
@@ -91,10 +92,10 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => f64]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => f64]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => String]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => String]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RenderOutput]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RenderOutput]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicElement]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Graphic]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroupTable]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Table<Graphic>]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => VectorDataTable]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Table<VectorData>]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroupTable]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Table<Graphic>]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => WgpuSurface]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => WgpuSurface]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Option<WgpuSurface>]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Option<WgpuSurface>]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => ImageTexture]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => ImageTexture]),
@@ -117,9 +118,9 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => WgpuSurface]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => WgpuSurface]),
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => RasterDataTable<GPU>]), async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Table<Raster<GPU>>]),
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RasterDataTable<GPU>]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Raster<GPU>>]),
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
into_node!(from: &WasmEditorApi, to: &WgpuExecutor), into_node!(from: &WasmEditorApi, to: &WgpuExecutor),
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
+3 -3
View File
@@ -406,7 +406,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
#register_node_impl #register_node_impl
#[cfg_attr(not(target_arch = "wasm32"), ctor)] #[cfg_attr(not(target_family = "wasm"), ctor)]
fn register_metadata() { fn register_metadata() {
let metadata = NodeMetadata { let metadata = NodeMetadata {
display_name: #display_name, display_name: #display_name,
@@ -618,7 +618,7 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st
Ok(quote! { Ok(quote! {
#[cfg_attr(not(target_arch = "wasm32"), ctor)] #[cfg_attr(not(target_family = "wasm"), ctor)]
fn register_node() { fn register_node() {
let mut registry = NODE_REGISTRY.lock().unwrap(); let mut registry = NODE_REGISTRY.lock().unwrap();
registry.insert( registry.insert(
@@ -628,7 +628,7 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st
] ]
); );
} }
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
extern "C" fn #registry_name() { extern "C" fn #registry_name() {
register_node(); register_node();
+8 -8
View File
@@ -969,7 +969,7 @@ mod tests {
fn test_node_with_implementations() { fn test_node_with_implementations() {
let attr = quote!(category("Raster: Adjustment")); let attr = quote!(category("Raster: Adjustment"));
let input = quote!( let input = quote!(
fn levels<P: Pixel>(image: RasterDataTable<P>, #[implementations(f32, f64)] shadows: f64) -> RasterDataTable<P> { fn levels<P: Pixel>(image: Table<Raster<P>>, #[implementations(f32, f64)] shadows: f64) -> Table<Raster<P>> {
// Implementation details... // Implementation details...
} }
); );
@@ -993,10 +993,10 @@ mod tests {
where_clause: None, where_clause: None,
input: Input { input: Input {
pat_ident: pat_ident("image"), pat_ident: pat_ident("image"),
ty: parse_quote!(RasterDataTable<P>), ty: parse_quote!(Table<Raster<P>>),
implementations: Punctuated::new(), implementations: Punctuated::new(),
}, },
output_type: parse_quote!(RasterDataTable<P>), output_type: parse_quote!(Table<Raster<P>>),
is_async: false, is_async: false,
fields: vec![ParsedField::Regular { fields: vec![ParsedField::Regular {
pat_ident: pat_ident("shadows"), pat_ident: pat_ident("shadows"),
@@ -1099,7 +1099,7 @@ mod tests {
fn test_async_node() { fn test_async_node() {
let attr = quote!(category("IO")); let attr = quote!(category("IO"));
let input = quote!( let input = quote!(
async fn load_image(api: &WasmEditorApi, #[expose] path: String) -> RasterDataTable<CPU> { async fn load_image(api: &WasmEditorApi, #[expose] path: String) -> Table<Raster<CPU>> {
// Implementation details... // Implementation details...
} }
); );
@@ -1126,7 +1126,7 @@ mod tests {
ty: parse_quote!(&WasmEditorApi), ty: parse_quote!(&WasmEditorApi),
implementations: Punctuated::new(), implementations: Punctuated::new(),
}, },
output_type: parse_quote!(RasterDataTable<CPU>), output_type: parse_quote!(Table<Raster<CPU>>),
is_async: true, is_async: true,
fields: vec![ParsedField::Regular { fields: vec![ParsedField::Regular {
pat_ident: pat_ident("path"), pat_ident: pat_ident("path"),
@@ -1248,7 +1248,7 @@ mod tests {
fn test_invalid_implementation_syntax() { fn test_invalid_implementation_syntax() {
let attr = quote!(category("Test")); let attr = quote!(category("Test"));
let input = quote!( let input = quote!(
fn test_node(_: (), #[implementations((Footprint, Color), (Footprint, RasterDataTable<CPU>))] input: impl Node<Footprint, Output = T>) -> T { fn test_node(_: (), #[implementations((Footprint, Color), (Footprint, Table<Raster<CPU>>))] input: impl Node<Footprint, Output = T>) -> T {
// Implementation details... // Implementation details...
} }
); );
@@ -1274,10 +1274,10 @@ mod tests {
#[implementations((), #tuples, Footprint)] footprint: F, #[implementations((), #tuples, Footprint)] footprint: F,
#[implementations( #[implementations(
() -> Color, () -> Color,
() -> RasterDataTable<CPU>, () -> Table<Raster<CPU>>,
() -> GradientStops, () -> GradientStops,
Footprint -> Color, Footprint -> Color,
Footprint -> RasterDataTable<CPU>, Footprint -> Table<Raster<CPU>>,
Footprint -> GradientStops, Footprint -> GradientStops,
)] )]
image: impl Node<F, Output = T>, image: impl Node<F, Output = T>,
+2 -2
View File
@@ -33,9 +33,9 @@ impl Context {
.request_device(&wgpu::DeviceDescriptor { .request_device(&wgpu::DeviceDescriptor {
label: None, label: None,
// #[cfg(not(feature = "passthrough"))] // #[cfg(not(feature = "passthrough"))]
#[cfg(target_arch = "wasm32")] #[cfg(target_family = "wasm")]
required_features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_family = "wasm"))]
required_features: wgpu::Features::PUSH_CONSTANTS, required_features: wgpu::Features::PUSH_CONSTANTS,
// Currently disabled because not all backend support passthrough. // Currently disabled because not all backend support passthrough.
// TODO: reenable only when vulkan adapter is available // TODO: reenable only when vulkan adapter is available

Some files were not shown because too many files have changed in this diff Show More