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 36a38e8658..b8daed3c91 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 @@ -166,11 +166,12 @@ fn generate_layout(introspected_data: &Arc>, Table, Table, - Vec, + GradientStops, f64, u32, u64, bool, + Vec, String, Option, DVec2, diff --git a/node-graph/libraries/graphic-types/src/graphic.rs b/node-graph/libraries/graphic-types/src/graphic.rs index 428affb89c..444352046e 100644 --- a/node-graph/libraries/graphic-types/src/graphic.rs +++ b/node-graph/libraries/graphic-types/src/graphic.rs @@ -204,6 +204,102 @@ pub trait IntoGraphicTable { flatten_table(&mut output, content); output } + + /// Deeply flattens any color content within a graphic table, discarding non-color content, and returning a table of only color elements. + fn into_flattened_color_table(self) -> Table + where + Self: std::marker::Sized, + { + let content = self.into_graphic_table(); + + fn flatten_table(output_color_table: &mut Table, current_graphic_table: Table) { + for current_graphic_row in current_graphic_table.iter() { + let current_graphic = current_graphic_row.element.clone(); + let source_node_id = *current_graphic_row.source_node_id; + + match current_graphic { + // If we're allowed to recurse, flatten any tables we encounter + Graphic::Graphic(mut current_graphic_table) => { + // Apply the parent graphic's transform to all child elements + for graphic in current_graphic_table.iter_mut() { + *graphic.transform = *current_graphic_row.transform * *graphic.transform; + } + + flatten_table(output_color_table, current_graphic_table); + } + // Push any leaf Color elements we encounter + Graphic::Color(color_table) => { + for current_color_row in color_table.iter() { + output_color_table.push(TableRow { + element: *current_color_row.element, + transform: *current_graphic_row.transform * *current_color_row.transform, + alpha_blending: AlphaBlending { + blend_mode: current_color_row.alpha_blending.blend_mode, + opacity: current_graphic_row.alpha_blending.opacity * current_color_row.alpha_blending.opacity, + fill: current_color_row.alpha_blending.fill, + clip: current_color_row.alpha_blending.clip, + }, + source_node_id, + }); + } + } + _ => {} + } + } + } + + let mut output = Table::new(); + flatten_table(&mut output, content); + output + } + + /// Deeply flattens any gradient content within a graphic table, discarding non-gradient content, and returning a table of only gradient elements. + fn into_flattened_gradient_table(self) -> Table + where + Self: std::marker::Sized, + { + let content = self.into_graphic_table(); + + fn flatten_table(output_gradient_table: &mut Table, current_graphic_table: Table) { + for current_graphic_row in current_graphic_table.iter() { + let current_graphic = current_graphic_row.element.clone(); + let source_node_id = *current_graphic_row.source_node_id; + + match current_graphic { + // If we're allowed to recurse, flatten any tables we encounter + Graphic::Graphic(mut current_graphic_table) => { + // Apply the parent graphic's transform to all child elements + for graphic in current_graphic_table.iter_mut() { + *graphic.transform = *current_graphic_row.transform * *graphic.transform; + } + + flatten_table(output_gradient_table, current_graphic_table); + } + // Push any leaf GradientStops elements we encounter + Graphic::Gradient(gradient_table) => { + for current_gradient_row in gradient_table.iter() { + output_gradient_table.push(TableRow { + element: current_gradient_row.element.clone(), + transform: *current_graphic_row.transform * *current_gradient_row.transform, + alpha_blending: AlphaBlending { + blend_mode: current_gradient_row.alpha_blending.blend_mode, + opacity: current_graphic_row.alpha_blending.opacity * current_gradient_row.alpha_blending.opacity, + fill: current_gradient_row.alpha_blending.fill, + clip: current_gradient_row.alpha_blending.clip, + }, + source_node_id, + }); + } + } + _ => {} + } + } + } + + let mut output = Table::new(); + flatten_table(&mut output, content); + output + } } impl IntoGraphicTable for Table { diff --git a/node-graph/nodes/graphic/src/graphic.rs b/node-graph/nodes/graphic/src/graphic.rs index 76364ba3f3..ffd98cec1e 100644 --- a/node-graph/nodes/graphic/src/graphic.rs +++ b/node-graph/nodes/graphic/src/graphic.rs @@ -7,7 +7,44 @@ use glam::{DAffine2, DVec2}; use graphic_types::graphic::{Graphic, IntoGraphicTable}; use graphic_types::{Artboard, Vector}; use raster_types::{CPU, GPU, Raster}; -use vector_types::{GradientStops, ReferencePoint}; +use vector_types::{GradientStop, GradientStops, ReferencePoint}; + +/// Returns the value at the specified index in the collection. +/// If no value exists at that index, the type's default value is returned. +#[node_macro::node(category("General"))] +pub fn index_elements( + _: impl Ctx, + /// The collection of data, such as a list or table. + #[implementations( + Vec, + Vec, + Vec, + Vec, + Vec, + Table, + Table, + Table, + Table>, + Table>, + Table, + Table, + )] + collection: T, + /// The index of the item to retrieve, starting from 0 for the first item. Negative indices count backwards from the end of the collection, starting from -1 for the last item. + index: SignedInteger, +) -> T::Output +where + T::Output: Clone + Default, +{ + let index = index as i32; + + if index < 0 { + collection.at_index_from_end(-index as usize) + } else { + collection.at_index(index as usize) + } + .unwrap_or_default() +} #[node_macro::node(category("General"))] async fn map( @@ -261,45 +298,42 @@ pub async fn flatten_vector(_: impl Ctx content.into_flattened_vector_table() } -/// Converts a graphic table into a vector table by deeply flattening any vector content it contains, and discarding any non-vector content. -#[node_macro::node(category("Vector"))] +/// Converts a graphic table into a raster table by deeply flattening any raster content it contains, and discarding any non-raster content. +#[node_macro::node(category("Raster"))] pub async fn flatten_raster(_: impl Ctx, #[implementations(Table, Table>)] content: T) -> Table> { content.into_flattened_raster_table() } -/// Returns the value at the specified index in the collection. -/// If no value exists at that index, the type's default value is returned. +/// Converts a graphic table into a color table by deeply flattening any color content it contains, and discarding any non-color content. #[node_macro::node(category("General"))] -pub fn index_elements( - _: impl Ctx, - /// The collection of data, such as a list or table. - #[implementations( - Vec, - Vec, - Vec, - Vec, - Vec, - Table, - Table, - Table, - Table>, - Table>, - Table, - Table, - )] - collection: T, - /// The index of the item to retrieve, starting from 0 for the first item. Negative indices count backwards from the end of the collection, starting from -1 for the last item. - index: SignedInteger, -) -> T::Output -where - T::Output: Clone + Default, -{ - let index = index as i32; +pub async fn flatten_color(_: impl Ctx, #[implementations(Table, Table)] content: T) -> Table { + content.into_flattened_color_table() +} - if index < 0 { - collection.at_index_from_end(-index as usize) - } else { - collection.at_index(index as usize) +/// Converts a graphic table into a gradient table by deeply flattening any gradient content it contains, and discarding any non-gradient content. +#[node_macro::node(category("General"))] +pub async fn flatten_gradient(_: impl Ctx, #[implementations(Table, Table)] content: T) -> Table { + content.into_flattened_gradient_table() +} + +/// Constructs a gradient from a table of colors, where the colors are evenly distributed as gradient stops across the range from 0 to 1. +#[node_macro::node(category("Color"))] +fn colors_to_gradient(_: impl Ctx, #[implementations(Table, Table)] colors: T) -> GradientStops { + let colors = colors.into_flattened_color_table(); + let total_colors = colors.len(); + + if total_colors == 0 { + return GradientStops::new(vec![GradientStop { + position: 0., + midpoint: 0.5, + color: Color::BLACK, + }]); } - .unwrap_or_default() + + let colors = colors.into_iter().enumerate().map(|(index, row)| GradientStop { + position: index as f64 / (total_colors - 1).max(1) as f64, + midpoint: 0.5, + color: row.element, + }); + GradientStops::new(colors) }