From 881784ba66e1d94e8054bb276004abc7f3327afa Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Tue, 28 Apr 2026 03:52:04 -0700 Subject: [PATCH] Make the Data panel show type-driven widgets for attributes (#4062) --- .../document/data_panel/data_panel_message.rs | 10 +- .../data_panel/data_panel_message_handler.rs | 291 +++++++++++++----- node-graph/libraries/core-types/src/table.rs | 10 + node-graph/nodes/graphic/src/graphic.rs | 28 +- 4 files changed, 253 insertions(+), 86 deletions(-) diff --git a/editor/src/messages/portfolio/document/data_panel/data_panel_message.rs b/editor/src/messages/portfolio/document/data_panel/data_panel_message.rs index a9067c84..57a93ed2 100644 --- a/editor/src/messages/portfolio/document/data_panel/data_panel_message.rs +++ b/editor/src/messages/portfolio/document/data_panel/data_panel_message.rs @@ -12,7 +12,7 @@ pub enum DataPanelMessage { ClearLayout, PushToElementPath { - index: usize, + step: PathStep, }, TruncateElementPath { len: usize, @@ -23,6 +23,14 @@ pub enum DataPanelMessage { }, } +/// One hop in the breadcrumb path through nested data the data panel is displaying. +/// Drilling into a row's element produces an `Element` step; drilling into one of a row's attributes produces an `Attribute` step. +#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize)] +pub enum PathStep { + Element(usize), + Attribute { row: usize, key: String }, +} + #[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize)] pub enum VectorTableTab { #[default] 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 3290c117..e00a87bb 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 @@ -1,6 +1,6 @@ use super::VectorTableTab; use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, LayoutTarget}; -use crate::messages::portfolio::document::data_panel::DataPanelMessage; +use crate::messages::portfolio::document::data_panel::{DataPanelMessage, PathStep}; use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface; use crate::messages::prelude::*; use crate::messages::tool::tool_messages::tool_prelude::*; @@ -29,7 +29,7 @@ pub struct DataPanelMessageContext<'a> { pub struct DataPanelMessageHandler { introspected_node: Option, introspected_data: Option>, - element_path: Vec, + element_path: Vec, active_vector_table_tab: VectorTableTab, } @@ -50,8 +50,8 @@ impl MessageHandler> for DataPanel self.update_layout(responses, context); } - DataPanelMessage::PushToElementPath { index } => { - self.element_path.push(index); + DataPanelMessage::PushToElementPath { step } => { + self.element_path.push(step); self.update_layout(responses, context); } DataPanelMessage::TruncateElementPath { len } => { @@ -140,7 +140,7 @@ impl DataPanelMessageHandler { struct LayoutData<'a> { current_depth: usize, - desired_path: &'a mut Vec, + desired_path: &'a mut Vec, breadcrumbs: Vec, vector_table_tab: VectorTableTab, } @@ -195,9 +195,14 @@ trait TableRowLayout { data.breadcrumbs.push(self.identifier()); self.element_page(data) } - fn element_widget(&self, index: usize) -> WidgetInstance { + /// Renders this value as a single inline widget inside a row of a Vec/Table. + /// `target` is the [`PathStep`] to push when the cell is clicked to drill into the value. + /// The default is a button labeled with `identifier()`. Types whose values are best shown + /// inline (colors, transforms, primitives, etc.) override this to ignore `target` and + /// return a richer non-navigating widget. + fn cell_widget(&self, target: PathStep) -> WidgetInstance { TextButton::new(self.identifier()) - .on_update(move |_| DataPanelMessage::PushToElementPath { index }.into()) + .on_update(move |_| DataPanelMessage::PushToElementPath { step: target.clone() }.into()) .narrow(true) .widget_instance() } @@ -214,22 +219,30 @@ impl TableRowLayout for Vec { format!("Vec<{}> ({} element{})", T::type_name(), self.len(), if self.len() == 1 { "" } else { "s" }) } fn element_page(&self, data: &mut LayoutData) -> Vec { - if let Some(index) = data.desired_path.get(data.current_depth).copied() { - if let Some(row) = self.get(index) { - data.current_depth += 1; - let result = row.layout_with_breadcrumb(data); - data.current_depth -= 1; - return result; - } else { - warn!("Desired path truncated"); - data.desired_path.truncate(data.current_depth); + if let Some(step) = data.desired_path.get(data.current_depth).cloned() { + match step { + PathStep::Element(index) => { + if let Some(row) = self.get(index) { + data.current_depth += 1; + let result = row.layout_with_breadcrumb(data); + data.current_depth -= 1; + return result; + } else { + warn!("Desired path truncated"); + data.desired_path.truncate(data.current_depth); + } + } + PathStep::Attribute { .. } => { + warn!("Attribute path step inside a Vec is unsupported"); + data.desired_path.truncate(data.current_depth); + } } } let mut rows = self .iter() .enumerate() - .map(|(index, row)| vec![TextLabel::new(format!("{index}")).narrow(true).widget_instance(), row.element_widget(index)]) + .map(|(index, row)| vec![TextLabel::new(format!("{index}")).narrow(true).widget_instance(), row.cell_widget(PathStep::Element(index))]) .collect::>(); rows.insert(0, column_headings(&["", "element"])); @@ -246,15 +259,31 @@ impl TableRowLayout for Table { format!("Table<{}> ({} element{})", T::type_name(), self.len(), if self.len() == 1 { "" } else { "s" }) } fn element_page(&self, data: &mut LayoutData) -> Vec { - if let Some(index) = data.desired_path.get(data.current_depth).copied() { - if let Some(element) = self.element(index) { - data.current_depth += 1; - let result = element.layout_with_breadcrumb(data); - data.current_depth -= 1; - return result; - } else { - warn!("Desired path truncated"); - data.desired_path.truncate(data.current_depth); + if let Some(step) = data.desired_path.get(data.current_depth).cloned() { + match step { + PathStep::Element(index) => { + if let Some(element) = self.element(index) { + data.current_depth += 1; + let result = element.layout_with_breadcrumb(data); + data.current_depth -= 1; + return result; + } else { + warn!("Desired path truncated"); + data.desired_path.truncate(data.current_depth); + } + } + PathStep::Attribute { row, key } => { + if let Some(any) = self.attribute_any(&key, row) { + data.current_depth += 1; + if let Some(result) = drilldown_attribute_layout(any, data) { + data.current_depth -= 1; + return result; + } + data.current_depth -= 1; + warn!("Drilldown unsupported for attribute {key:?}"); + } + data.desired_path.truncate(data.current_depth); + } } } @@ -263,26 +292,14 @@ impl TableRowLayout for Table { let mut rows = (0..self.len()) .map(|index| { let element = self.element(index).unwrap(); - let mut cells = vec![TextLabel::new(format!("{index}")).narrow(true).widget_instance(), element.element_widget(index)]; + let mut cells = vec![TextLabel::new(format!("{index}")).narrow(true).widget_instance(), element.cell_widget(PathStep::Element(index))]; for key in &attribute_keys { - let value = self - .attribute_display_value(key, index, |ty| { - if let Some(&value) = ty.downcast_ref::() { - Some(format_transform_matrix(value)) - } else if let Some(&value) = ty.downcast_ref::() { - Some(format_dvec2(value)) - } else if let Some(&value) = ty.downcast_ref::() { - Some(format_alpha_blending(value)) - } else if let Some(&value) = ty.downcast_ref::>() { - Some(value.map_or_else(|| "-".to_string(), |id| id.to_string())) - } else if let Some(value) = ty.downcast_ref::>() { - Some(format!("{} Objects", value.len())) - } else { - None - } - }) - .unwrap_or_else(|| "-".to_string()); - cells.push(TextLabel::new(value).narrow(true).widget_instance()); + let target = PathStep::Attribute { row: index, key: key.clone() }; + let widget = self.attribute_any(key, index).and_then(|any| dispatch_cell_widget(any, target)).unwrap_or_else(|| { + let text = self.attribute_display_value(key, index, |_| None).unwrap_or_else(|| "-".to_string()); + TextLabel::new(text).narrow(true).widget_instance() + }); + cells.push(widget); } cells }) @@ -548,7 +565,7 @@ impl TableRowLayout for Color { fn identifier(&self) -> String { format!("Color (#{})", self.to_gamma_srgb().to_rgba_hex_srgb()) } - fn element_widget(&self, _index: usize) -> WidgetInstance { + fn cell_widget(&self, _target: PathStep) -> WidgetInstance { ColorInput::new(FillChoice::Solid(*self)) .disabled(true) .menu_direction(Some(MenuDirection::Top)) @@ -556,7 +573,7 @@ impl TableRowLayout for Color { .widget_instance() } fn element_page(&self, _data: &mut LayoutData) -> Vec { - let widgets = vec![self.element_widget(0)]; + let widgets = vec![self.cell_widget(PathStep::Element(0))]; vec![LayoutGroup::row(widgets)] } } @@ -568,7 +585,7 @@ impl TableRowLayout for GradientStops { fn identifier(&self) -> String { format!("Gradient ({} stops)", self.len()) } - fn element_widget(&self, _index: usize) -> WidgetInstance { + fn cell_widget(&self, _target: PathStep) -> WidgetInstance { ColorInput::new(FillChoice::Gradient(self.clone())) .menu_direction(Some(MenuDirection::Top)) .disabled(true) @@ -576,7 +593,7 @@ impl TableRowLayout for GradientStops { .widget_instance() } fn element_page(&self, _data: &mut LayoutData) -> Vec { - let widgets = vec![self.element_widget(0)]; + let widgets = vec![self.cell_widget(PathStep::Element(0))]; vec![LayoutGroup::row(widgets)] } } @@ -588,9 +605,11 @@ impl TableRowLayout for f64 { fn identifier(&self) -> String { "Number (f64)".to_string() } + fn cell_widget(&self, _target: PathStep) -> WidgetInstance { + NumberInput::new(Some(*self)).disabled(true).max_width(220).display_decimal_places(20).widget_instance() + } fn element_page(&self, _data: &mut LayoutData) -> Vec { - let widgets = vec![NumberInput::new(Some(*self)).disabled(true).max_width(220).display_decimal_places(20).widget_instance()]; - vec![LayoutGroup::row(widgets)] + vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0))])] } } @@ -601,9 +620,11 @@ impl TableRowLayout for u32 { fn identifier(&self) -> String { "Number (u32)".to_string() } + fn cell_widget(&self, _target: PathStep) -> WidgetInstance { + NumberInput::new(Some(*self as f64)).disabled(true).max_width(220).display_decimal_places(20).widget_instance() + } fn element_page(&self, _data: &mut LayoutData) -> Vec { - let widgets = vec![NumberInput::new(Some(*self as f64)).disabled(true).max_width(220).display_decimal_places(20).widget_instance()]; - vec![LayoutGroup::row(widgets)] + vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0))])] } } @@ -614,10 +635,12 @@ impl TableRowLayout for u64 { fn identifier(&self) -> String { "Number (u64)".to_string() } + // TODO: Make this robust for large u64 values that don't fit in f64 (above roughly 2^53). Perhaps using a bigint kind of approach through the widget's data flow. + fn cell_widget(&self, _target: PathStep) -> WidgetInstance { + NumberInput::new(Some(*self as f64)).disabled(true).max_width(220).display_decimal_places(20).widget_instance() + } fn element_page(&self, _data: &mut LayoutData) -> Vec { - // TODO: Make this robust for large u64 values that don't fit in f64 (above roughly 2^53). Perhaps using a bigint kind of approach through the widget's data flow. - let widgets = vec![NumberInput::new(Some(*self as f64)).disabled(true).max_width(220).display_decimal_places(20).widget_instance()]; - vec![LayoutGroup::row(widgets)] + vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0))])] } } @@ -628,9 +651,11 @@ impl TableRowLayout for bool { fn identifier(&self) -> String { "Bool".to_string() } + fn cell_widget(&self, _target: PathStep) -> WidgetInstance { + TextLabel::new(self.to_string()).narrow(true).widget_instance() + } fn element_page(&self, _data: &mut LayoutData) -> Vec { - let widgets = vec![TextLabel::new(self.to_string()).widget_instance()]; - vec![LayoutGroup::row(widgets)] + vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0))])] } } @@ -647,9 +672,11 @@ impl TableRowLayout for String { format!("\"{}\"", first_line) } } + fn cell_widget(&self, _target: PathStep) -> WidgetInstance { + TextLabel::new(self.identifier()).narrow(true).widget_instance() + } fn element_page(&self, _data: &mut LayoutData) -> Vec { - let widgets = vec![TextAreaInput::new(self.to_string()).monospace(true).disabled(true).widget_instance()]; - vec![LayoutGroup::row(widgets)] + vec![LayoutGroup::row(vec![TextAreaInput::new(self.to_string()).monospace(true).disabled(true).widget_instance()])] } } @@ -660,9 +687,11 @@ impl TableRowLayout for Option { fn identifier(&self) -> String { "Option".to_string() } + fn cell_widget(&self, _target: PathStep) -> WidgetInstance { + TextLabel::new(format!("{self:?}")).narrow(true).widget_instance() + } fn element_page(&self, _data: &mut LayoutData) -> Vec { - let widgets = vec![TextLabel::new(format!("{self:?}")).widget_instance()]; - vec![LayoutGroup::row(widgets)] + vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0))])] } } @@ -673,9 +702,11 @@ impl TableRowLayout for DVec2 { fn identifier(&self) -> String { "Vec2".to_string() } + fn cell_widget(&self, _target: PathStep) -> WidgetInstance { + TextLabel::new(format_dvec2(*self)).narrow(true).widget_instance() + } fn element_page(&self, _data: &mut LayoutData) -> Vec { - let widgets = vec![TextLabel::new(format!("({}, {})", self.x, self.y)).widget_instance()]; - vec![LayoutGroup::row(widgets)] + vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0))])] } } @@ -686,9 +717,11 @@ impl TableRowLayout for Vec2 { fn identifier(&self) -> String { "Vec2".to_string() } + fn cell_widget(&self, _target: PathStep) -> WidgetInstance { + TextLabel::new(format_dvec2(DVec2::new(self.x as f64, self.y as f64))).narrow(true).widget_instance() + } fn element_page(&self, _data: &mut LayoutData) -> Vec { - let widgets = vec![TextLabel::new(format!("({}, {})", self.x, self.y)).widget_instance()]; - vec![LayoutGroup::row(widgets)] + vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0))])] } } @@ -699,9 +732,11 @@ impl TableRowLayout for DAffine2 { fn identifier(&self) -> String { "Transform".to_string() } + fn cell_widget(&self, _target: PathStep) -> WidgetInstance { + TextLabel::new(format_transform_matrix(*self)).narrow(true).widget_instance() + } fn element_page(&self, _data: &mut LayoutData) -> Vec { - let widgets = vec![TextLabel::new(format_transform_matrix(*self)).widget_instance()]; - vec![LayoutGroup::row(widgets)] + vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0))])] } } @@ -712,11 +747,125 @@ impl TableRowLayout for Affine2 { fn identifier(&self) -> String { "Transform".to_string() } - fn element_page(&self, _data: &mut LayoutData) -> Vec { + fn cell_widget(&self, _target: PathStep) -> WidgetInstance { let matrix = DAffine2::from_cols_array(&self.to_cols_array().map(|x| x as f64)); - let widgets = vec![TextLabel::new(format_transform_matrix(matrix)).widget_instance()]; - vec![LayoutGroup::row(widgets)] + TextLabel::new(format_transform_matrix(matrix)).narrow(true).widget_instance() } + fn element_page(&self, _data: &mut LayoutData) -> Vec { + vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0))])] + } +} + +impl TableRowLayout for AlphaBlending { + fn type_name() -> &'static str { + "AlphaBlending" + } + fn identifier(&self) -> String { + format_alpha_blending(*self) + } + fn cell_widget(&self, _target: PathStep) -> WidgetInstance { + TextLabel::new(format_alpha_blending(*self)).narrow(true).widget_instance() + } + fn element_page(&self, _data: &mut LayoutData) -> Vec { + vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0))])] + } +} + +impl TableRowLayout for Option { + fn type_name() -> &'static str { + "NodeId" + } + fn identifier(&self) -> String { + match self { + Some(node_id) => format!("Node {}", node_id), + None => "-".to_string(), + } + } + fn cell_widget(&self, _target: PathStep) -> WidgetInstance { + match *self { + Some(node_id) => TextButton::new("Go to Node") + .tooltip_description("Click to select the node with this ID in the graph.") + .on_update(move |_| NodeGraphMessage::SelectedNodesSet { nodes: vec![node_id] }.into()) + .narrow(true) + .widget_instance(), + None => TextLabel::new("-").narrow(true).widget_instance(), + } + } + fn element_page(&self, _data: &mut LayoutData) -> Vec { + vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0))])] + } +} + +/// Invokes another macro with the full list of `TableRowLayout`-implementing types whose values may appear +/// as attribute cell values. Both the cell-rendering and drilldown-navigation dispatchers iterate this list, +/// so adding a new attribute-displayable type is a single edit here. +macro_rules! known_table_row_types { + ($apply:ident) => { + $apply!( + Table, + Table, + Table, + Table>, + Table>, + Table, + Table, + GradientStops, + Color, + Option, + AlphaBlending, + DAffine2, + DVec2, + Affine2, + Vec2, + Option, + f64, + u32, + u64, + bool, + String, + Vec, + Vector, + Raster, + Raster, + Artboard, + Graphic, + ); + }; +} + +/// Type-dispatched widget for displaying an attribute cell in a `Table` row. +/// Delegates to [`TableRowLayout::cell_widget`] so the same widget code is shared between +/// element-column rendering and attribute-column rendering. Returns `None` for unrecognized types so the +/// caller can fall back to a debug-formatted [`TextLabel`]. +fn dispatch_cell_widget(any: &dyn Any, target: PathStep) -> Option { + macro_rules! check { + ( $($ty:ty),* $(,)? ) => { + $( + if let Some(value) = any.downcast_ref::<$ty>() { + return Some(value.cell_widget(target)); + } + )* + }; + } + known_table_row_types!(check); + None +} + +/// Type-dispatched recursion into an attribute value for the data panel breadcrumb navigation. +/// Mirrors [`dispatch_cell_widget`] but routes to [`TableRowLayout::layout_with_breadcrumb`]. +/// Returns `None` for unrecognized types. +fn drilldown_attribute_layout(any: &dyn Any, data: &mut LayoutData) -> Option> { + macro_rules! check { + ( $($ty:ty),* $(,)? ) => { + $( + if let Some(value) = any.downcast_ref::<$ty>() { + return Some(value.layout_with_breadcrumb(data)); + } + )* + }; + } + known_table_row_types!(check); + None } fn format_transform_matrix(transform: DAffine2) -> String { diff --git a/node-graph/libraries/core-types/src/table.rs b/node-graph/libraries/core-types/src/table.rs index ea83f0e7..8de97815 100644 --- a/node-graph/libraries/core-types/src/table.rs +++ b/node-graph/libraries/core-types/src/table.rs @@ -447,6 +447,11 @@ impl AttributeColumns { }) } + /// Returns a type-erased reference to the cell value at the given index in the column for the given key. + fn get_any_cell(&self, key: &str, index: usize) -> Option<&dyn std::any::Any> { + self.columns.iter().find_map(|(k, column)| if k == key { column.get_any(index) } else { None }) + } + /// Returns an iterator over the keys of all stored attribute columns, in insertion order. fn keys(&self) -> impl Iterator { self.columns.iter().map(|(key, _)| key.as_str()) @@ -664,6 +669,11 @@ impl Table { self.attributes.display_cell_value(key, index, overrides) } + /// Returns a type-erased reference to the attribute value at the given row index and key, or `None` if absent. + pub fn attribute_any(&self, key: &str, index: usize) -> Option<&dyn std::any::Any> { + self.attributes.get_any_cell(key, index) + } + // ===================== // Split borrow helpers // ===================== diff --git a/node-graph/nodes/graphic/src/graphic.rs b/node-graph/nodes/graphic/src/graphic.rs index c0e92db8..22cb0078 100644 --- a/node-graph/nodes/graphic/src/graphic.rs +++ b/node-graph/nodes/graphic/src/graphic.rs @@ -195,26 +195,26 @@ async fn write_attribute, Table, Table, Table, Table, Table, Table, - Table, Table, Table, Table, Table, Table, Table, - Table, Table, Table, Table, Table, Table, Table, - Table>, Table>, Table>, Table>, Table>, Table>, Table>, - Table>, Table>, Table>, Table>, Table>, Table>, Table>, - Table, Table, Table, Table, Table, Table, Table, - Table, Table, Table, Table, Table, Table, Table, + Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, + Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, + Table, Table, Table, Table, Table, Table, Table, Table, Table, Table, + Table>, Table>, Table>, Table>, Table>, Table>, Table>, Table>, Table>, Table>, + Table>, Table>, Table>, Table>, Table>, Table>, 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 attribute name (column key) to write or replace. name: String, /// The node that produces the per-row value. Called once per row with the row index in context. #[implementations( - Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> DVec2, Context -> DAffine2, Context -> Option, - Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> DVec2, Context -> DAffine2, Context -> Option, - Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> DVec2, Context -> DAffine2, Context -> Option, - Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> DVec2, Context -> DAffine2, Context -> Option, - Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> DVec2, Context -> DAffine2, Context -> Option, - Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> DVec2, Context -> DAffine2, Context -> Option, - Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> DVec2, Context -> DAffine2, Context -> Option, + Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Vec, Context -> DVec2, Context -> DAffine2, Context -> Option, Context -> Table, Context -> Table, + Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Vec, Context -> DVec2, Context -> DAffine2, Context -> Option, Context -> Table, Context -> Table, + Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Vec, Context -> DVec2, Context -> DAffine2, Context -> Option, Context -> Table, Context -> Table, + Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Vec, Context -> DVec2, Context -> DAffine2, Context -> Option, Context -> Table, Context -> Table, + Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Vec, Context -> DVec2, Context -> DAffine2, Context -> Option, Context -> Table, Context -> Table, + Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Vec, Context -> DVec2, Context -> DAffine2, Context -> Option, Context -> Table, Context -> Table, + Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Vec, Context -> DVec2, Context -> DAffine2, Context -> Option, Context -> Table, Context -> Table, )] value: impl Node<'n, Context<'static>, Output = U>, ) -> Table {