diff --git a/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs b/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs index fea4a9e816..41c6048e57 100644 --- a/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs +++ b/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs @@ -12,7 +12,7 @@ use graphene_std::memo::IORecord; use graphene_std::raster_types::{CPU, GPU, Raster}; use graphene_std::table::Table; use graphene_std::vector::Vector; -use graphene_std::vector::style::{Fill, FillChoice}; +use graphene_std::vector::style::{Fill, FillChoice, GradientSpreadMethod, GradientType}; use graphene_std::{Artboard, Color, Context, Graphic}; use std::any::Any; use std::sync::Arc; @@ -191,6 +191,11 @@ fn generate_layout(introspected_data: &Arc, Table, Table, + Table, + Table, + Table, + Table, + Table, GradientStops, f64, u32, @@ -200,6 +205,9 @@ fn generate_layout(introspected_data: &Arc, DVec2, DAffine2, + BlendMode, + GradientType, + GradientSpreadMethod, ]) } @@ -757,6 +765,51 @@ impl TableRowLayout for Affine2 { } } +impl TableRowLayout for BlendMode { + fn type_name() -> &'static str { + "BlendMode" + } + fn identifier(&self) -> String { + self.to_string() + } + fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance { + TextLabel::new(self.to_string()).narrow(true).widget_instance() + } + fn value_page(&self, _data: &mut LayoutData) -> Vec { + vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])] + } +} + +impl TableRowLayout for GradientType { + fn type_name() -> &'static str { + "GradientType" + } + fn identifier(&self) -> String { + self.to_string() + } + fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance { + TextLabel::new(self.to_string()).narrow(true).widget_instance() + } + fn value_page(&self, _data: &mut LayoutData) -> Vec { + vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])] + } +} + +impl TableRowLayout for GradientSpreadMethod { + fn type_name() -> &'static str { + "GradientSpreadMethod" + } + fn identifier(&self) -> String { + self.to_string() + } + fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance { + TextLabel::new(self.to_string()).narrow(true).widget_instance() + } + fn value_page(&self, _data: &mut LayoutData) -> Vec { + vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])] + } +} + /// Resolves the value/breadcrumb label for a `NodeId` against `network_interface` at the given `network_path`, /// falling back to "Node {id}" if the node isn't present (e.g. an ID that no longer maps to a real node). fn node_id_display_label(node_id: NodeId, network_interface: &NodeNetworkInterface, network_path: &[NodeId]) -> String { @@ -921,6 +974,9 @@ macro_rules! known_table_row_types { /// Uses `Display` instead of `Debug` for attribute types that have a nicer human-readable format. fn display_value_override(any: &dyn Any) -> Option { + if let Some(value) = any.downcast_ref::() { + return Some(format_dvec2(*value)); + } if let Some(value) = any.downcast_ref::() { return Some(value.to_string()); } diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 9af0bc21e3..8689848cb6 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -99,6 +99,11 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => Table]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Graphic]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::text::Font]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table]), @@ -156,6 +161,11 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => Table]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table]), #[cfg(target_family = "wasm")] async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => CanvasHandle]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => f64]), diff --git a/node-graph/libraries/core-types/src/table.rs b/node-graph/libraries/core-types/src/table.rs index 2dd6dd8f50..e4b119c2db 100644 --- a/node-graph/libraries/core-types/src/table.rs +++ b/node-graph/libraries/core-types/src/table.rs @@ -24,7 +24,7 @@ pub const ATTR_OPACITY: &str = "opacity"; /// Like opacity but does not affect content clipped to the row. pub const ATTR_OPACITY_FILL: &str = "opacity_fill"; -/// Whether a row inherits the alpha of the content beneath it (clipping mask). +/// `bool` for whether a row inherits the alpha of the content beneath it (clipping mask). pub const ATTR_CLIPPING_MASK: &str = "clipping_mask"; /// `Table` path from the root network to the layer node owning this row. @@ -47,16 +47,16 @@ pub const ATTR_EDITOR_CLICK_TARGET: &str = "editor:click_target"; /// its drag cage. Stored as an affine to allow non-axis-aligned frames in the future. pub const ATTR_EDITOR_TEXT_FRAME: &str = "editor:text_frame"; -/// Byte offset where a regex match begins ('Regex Find All', 'Regex Capture' text nodes). +/// `u64` byte offset where a regex match begins ('Regex Find All', 'Regex Capture' text nodes). pub const ATTR_START: &str = "start"; -/// Byte offset where a regex match ends ('Regex Find All', 'Regex Capture' text nodes). +/// `u64` byte offset where a regex match ends ('Regex Find All', 'Regex Capture' text nodes). pub const ATTR_END: &str = "end"; -/// Regex named-capture-group's name, or empty for unnamed groups ('Regex Capture' text node). +/// `String` for a regex named-capture-group's name, or empty for unnamed groups ('Regex Capture' text node). pub const ATTR_NAME: &str = "name"; -/// JSON value's type string (`"string"`, `"number"`, `"object"`, etc.) from 'JSON Query All'. +/// `String` for a JSON value's type (`"string"`, `"number"`, `"object"`, etc.) from 'JSON Query All'. pub const ATTR_TYPE: &str = "type"; /// Artboard's `DVec2` top-left corner in document coordinates. @@ -68,7 +68,7 @@ pub const ATTR_DIMENSIONS: &str = "dimensions"; /// Artboard's `Color` background fill. pub const ATTR_BACKGROUND: &str = "background"; -/// Whether an artboard clips content to its bounds. +/// `bool` for whether an artboard clips content to its bounds. pub const ATTR_CLIP: &str = "clip"; /// Gradient's `GradientSpreadMethod` (`Pad`, `Reflect`, or `Repeat`). diff --git a/node-graph/nodes/graphic/src/graphic.rs b/node-graph/nodes/graphic/src/graphic.rs index 45e1670c29..4a20645aea 100644 --- a/node-graph/nodes/graphic/src/graphic.rs +++ b/node-graph/nodes/graphic/src/graphic.rs @@ -2,11 +2,12 @@ use core_types::bounds::{BoundingBox, RenderBoundingBox}; use core_types::registry::types::{Angle, SignedInteger}; use core_types::table::{Table, TableRow}; use core_types::uuid::NodeId; -use core_types::{ATTR_EDITOR_LAYER_PATH, ATTR_TRANSFORM, AnyHash, CacheHash, CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl}; +use core_types::{ATTR_EDITOR_LAYER_PATH, ATTR_TRANSFORM, AnyHash, BlendMode, CacheHash, CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl}; use glam::{DAffine2, DVec2}; use graphic_types::graphic::{Graphic, IntoGraphicTable}; use graphic_types::{Artboard, Vector}; use raster_types::{CPU, GPU, Raster}; +use vector_types::gradient::{GradientSpreadMethod, GradientType}; use vector_types::{GradientStop, GradientStops, ReferencePoint}; /// Returns the value at the specified index in the list. @@ -216,14 +217,14 @@ pub fn path_of_subgraph(_: impl Ctx, node_path: Table) -> Table node_path.into_iter().take(len.saturating_sub(1)).collect() } -/// Writes a named attribute on each item of the input `Table`. The value-producing input is evaluated once per item, -/// with the item's index and the item itself (as a `Table` containing only that item, passed as a vararg) provided via -/// context, so the upstream pipeline can return a different value per item that may be derived from the item's own data. -/// If the attribute already exists, its values are replaced; if not, the attribute is added. -#[node_macro::node(category("General"))] +/// Sets a named attribute on the input `Table`, computing one value per item via the value-producing input. That input +/// is evaluated once per item, with the item's index and the item itself (as a `Table` containing only that item, +/// passed as a vararg) provided via context, so the upstream pipeline can return a different value per item that may +/// be derived from the item's own data. If the attribute already exists, its values are replaced; if not, it's added. +#[node_macro::node(category("Attributes: Write"))] async fn write_attribute( ctx: impl ExtractAll + CloneVarArgs + Ctx, - /// The `Table` whose items will gain or have replaced the named attribute. + /// The `Table` to set the named attribute on (one value per item). #[implementations( Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, @@ -257,6 +258,243 @@ async fn write_attribute( + _: impl Ctx, + /// The `Table` to attach the new attribute to. + #[implementations( + Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, + Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, + Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, + Table>, Table>, Table>, Table>, Table>, Table>, Table>, Table>, Table>, Table>, Table>, Table>, Table>, + Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, + Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, + Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, + Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, + Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, + Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, + Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, + Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, + Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, + )] + mut content: Table, + /// The table to draw the element values from. + #[expose] + #[implementations( + Table, Table, Table, Table>, Table, Table, Table, Table, Table, Table, Table, Table, Table, + Table, Table, Table, Table>, Table, Table, Table, Table, Table, Table, Table, Table, Table, + Table, Table, Table, Table>, Table, Table, Table, Table, Table, Table, Table, Table, Table, + Table, Table, Table, Table>, Table, Table, Table, Table, Table, Table, Table, Table, Table, + Table, Table, Table, Table>, Table, Table, Table, Table, Table, Table, Table, Table, Table, + Table, Table, Table, Table>, Table, Table, Table, Table, Table, Table, Table, Table, Table, + Table, Table, Table, Table>, Table, Table, Table, Table, Table, Table, Table, Table, Table, + Table, Table, Table, Table>, Table, Table, Table, Table, Table, Table, Table, Table, Table, + Table, Table, Table, Table>, Table, Table, Table, Table, Table, Table, Table, Table, Table, + Table, Table, Table, Table>, Table, Table, Table, Table, Table, Table, Table, Table, Table, + Table, Table, Table, Table>, Table, Table, Table, Table, Table, Table, Table, Table, Table, + Table, Table, Table, Table>, Table, Table, Table, Table, Table, Table, Table, Table, Table, + Table, Table, Table, Table>, Table, Table, Table, Table, Table, Table, Table, Table, Table, + )] + source: Table, + /// The name to assign to the new destination attribute. + name: String, +) -> Table { + if source.is_empty() { + return content; + } + for index in 0..content.len() { + let Some(value) = source.element(index % source.len()).cloned() else { continue }; + content.set_attribute(&name, index, value); + } + content +} + +/// Reads a named `Vector` attribute from the input table, outputting each value as an element of a new `Table`. +#[node_macro::node(category("Attributes: Read"))] +fn read_attribute_vector( + _: impl Ctx, + #[implementations( + Table, Table, Table, Table>, Table, Table, + Table, Table, Table, Table, Table, Table, Table, + )] + content: Table, + /// The attribute name (key) to read. + name: String, +) -> Table { + let mut result = Table::with_capacity(content.len()); + for index in 0..content.len() { + let Some(value) = content.attribute::(&name, index) else { continue }; + result.push(TableRow::new_from_element(value.clone())); + } + result +} + +/// Reads a named numeric attribute (`f64`, `u64`, or `u32`) from the input table, outputting each value as an element of a new `Table`. Integer values are converted to `f64`. +#[node_macro::node(category("Attributes: Read"))] +fn read_attribute_number( + _: impl Ctx, + #[implementations( + Table, Table, Table, Table>, Table, Table, + Table, Table, Table, Table, Table, Table, Table, + )] + content: Table, + /// The attribute name (key) to read. + name: String, +) -> Table { + let mut result = Table::with_capacity(content.len()); + for index in 0..content.len() { + let value = content + .attribute::(&name, index) + .copied() + .or_else(|| content.attribute::(&name, index).map(|v| *v as f64)) + .or_else(|| content.attribute::(&name, index).map(|v| *v as f64)); + let Some(value) = value else { continue }; + result.push(TableRow::new_from_element(value)); + } + result +} + +/// Reads a named `bool` attribute from the input table, outputting each value as an element of a new `Table`. +#[node_macro::node(category("Attributes: Read"))] +fn read_attribute_bool( + _: impl Ctx, + #[implementations( + Table, Table, Table, Table>, Table, Table, + Table, Table, Table, Table, Table, Table, Table, + )] + content: Table, + /// The attribute name (key) to read. + name: String, +) -> Table { + let mut result = Table::with_capacity(content.len()); + for index in 0..content.len() { + let Some(value) = content.attribute::(&name, index) else { continue }; + result.push(TableRow::new_from_element(*value)); + } + result +} + +/// Reads a named `String` attribute from the input table, outputting each value as an element of a new `Table`. +#[node_macro::node(category("Attributes: Read"))] +fn read_attribute_string( + _: impl Ctx, + #[implementations( + Table, Table, Table, Table>, Table, Table, + Table, Table, Table, Table, Table, Table, Table, + )] + content: Table, + /// The attribute name (key) to read. + name: String, +) -> Table { + let mut result = Table::with_capacity(content.len()); + for index in 0..content.len() { + let Some(value) = content.attribute::(&name, index) else { continue }; + result.push(TableRow::new_from_element(value.clone())); + } + result +} + +/// Reads a named `DAffine2` transform attribute from the input table, outputting each value as an element of a new `Table`. +#[node_macro::node(category("Attributes: Read"))] +fn read_attribute_transform( + _: impl Ctx, + #[implementations( + Table, Table, Table, Table>, Table, Table, + Table, Table, Table, Table, Table, Table, Table, + )] + content: Table, + /// The attribute name (key) to read. + name: String, +) -> Table { + let mut result = Table::with_capacity(content.len()); + for index in 0..content.len() { + let Some(value) = content.attribute::(&name, index) else { continue }; + result.push(TableRow::new_from_element(*value)); + } + result +} + +/// Reads a named `Color` attribute from the input table, outputting each value as an element of a new `Table`. +#[node_macro::node(category("Attributes: Read"))] +fn read_attribute_color( + _: impl Ctx, + #[implementations( + Table, Table, Table, Table>, Table, Table, + Table, Table, Table, Table, Table, Table, Table, + )] + content: Table, + /// The attribute name (key) to read. + name: String, +) -> Table { + let mut result = Table::with_capacity(content.len()); + for index in 0..content.len() { + let Some(value) = content.attribute::(&name, index) else { continue }; + result.push(TableRow::new_from_element(*value)); + } + result +} + +/// Reads a named `BlendMode` attribute from the input table, outputting each value as an element of a new `Table`. +#[node_macro::node(category("Attributes: Read"))] +fn read_attribute_blend_mode( + _: impl Ctx, + #[implementations( + Table, Table, Table, Table>, Table, Table, + Table, Table, Table, Table, Table, Table, Table, + )] + content: Table, + /// The attribute name (key) to read. + name: String, +) -> Table { + let mut result = Table::with_capacity(content.len()); + for index in 0..content.len() { + let Some(value) = content.attribute::(&name, index) else { continue }; + result.push(TableRow::new_from_element(*value)); + } + result +} + +/// Reads a named `GradientType` attribute from the input table, outputting each value as an element of a new `Table`. +#[node_macro::node(category("Attributes: Read"))] +fn read_attribute_gradient_type( + _: impl Ctx, + #[implementations( + Table, Table, Table, Table>, Table, Table, + Table, Table, Table, Table, Table, Table, Table, + )] + content: Table, + /// The attribute name (key) to read. + name: String, +) -> Table { + let mut result = Table::with_capacity(content.len()); + for index in 0..content.len() { + let Some(value) = content.attribute::(&name, index) else { continue }; + result.push(TableRow::new_from_element(*value)); + } + result +} + +/// Reads a named `GradientSpreadMethod` attribute from the input table, outputting each value as an element of a new `Table`. +#[node_macro::node(category("Attributes: Read"))] +fn read_attribute_spread_method( + _: impl Ctx, + #[implementations( + Table, Table, Table, Table>, Table, Table, + Table, Table, Table, Table, Table, Table, Table, + )] + content: Table, + /// The attribute name (key) to read. + name: String, +) -> Table { + let mut result = Table::with_capacity(content.len()); + for index in 0..content.len() { + let Some(value) = content.attribute::(&name, index) else { continue }; + result.push(TableRow::new_from_element(*value)); + } + result +} + /// Joins two `Table`s of the same type, extending the base `Table` with the items from the new `Table`. #[node_macro::node(category("General"))] pub async fn extend(