mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-29 01:18:11 +08:00
Merge branch 'master' into ir/ref-centroid
This commit is contained in:
Generated
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
@@ -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(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -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(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
+9
-13
@@ -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 }]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-8
@@ -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();
|
||||||
|
|||||||
+2
-2
@@ -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 };
|
||||||
|
|||||||
+47
-48
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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 });
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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>>;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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.);
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -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],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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>;
|
||||||
|
|||||||
@@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>,
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
)]
|
)]
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.;
|
||||||
|
|||||||
@@ -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
@@ -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<_>>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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>,
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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()));
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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],
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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")]
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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>,
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user