Improve the Data panel with type-specific detail pages and nested-layer support (#4070)
* Improve the Data panel with more type-specific detail pages * Add network_path to SetDisplayName so renames target any network depth * Track nested layers via full editor:layer paths and rename parent_layer to path_of_subgraph * Polish the data panel NodeId leaf page with an editable name field * Make lock and visibility toggles work for layers in nested subgraphs * Fix formatting * Fix connected_to_output running in the wrong network for nested-layer toggles
This commit is contained in:
parent
84fb901b5a
commit
6b11b47753
|
|
@ -35,9 +35,14 @@ impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHa
|
||||||
});
|
});
|
||||||
responses.add(NodeGraphMessage::SetDisplayNameImpl {
|
responses.add(NodeGraphMessage::SetDisplayNameImpl {
|
||||||
node_id,
|
node_id,
|
||||||
|
network_path: Vec::new(),
|
||||||
alias: "Background".to_string(),
|
alias: "Background".to_string(),
|
||||||
});
|
});
|
||||||
responses.add(NodeGraphMessage::SetLocked { node_id, locked: true });
|
responses.add(NodeGraphMessage::SetLocked {
|
||||||
|
node_id,
|
||||||
|
network_path: Vec::new(),
|
||||||
|
locked: true,
|
||||||
|
});
|
||||||
} else if self.dimensions.x > 0 && self.dimensions.y > 0 {
|
} else if self.dimensions.x > 0 && self.dimensions.y > 0 {
|
||||||
// Finite canvas: create an artboard with the specified dimensions
|
// Finite canvas: create an artboard with the specified dimensions
|
||||||
responses.add(GraphOperationMessage::NewArtboard {
|
responses.add(GraphOperationMessage::NewArtboard {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@ pub enum DataPanelMessage {
|
||||||
inspect_result: InspectResult,
|
inspect_result: InspectResult,
|
||||||
},
|
},
|
||||||
ClearLayout,
|
ClearLayout,
|
||||||
|
/// Re-render the existing layout against the latest network interface state. Use this when node metadata
|
||||||
|
/// (display name, visibility, locked, etc.) changes but the introspected output value hasn't.
|
||||||
|
Refresh,
|
||||||
|
|
||||||
PushToElementPath {
|
PushToElementPath {
|
||||||
step: PathStep,
|
step: PathStep,
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,12 @@ impl MessageHandler<DataPanelMessage, DataPanelMessageContext<'_>> for DataPanel
|
||||||
self.active_vector_table_tab = VectorTableTab::default();
|
self.active_vector_table_tab = VectorTableTab::default();
|
||||||
self.update_layout(responses, context);
|
self.update_layout(responses, context);
|
||||||
}
|
}
|
||||||
|
DataPanelMessage::Refresh => {
|
||||||
|
// Re-render against the current network_interface without disturbing introspected_data or the breadcrumb path.
|
||||||
|
// Always re-renders, even when introspected_data is None, since the header still shows the inspected node's
|
||||||
|
// name/lock/visibility state from the network interface and that state can change independently of the data.
|
||||||
|
self.update_layout(responses, context);
|
||||||
|
}
|
||||||
|
|
||||||
DataPanelMessage::PushToElementPath { step } => {
|
DataPanelMessage::PushToElementPath { step } => {
|
||||||
self.element_path.push(step);
|
self.element_path.push(step);
|
||||||
|
|
@ -80,6 +86,8 @@ impl DataPanelMessageHandler {
|
||||||
let mut layout_data = LayoutData {
|
let mut layout_data = LayoutData {
|
||||||
current_depth: 0,
|
current_depth: 0,
|
||||||
desired_path: &mut self.element_path,
|
desired_path: &mut self.element_path,
|
||||||
|
network_interface: &*network_interface,
|
||||||
|
node_lookup_network_path: Vec::new(),
|
||||||
breadcrumbs: Vec::new(),
|
breadcrumbs: Vec::new(),
|
||||||
vector_table_tab: self.active_vector_table_tab,
|
vector_table_tab: self.active_vector_table_tab,
|
||||||
};
|
};
|
||||||
|
|
@ -98,6 +106,7 @@ impl DataPanelMessageHandler {
|
||||||
if let Some((node_id, parent_path)) = self.introspected_node_path.split_last() {
|
if let Some((node_id, parent_path)) = self.introspected_node_path.split_last() {
|
||||||
let node_id = *node_id;
|
let node_id = *node_id;
|
||||||
let is_layer = network_interface.is_layer(&node_id, parent_path);
|
let is_layer = network_interface.is_layer(&node_id, parent_path);
|
||||||
|
let parent_path_owned = parent_path.to_vec();
|
||||||
|
|
||||||
widgets.extend([
|
widgets.extend([
|
||||||
if is_layer {
|
if is_layer {
|
||||||
|
|
@ -111,6 +120,7 @@ impl DataPanelMessageHandler {
|
||||||
.on_update(move |text_input| {
|
.on_update(move |text_input| {
|
||||||
NodeGraphMessage::SetDisplayName {
|
NodeGraphMessage::SetDisplayName {
|
||||||
node_id,
|
node_id,
|
||||||
|
network_path: parent_path_owned.clone(),
|
||||||
alias: text_input.value.clone(),
|
alias: text_input.value.clone(),
|
||||||
skip_adding_history_step: false,
|
skip_adding_history_step: false,
|
||||||
}
|
}
|
||||||
|
|
@ -144,6 +154,11 @@ impl DataPanelMessageHandler {
|
||||||
struct LayoutData<'a> {
|
struct LayoutData<'a> {
|
||||||
current_depth: usize,
|
current_depth: usize,
|
||||||
desired_path: &'a mut Vec<PathStep>,
|
desired_path: &'a mut Vec<PathStep>,
|
||||||
|
network_interface: &'a NodeNetworkInterface,
|
||||||
|
/// The `network_path` to use when resolving a `NodeId` cell or leaf page against the network interface.
|
||||||
|
/// Defaults to root (`&[]`); `Table<NodeId>` rendering temporarily sets it to the path's prefix so nested
|
||||||
|
/// layers (e.g. inside a Ctrl+M-merged custom subgraph) resolve correctly.
|
||||||
|
node_lookup_network_path: Vec<NodeId>,
|
||||||
breadcrumbs: Vec<String>,
|
breadcrumbs: Vec<String>,
|
||||||
vector_table_tab: VectorTableTab,
|
vector_table_tab: VectorTableTab,
|
||||||
}
|
}
|
||||||
|
|
@ -161,6 +176,11 @@ macro_rules! generate_layout_downcast {
|
||||||
}
|
}
|
||||||
// TODO: We simply try all these types sequentially. Find a better strategy.
|
// TODO: We simply try all these types sequentially. Find a better strategy.
|
||||||
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>> {
|
||||||
|
// `Table<NodeId>` is interpreted as a path (e.g. the value produced by `path_of_subgraph`), shown as a
|
||||||
|
// table where each row's NodeId resolves against the prefix made up of the rows above it.
|
||||||
|
if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<NodeId>>>() {
|
||||||
|
return Some(table_node_id_path_layout_with_breadcrumb(&io.output, data));
|
||||||
|
}
|
||||||
generate_layout_downcast!(introspected_data, data, [
|
generate_layout_downcast!(introspected_data, data, [
|
||||||
Table<Artboard>,
|
Table<Artboard>,
|
||||||
Table<Graphic>,
|
Table<Graphic>,
|
||||||
|
|
@ -170,7 +190,6 @@ fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'st
|
||||||
Table<Color>,
|
Table<Color>,
|
||||||
Table<GradientStops>,
|
Table<GradientStops>,
|
||||||
Table<String>,
|
Table<String>,
|
||||||
Table<NodeId>,
|
|
||||||
Table<f64>,
|
Table<f64>,
|
||||||
Table<u8>,
|
Table<u8>,
|
||||||
GradientStops,
|
GradientStops,
|
||||||
|
|
@ -203,10 +222,12 @@ trait TableRowLayout {
|
||||||
}
|
}
|
||||||
/// Renders this value as a single inline widget inside a row of a Vec/Table.
|
/// 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.
|
/// `target` is the [`PathStep`] to push when the cell is clicked to drill into the value.
|
||||||
|
/// `data` provides shared context (notably `network_interface`) for types whose label or content
|
||||||
|
/// depends on lookup beyond their own value (e.g. `NodeId` resolving a node's display name).
|
||||||
/// The default is a button labeled with `identifier()`. Types whose values are best shown
|
/// 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
|
/// inline (colors, transforms, primitives, etc.) override this to ignore `target` and
|
||||||
/// return a richer non-navigating widget.
|
/// return a richer non-navigating widget.
|
||||||
fn cell_widget(&self, target: PathStep) -> WidgetInstance {
|
fn cell_widget(&self, target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
||||||
TextButton::new(self.identifier())
|
TextButton::new(self.identifier())
|
||||||
.on_update(move |_| DataPanelMessage::PushToElementPath { step: target.clone() }.into())
|
.on_update(move |_| DataPanelMessage::PushToElementPath { step: target.clone() }.into())
|
||||||
.narrow(true)
|
.narrow(true)
|
||||||
|
|
@ -258,10 +279,10 @@ impl<T: TableRowLayout> TableRowLayout for Table<T> {
|
||||||
let mut rows = (0..self.len())
|
let mut rows = (0..self.len())
|
||||||
.map(|index| {
|
.map(|index| {
|
||||||
let element = self.element(index).unwrap();
|
let element = self.element(index).unwrap();
|
||||||
let mut cells = vec![TextLabel::new(format!("{index}")).narrow(true).widget_instance(), element.cell_widget(PathStep::Element(index))];
|
let mut cells = vec![TextLabel::new(format!("{index}")).narrow(true).widget_instance(), element.cell_widget(PathStep::Element(index), data)];
|
||||||
for key in &attribute_keys {
|
for key in &attribute_keys {
|
||||||
let target = PathStep::Attribute { row: index, key: key.clone() };
|
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 widget = self.attribute_any(key, index).and_then(|any| dispatch_cell_widget(any, target, data)).unwrap_or_else(|| {
|
||||||
let text = self.attribute_display_value(key, index, |_| None).unwrap_or_else(|| "-".to_string());
|
let text = self.attribute_display_value(key, index, |_| None).unwrap_or_else(|| "-".to_string());
|
||||||
TextLabel::new(text).narrow(true).widget_instance()
|
TextLabel::new(text).narrow(true).widget_instance()
|
||||||
});
|
});
|
||||||
|
|
@ -531,7 +552,7 @@ impl TableRowLayout for Color {
|
||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
format!("Color (#{})", self.to_gamma_srgb().to_rgba_hex_srgb())
|
format!("Color (#{})", self.to_gamma_srgb().to_rgba_hex_srgb())
|
||||||
}
|
}
|
||||||
fn cell_widget(&self, _target: PathStep) -> WidgetInstance {
|
fn cell_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
||||||
ColorInput::new(FillChoice::Solid(*self))
|
ColorInput::new(FillChoice::Solid(*self))
|
||||||
.disabled(true)
|
.disabled(true)
|
||||||
.menu_direction(Some(MenuDirection::Top))
|
.menu_direction(Some(MenuDirection::Top))
|
||||||
|
|
@ -539,7 +560,7 @@ impl TableRowLayout for Color {
|
||||||
.widget_instance()
|
.widget_instance()
|
||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
let widgets = vec![self.cell_widget(PathStep::Element(0))];
|
let widgets = vec![self.cell_widget(PathStep::Element(0), _data)];
|
||||||
vec![LayoutGroup::row(widgets)]
|
vec![LayoutGroup::row(widgets)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -551,7 +572,7 @@ impl TableRowLayout for GradientStops {
|
||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
format!("Gradient ({} stops)", self.len())
|
format!("Gradient ({} stops)", self.len())
|
||||||
}
|
}
|
||||||
fn cell_widget(&self, _target: PathStep) -> WidgetInstance {
|
fn cell_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
||||||
ColorInput::new(FillChoice::Gradient(self.clone()))
|
ColorInput::new(FillChoice::Gradient(self.clone()))
|
||||||
.menu_direction(Some(MenuDirection::Top))
|
.menu_direction(Some(MenuDirection::Top))
|
||||||
.disabled(true)
|
.disabled(true)
|
||||||
|
|
@ -559,7 +580,7 @@ impl TableRowLayout for GradientStops {
|
||||||
.widget_instance()
|
.widget_instance()
|
||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
let widgets = vec![self.cell_widget(PathStep::Element(0))];
|
let widgets = vec![self.cell_widget(PathStep::Element(0), _data)];
|
||||||
vec![LayoutGroup::row(widgets)]
|
vec![LayoutGroup::row(widgets)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -569,13 +590,13 @@ impl TableRowLayout for f64 {
|
||||||
"Number (f64)"
|
"Number (f64)"
|
||||||
}
|
}
|
||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
"Number (f64)".to_string()
|
format!("{self}")
|
||||||
}
|
|
||||||
fn cell_widget(&self, _target: PathStep) -> WidgetInstance {
|
|
||||||
NumberInput::new(Some(*self)).disabled(true).max_width(220).display_decimal_places(20).widget_instance()
|
|
||||||
}
|
}
|
||||||
|
// Cells fall back to the default drill-in button (labeled with the value via `identifier`); the leaf page shows the rich `NumberInput`.
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0))])]
|
vec![LayoutGroup::row(vec![
|
||||||
|
NumberInput::new(Some(*self)).disabled(true).max_width(220).display_decimal_places(20).widget_instance(),
|
||||||
|
])]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -586,11 +607,9 @@ impl TableRowLayout for u8 {
|
||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
format!("{self:02X}")
|
format!("{self:02X}")
|
||||||
}
|
}
|
||||||
fn cell_widget(&self, _target: PathStep) -> WidgetInstance {
|
// Cells fall back to the default drill-in button (labeled with the hex value via `identifier`); the leaf page shows the same hex value as a label.
|
||||||
TextLabel::new(self.identifier()).narrow(true).widget_instance()
|
|
||||||
}
|
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0))])]
|
vec![LayoutGroup::row(vec![TextLabel::new(self.identifier()).widget_instance()])]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -599,13 +618,13 @@ impl TableRowLayout for u32 {
|
||||||
"Number (u32)"
|
"Number (u32)"
|
||||||
}
|
}
|
||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
"Number (u32)".to_string()
|
format!("{self}")
|
||||||
}
|
|
||||||
fn cell_widget(&self, _target: PathStep) -> WidgetInstance {
|
|
||||||
NumberInput::new(Some(*self as f64)).disabled(true).max_width(220).display_decimal_places(20).widget_instance()
|
|
||||||
}
|
}
|
||||||
|
// Cells fall back to the default drill-in button (labeled with the value via `identifier`); the leaf page shows the rich `NumberInput`.
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0))])]
|
vec![LayoutGroup::row(vec![
|
||||||
|
NumberInput::new(Some(*self as f64)).disabled(true).max_width(220).display_decimal_places(20).widget_instance(),
|
||||||
|
])]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -614,14 +633,14 @@ impl TableRowLayout for u64 {
|
||||||
"Number (u64)"
|
"Number (u64)"
|
||||||
}
|
}
|
||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
"Number (u64)".to_string()
|
format!("{self}")
|
||||||
}
|
}
|
||||||
|
// Cells fall back to the default drill-in button (labeled with the value via `identifier`); the leaf page shows the rich `NumberInput`.
|
||||||
// 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.
|
// 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<LayoutGroup> {
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0))])]
|
vec![LayoutGroup::row(vec![
|
||||||
|
NumberInput::new(Some(*self as f64)).disabled(true).max_width(220).display_decimal_places(20).widget_instance(),
|
||||||
|
])]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -632,11 +651,11 @@ impl TableRowLayout for bool {
|
||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
"Bool".to_string()
|
"Bool".to_string()
|
||||||
}
|
}
|
||||||
fn cell_widget(&self, _target: PathStep) -> WidgetInstance {
|
fn cell_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
||||||
TextLabel::new(self.to_string()).narrow(true).widget_instance()
|
TextLabel::new(self.to_string()).narrow(true).widget_instance()
|
||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0))])]
|
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0), _data)])]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -653,9 +672,7 @@ impl TableRowLayout for String {
|
||||||
format!("\"{}\"", first_line)
|
format!("\"{}\"", first_line)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn cell_widget(&self, _target: PathStep) -> WidgetInstance {
|
// Cells fall back to the default drill-in button (labeled with the truncated quoted preview via `identifier`); the leaf page shows the full multi-line text in a `TextAreaInput`.
|
||||||
TextLabel::new(self.identifier()).narrow(true).widget_instance()
|
|
||||||
}
|
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
vec![LayoutGroup::row(vec![TextAreaInput::new(self.to_string()).monospace(true).disabled(true).widget_instance()])]
|
vec![LayoutGroup::row(vec![TextAreaInput::new(self.to_string()).monospace(true).disabled(true).widget_instance()])]
|
||||||
}
|
}
|
||||||
|
|
@ -668,11 +685,11 @@ impl TableRowLayout for Option<f64> {
|
||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
"Option<f64>".to_string()
|
"Option<f64>".to_string()
|
||||||
}
|
}
|
||||||
fn cell_widget(&self, _target: PathStep) -> WidgetInstance {
|
fn cell_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
||||||
TextLabel::new(format!("{self:?}")).narrow(true).widget_instance()
|
TextLabel::new(format!("{self:?}")).narrow(true).widget_instance()
|
||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0))])]
|
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0), _data)])]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -683,11 +700,11 @@ impl TableRowLayout for DVec2 {
|
||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
"Vec2".to_string()
|
"Vec2".to_string()
|
||||||
}
|
}
|
||||||
fn cell_widget(&self, _target: PathStep) -> WidgetInstance {
|
fn cell_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
||||||
TextLabel::new(format_dvec2(*self)).narrow(true).widget_instance()
|
TextLabel::new(format_dvec2(*self)).narrow(true).widget_instance()
|
||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0))])]
|
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0), _data)])]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -698,11 +715,11 @@ impl TableRowLayout for Vec2 {
|
||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
"Vec2".to_string()
|
"Vec2".to_string()
|
||||||
}
|
}
|
||||||
fn cell_widget(&self, _target: PathStep) -> WidgetInstance {
|
fn cell_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
||||||
TextLabel::new(format_dvec2(DVec2::new(self.x as f64, self.y as f64))).narrow(true).widget_instance()
|
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<LayoutGroup> {
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0))])]
|
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0), _data)])]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -713,11 +730,11 @@ impl TableRowLayout for DAffine2 {
|
||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
"Transform".to_string()
|
"Transform".to_string()
|
||||||
}
|
}
|
||||||
fn cell_widget(&self, _target: PathStep) -> WidgetInstance {
|
fn cell_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
||||||
TextLabel::new(format_transform_matrix(*self)).narrow(true).widget_instance()
|
TextLabel::new(format_transform_matrix(*self)).narrow(true).widget_instance()
|
||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0))])]
|
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0), _data)])]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -728,12 +745,12 @@ impl TableRowLayout for Affine2 {
|
||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
"Transform".to_string()
|
"Transform".to_string()
|
||||||
}
|
}
|
||||||
fn cell_widget(&self, _target: PathStep) -> WidgetInstance {
|
fn cell_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
||||||
let matrix = DAffine2::from_cols_array(&self.to_cols_array().map(|x| x as f64));
|
let matrix = DAffine2::from_cols_array(&self.to_cols_array().map(|x| x as f64));
|
||||||
TextLabel::new(format_transform_matrix(matrix)).narrow(true).widget_instance()
|
TextLabel::new(format_transform_matrix(matrix)).narrow(true).widget_instance()
|
||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0))])]
|
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0), _data)])]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -744,11 +761,21 @@ impl TableRowLayout for AlphaBlending {
|
||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
format_alpha_blending(*self)
|
format_alpha_blending(*self)
|
||||||
}
|
}
|
||||||
fn cell_widget(&self, _target: PathStep) -> WidgetInstance {
|
fn cell_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
||||||
TextLabel::new(format_alpha_blending(*self)).narrow(true).widget_instance()
|
TextLabel::new(format_alpha_blending(*self)).narrow(true).widget_instance()
|
||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0))])]
|
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0), _data)])]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves the cell/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 {
|
||||||
|
if network_interface.node_metadata(&node_id, network_path).is_some() {
|
||||||
|
network_interface.display_name(&node_id, network_path)
|
||||||
|
} else {
|
||||||
|
format!("Node {node_id}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -759,41 +786,108 @@ impl TableRowLayout for NodeId {
|
||||||
fn identifier(&self) -> String {
|
fn identifier(&self) -> String {
|
||||||
format!("Node {self}")
|
format!("Node {self}")
|
||||||
}
|
}
|
||||||
fn cell_widget(&self, _target: PathStep) -> WidgetInstance {
|
// Override so the breadcrumb uses the same resolved display name as the cell button, instead of the bare-ID fallback `identifier()` returns.
|
||||||
|
fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
|
data.breadcrumbs.push(node_id_display_label(*self, data.network_interface, &data.node_lookup_network_path));
|
||||||
|
self.element_page(data)
|
||||||
|
}
|
||||||
|
// Cell label resolves the node's display name via the network interface so the button reads as the name shown
|
||||||
|
// in the Node Graph / Layers panels. The lookup uses `data.node_lookup_network_path` (set by the enclosing
|
||||||
|
// `Table<NodeId>` if rendering a path) so the resolution succeeds at any nesting depth. The button's icon
|
||||||
|
// signals layer-vs-node kind. Falls back to "Node {id}" with no icon if the lookup misses.
|
||||||
|
fn cell_widget(&self, target: PathStep, data: &LayoutData) -> WidgetInstance {
|
||||||
|
let label = node_id_display_label(*self, data.network_interface, &data.node_lookup_network_path);
|
||||||
|
let mut button = TextButton::new(label)
|
||||||
|
.on_update(move |_| DataPanelMessage::PushToElementPath { step: target.clone() }.into())
|
||||||
|
.narrow(true);
|
||||||
|
if data.network_interface.node_metadata(self, &data.node_lookup_network_path).is_some() {
|
||||||
|
let icon = if data.network_interface.is_layer(self, &data.node_lookup_network_path) { "Layer" } else { "Node" };
|
||||||
|
button = button.icon(icon);
|
||||||
|
}
|
||||||
|
button.widget_instance()
|
||||||
|
}
|
||||||
|
// The leaf page shows the node's kind, name (editable), lock/visibility toggles, and a "Select Layer/Node" action button.
|
||||||
|
fn element_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
let node_id = *self;
|
let node_id = *self;
|
||||||
TextButton::new("Go to Node")
|
let network_path = data.node_lookup_network_path.clone();
|
||||||
.tooltip_description("Click to select the node with this ID in the graph.")
|
let known = data.network_interface.node_metadata(&node_id, &network_path).is_some();
|
||||||
.on_update(move |_| NodeGraphMessage::SelectedNodesSet { nodes: vec![node_id] }.into())
|
let is_layer = known && data.network_interface.is_layer(&node_id, &network_path);
|
||||||
.narrow(true)
|
let name = if known {
|
||||||
|
data.network_interface.display_name(&node_id, &network_path)
|
||||||
|
} else {
|
||||||
|
"(node not found)".to_string()
|
||||||
|
};
|
||||||
|
let kind_widget = if known {
|
||||||
|
IconLabel::new(if is_layer { "Layer" } else { "Node" }).widget_instance()
|
||||||
|
} else {
|
||||||
|
TextLabel::new("-").widget_instance()
|
||||||
|
};
|
||||||
|
let name_widget = if known {
|
||||||
|
let path_for_rename = network_path.clone();
|
||||||
|
TextInput::new(name)
|
||||||
|
.tooltip_description(if is_layer { "Name of this layer." } else { "Name of this node." })
|
||||||
|
.on_update(move |text_input| {
|
||||||
|
NodeGraphMessage::SetDisplayName {
|
||||||
|
node_id,
|
||||||
|
network_path: path_for_rename.clone(),
|
||||||
|
alias: text_input.value.clone(),
|
||||||
|
skip_adding_history_step: false,
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
})
|
||||||
|
.max_width(200)
|
||||||
.widget_instance()
|
.widget_instance()
|
||||||
|
} else {
|
||||||
|
TextLabel::new(name).widget_instance()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut header = vec![kind_widget, Separator::new(SeparatorStyle::Related).widget_instance(), name_widget];
|
||||||
|
|
||||||
|
if known {
|
||||||
|
let is_locked = data.network_interface.is_locked(&node_id, &network_path);
|
||||||
|
let is_visible = data.network_interface.is_visible(&node_id, &network_path);
|
||||||
|
|
||||||
|
let path_for_lock = network_path.clone();
|
||||||
|
let path_for_visibility = network_path.clone();
|
||||||
|
|
||||||
|
header.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());
|
||||||
|
header.push(
|
||||||
|
IconButton::new(if is_locked { "PadlockLocked" } else { "PadlockUnlocked" }, 24)
|
||||||
|
.hover_icon(if is_locked { "PadlockUnlocked" } else { "PadlockLocked" })
|
||||||
|
.tooltip_label(if is_locked { "Unlock" } else { "Lock" })
|
||||||
|
.on_update(move |_| {
|
||||||
|
NodeGraphMessage::ToggleLocked {
|
||||||
|
node_id,
|
||||||
|
network_path: path_for_lock.clone(),
|
||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
.into()
|
||||||
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0))])]
|
})
|
||||||
|
.widget_instance(),
|
||||||
|
);
|
||||||
|
header.push(
|
||||||
|
IconButton::new(if is_visible { "EyeVisible" } else { "EyeHidden" }, 24)
|
||||||
|
.hover_icon(if is_visible { "EyeHide" } else { "EyeShow" })
|
||||||
|
.tooltip_label(if is_visible { "Hide" } else { "Show" })
|
||||||
|
.on_update(move |_| {
|
||||||
|
NodeGraphMessage::ToggleVisibility {
|
||||||
|
node_id,
|
||||||
|
network_path: path_for_visibility.clone(),
|
||||||
}
|
}
|
||||||
|
.into()
|
||||||
|
})
|
||||||
|
.widget_instance(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TableRowLayout for Option<NodeId> {
|
header.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());
|
||||||
fn type_name() -> &'static str {
|
header.push(
|
||||||
"NodeId"
|
TextButton::new(if is_layer { "Select Layer" } else { "Select Node" })
|
||||||
}
|
.tooltip_description(if is_layer { "Click to select this layer." } else { "Click to select this node." })
|
||||||
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())
|
.on_update(move |_| NodeGraphMessage::SelectedNodesSet { nodes: vec![node_id] }.into())
|
||||||
.narrow(true)
|
|
||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
None => TextLabel::new("-").narrow(true).widget_instance(),
|
);
|
||||||
}
|
|
||||||
}
|
vec![LayoutGroup::row(header)]
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
|
||||||
vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0))])]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -817,7 +911,6 @@ macro_rules! known_table_row_types {
|
||||||
GradientStops,
|
GradientStops,
|
||||||
Color,
|
Color,
|
||||||
NodeId,
|
NodeId,
|
||||||
Option<NodeId>,
|
|
||||||
AlphaBlending,
|
AlphaBlending,
|
||||||
DAffine2,
|
DAffine2,
|
||||||
DVec2,
|
DVec2,
|
||||||
|
|
@ -843,12 +936,12 @@ macro_rules! known_table_row_types {
|
||||||
/// Delegates to [`TableRowLayout::cell_widget`] so the same widget code is shared between
|
/// 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
|
/// element-column rendering and attribute-column rendering. Returns `None` for unrecognized types so the
|
||||||
/// caller can fall back to a debug-formatted [`TextLabel`].
|
/// caller can fall back to a debug-formatted [`TextLabel`].
|
||||||
fn dispatch_cell_widget(any: &dyn Any, target: PathStep) -> Option<WidgetInstance> {
|
fn dispatch_cell_widget(any: &dyn Any, target: PathStep, data: &LayoutData) -> Option<WidgetInstance> {
|
||||||
macro_rules! check {
|
macro_rules! check {
|
||||||
( $($ty:ty),* $(,)? ) => {
|
( $($ty:ty),* $(,)? ) => {
|
||||||
$(
|
$(
|
||||||
if let Some(value) = any.downcast_ref::<$ty>() {
|
if let Some(value) = any.downcast_ref::<$ty>() {
|
||||||
return Some(value.cell_widget(target));
|
return Some(value.cell_widget(target, data));
|
||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
};
|
};
|
||||||
|
|
@ -857,10 +950,54 @@ fn dispatch_cell_widget(any: &dyn Any, target: PathStep) -> Option<WidgetInstanc
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Renders a `Table<NodeId>` as a path: the standard table view, but each row's `NodeId` cell is resolved
|
||||||
|
/// against the network path made up of all preceding rows. So for a path `[outer, middle, leaf]`, row 0
|
||||||
|
/// resolves at root, row 1 resolves at `[outer]`, and row 2 resolves at `[outer, middle]` — letting deeply
|
||||||
|
/// nested layers display each step's correct name. Drilling into a row drops into that node's leaf page
|
||||||
|
/// using the same prefix as `network_path`.
|
||||||
|
fn table_node_id_path_layout_with_breadcrumb(path: &Table<NodeId>, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
|
data.breadcrumbs.push(path.identifier());
|
||||||
|
|
||||||
|
if let Some(step) = data.desired_path.get(data.current_depth).cloned() {
|
||||||
|
if let PathStep::Element(index) = step
|
||||||
|
&& let Some(node_id) = path.element(index)
|
||||||
|
{
|
||||||
|
let prefix: Vec<NodeId> = path.iter_element_values().take(index).copied().collect();
|
||||||
|
let saved = std::mem::replace(&mut data.node_lookup_network_path, prefix);
|
||||||
|
data.current_depth += 1;
|
||||||
|
let result = node_id.layout_with_breadcrumb(data);
|
||||||
|
data.current_depth -= 1;
|
||||||
|
data.node_lookup_network_path = saved;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
warn!("Desired path truncated");
|
||||||
|
data.desired_path.truncate(data.current_depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut rows = (0..path.len())
|
||||||
|
.map(|index| {
|
||||||
|
let node_id = path.element(index).unwrap();
|
||||||
|
let prefix: Vec<NodeId> = path.iter_element_values().take(index).copied().collect();
|
||||||
|
let saved = std::mem::replace(&mut data.node_lookup_network_path, prefix);
|
||||||
|
let widget = node_id.cell_widget(PathStep::Element(index), data);
|
||||||
|
data.node_lookup_network_path = saved;
|
||||||
|
vec![TextLabel::new(format!("{index}")).narrow(true).widget_instance(), widget]
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
rows.insert(0, column_headings(&["", "element"]));
|
||||||
|
|
||||||
|
vec![LayoutGroup::table(rows, false)]
|
||||||
|
}
|
||||||
|
|
||||||
/// Type-dispatched recursion into an attribute value for the data panel breadcrumb navigation.
|
/// Type-dispatched recursion into an attribute value for the data panel breadcrumb navigation.
|
||||||
/// Mirrors [`dispatch_cell_widget`] but routes to [`TableRowLayout::layout_with_breadcrumb`].
|
/// Mirrors [`dispatch_cell_widget`] but routes to [`TableRowLayout::layout_with_breadcrumb`].
|
||||||
/// Returns `None` for unrecognized types.
|
/// Returns `None` for unrecognized types.
|
||||||
fn drilldown_attribute_layout(any: &dyn Any, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> {
|
fn drilldown_attribute_layout(any: &dyn Any, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> {
|
||||||
|
// `Table<NodeId>` is interpreted as a path (e.g. the `editor:layer` attribute), so each row's NodeId cell
|
||||||
|
// resolves against the prefix made up of preceding rows. Handled before the generic `Table<T>` blanket impl.
|
||||||
|
if let Some(path) = any.downcast_ref::<Table<NodeId>>() {
|
||||||
|
return Some(table_node_id_path_layout_with_breadcrumb(path, data));
|
||||||
|
}
|
||||||
macro_rules! check {
|
macro_rules! check {
|
||||||
( $($ty:ty),* $(,)? ) => {
|
( $($ty:ty),* $(,)? ) => {
|
||||||
$(
|
$(
|
||||||
|
|
|
||||||
|
|
@ -699,6 +699,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||||
if let Some(name) = name {
|
if let Some(name) = name {
|
||||||
responses.add(NodeGraphMessage::SetDisplayName {
|
responses.add(NodeGraphMessage::SetDisplayName {
|
||||||
node_id: layer.to_node(),
|
node_id: layer.to_node(),
|
||||||
|
network_path: Vec::new(),
|
||||||
alias: name,
|
alias: name,
|
||||||
skip_adding_history_step: false,
|
skip_adding_history_step: false,
|
||||||
});
|
});
|
||||||
|
|
@ -756,6 +757,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||||
if let Some(name) = name {
|
if let Some(name) = name {
|
||||||
responses.add(NodeGraphMessage::SetDisplayName {
|
responses.add(NodeGraphMessage::SetDisplayName {
|
||||||
node_id: layer.to_node(),
|
node_id: layer.to_node(),
|
||||||
|
network_path: Vec::new(),
|
||||||
alias: name,
|
alias: name,
|
||||||
skip_adding_history_step: false,
|
skip_adding_history_step: false,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -197,10 +197,12 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
|
||||||
|
|
||||||
responses.add(NodeGraphMessage::SetDisplayNameImpl {
|
responses.add(NodeGraphMessage::SetDisplayNameImpl {
|
||||||
node_id: id,
|
node_id: id,
|
||||||
|
network_path: Vec::new(),
|
||||||
alias: layer_alias.to_string(),
|
alias: layer_alias.to_string(),
|
||||||
});
|
});
|
||||||
responses.add(NodeGraphMessage::SetDisplayNameImpl {
|
responses.add(NodeGraphMessage::SetDisplayNameImpl {
|
||||||
node_id: control_path_id,
|
node_id: control_path_id,
|
||||||
|
network_path: Vec::new(),
|
||||||
alias: path_alias.to_string(),
|
alias: path_alias.to_string(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -245,6 +247,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
|
||||||
network_interface.move_layer_to_stack(layer, parent, insert_index, &[]);
|
network_interface.move_layer_to_stack(layer, parent, insert_index, &[]);
|
||||||
responses.add(NodeGraphMessage::SetDisplayNameImpl {
|
responses.add(NodeGraphMessage::SetDisplayNameImpl {
|
||||||
node_id: id,
|
node_id: id,
|
||||||
|
network_path: Vec::new(),
|
||||||
alias: "Boolean Operation".to_string(),
|
alias: "Boolean Operation".to_string(),
|
||||||
});
|
});
|
||||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
|
|
@ -343,6 +346,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
|
||||||
|
|
||||||
responses.add(NodeGraphMessage::SetDisplayName {
|
responses.add(NodeGraphMessage::SetDisplayName {
|
||||||
node_id,
|
node_id,
|
||||||
|
network_path: Vec::new(),
|
||||||
alias: network_interface.display_name(&artboard.to_node(), &[]),
|
alias: network_interface.display_name(&artboard.to_node(), &[]),
|
||||||
skip_adding_history_step: true,
|
skip_adding_history_step: true,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -236,7 +236,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
||||||
// Derive the parent layer's NodeId from the document path
|
// Derive the parent layer's NodeId from the document path
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)],
|
inputs: vec![NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::parent_layer::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(graphic::path_of_subgraph::IDENTIFIER),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
// Stamp each row of the content with the parent layer's NodeId via the `editor:layer` attribute,
|
// Stamp each row of the content with the parent layer's NodeId via the `editor:layer` attribute,
|
||||||
|
|
@ -299,7 +299,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
// 2: parent_layer
|
// 2: path_of_subgraph
|
||||||
DocumentNodeMetadata {
|
DocumentNodeMetadata {
|
||||||
persistent_metadata: DocumentNodePersistentMetadata {
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, 1)),
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, 1)),
|
||||||
|
|
@ -371,7 +371,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
||||||
// Derive the parent layer's NodeId from the document path
|
// Derive the parent layer's NodeId from the document path
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)],
|
inputs: vec![NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::parent_layer::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(graphic::path_of_subgraph::IDENTIFIER),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
// Stamp each row of the content with the parent layer's NodeId via the `editor:layer` attribute,
|
// Stamp each row of the content with the parent layer's NodeId via the `editor:layer` attribute,
|
||||||
|
|
@ -461,7 +461,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
// 1: parent_layer
|
// 1: path_of_subgraph
|
||||||
DocumentNodeMetadata {
|
DocumentNodeMetadata {
|
||||||
persistent_metadata: DocumentNodePersistentMetadata {
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, 3)),
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, 3)),
|
||||||
|
|
|
||||||
|
|
@ -157,11 +157,15 @@ pub enum NodeGraphMessage {
|
||||||
},
|
},
|
||||||
SetDisplayName {
|
SetDisplayName {
|
||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
|
/// The path to the network containing `node_id`. Empty for nodes at the root document network.
|
||||||
|
/// Lets the rename target a node at any nesting depth, independent of the current selection network.
|
||||||
|
network_path: Vec<NodeId>,
|
||||||
alias: String,
|
alias: String,
|
||||||
skip_adding_history_step: bool,
|
skip_adding_history_step: bool,
|
||||||
},
|
},
|
||||||
SetDisplayNameImpl {
|
SetDisplayNameImpl {
|
||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
|
network_path: Vec<NodeId>,
|
||||||
alias: String,
|
alias: String,
|
||||||
},
|
},
|
||||||
SetToNodeOrLayer {
|
SetToNodeOrLayer {
|
||||||
|
|
@ -199,15 +203,22 @@ pub enum NodeGraphMessage {
|
||||||
ToggleSelectedLocked,
|
ToggleSelectedLocked,
|
||||||
ToggleLocked {
|
ToggleLocked {
|
||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
|
/// The path to the network containing `node_id`. Empty for nodes at the root document network.
|
||||||
|
/// Lets the toggle target a node at any nesting depth, independent of the current selection network.
|
||||||
|
network_path: Vec<NodeId>,
|
||||||
},
|
},
|
||||||
SetLocked {
|
SetLocked {
|
||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
|
network_path: Vec<NodeId>,
|
||||||
locked: bool,
|
locked: bool,
|
||||||
},
|
},
|
||||||
ToggleSelectedIsPinned,
|
ToggleSelectedIsPinned,
|
||||||
ToggleSelectedVisibility,
|
ToggleSelectedVisibility,
|
||||||
ToggleVisibility {
|
ToggleVisibility {
|
||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
|
/// The path to the network containing `node_id`. Empty for nodes at the root document network.
|
||||||
|
/// Lets the toggle target a node at any nesting depth, independent of the current selection network.
|
||||||
|
network_path: Vec<NodeId>,
|
||||||
},
|
},
|
||||||
SetPinned {
|
SetPinned {
|
||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
|
|
@ -215,10 +226,12 @@ pub enum NodeGraphMessage {
|
||||||
},
|
},
|
||||||
SetVisibility {
|
SetVisibility {
|
||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
|
network_path: Vec<NodeId>,
|
||||||
visible: bool,
|
visible: bool,
|
||||||
},
|
},
|
||||||
SetLockedOrVisibilitySideEffects {
|
SetLockedOrVisibilitySideEffects {
|
||||||
node_ids: Vec<NodeId>,
|
node_ids: Vec<NodeId>,
|
||||||
|
network_path: Vec<NodeId>,
|
||||||
},
|
},
|
||||||
UpdateEdges,
|
UpdateEdges,
|
||||||
UpdateBoxSelection,
|
UpdateBoxSelection,
|
||||||
|
|
|
||||||
|
|
@ -659,6 +659,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||||
});
|
});
|
||||||
responses.add(NodeGraphMessage::SetDisplayNameImpl {
|
responses.add(NodeGraphMessage::SetDisplayNameImpl {
|
||||||
node_id: encapsulating_node_id,
|
node_id: encapsulating_node_id,
|
||||||
|
network_path: selection_network_path.to_vec(),
|
||||||
alias: "Untitled Node".to_string(),
|
alias: "Untitled Node".to_string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -909,13 +910,19 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||||
|
|
||||||
// Toggle visibility of clicked node and return
|
// Toggle visibility of clicked node and return
|
||||||
if let Some(clicked_visibility) = network_interface.layer_click_target_from_click(click, network_interface::LayerClickTargetTypes::Visibility, selection_network_path) {
|
if let Some(clicked_visibility) = network_interface.layer_click_target_from_click(click, network_interface::LayerClickTargetTypes::Visibility, selection_network_path) {
|
||||||
responses.add(NodeGraphMessage::ToggleVisibility { node_id: clicked_visibility });
|
responses.add(NodeGraphMessage::ToggleVisibility {
|
||||||
|
node_id: clicked_visibility,
|
||||||
|
network_path: selection_network_path.to_vec(),
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle lock of clicked node and return
|
// Toggle lock of clicked node and return
|
||||||
if let Some(clicked_lock) = network_interface.layer_click_target_from_click(click, network_interface::LayerClickTargetTypes::Lock, selection_network_path) {
|
if let Some(clicked_lock) = network_interface.layer_click_target_from_click(click, network_interface::LayerClickTargetTypes::Lock, selection_network_path) {
|
||||||
responses.add(NodeGraphMessage::ToggleLocked { node_id: clicked_lock });
|
responses.add(NodeGraphMessage::ToggleLocked {
|
||||||
|
node_id: clicked_lock,
|
||||||
|
network_path: selection_network_path.to_vec(),
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1816,13 +1823,14 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||||
}
|
}
|
||||||
NodeGraphMessage::SetDisplayName {
|
NodeGraphMessage::SetDisplayName {
|
||||||
node_id,
|
node_id,
|
||||||
|
network_path,
|
||||||
alias,
|
alias,
|
||||||
skip_adding_history_step,
|
skip_adding_history_step,
|
||||||
} => {
|
} => {
|
||||||
if !skip_adding_history_step {
|
if !skip_adding_history_step {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
}
|
}
|
||||||
responses.add(NodeGraphMessage::SetDisplayNameImpl { node_id, alias });
|
responses.add(NodeGraphMessage::SetDisplayNameImpl { node_id, network_path, alias });
|
||||||
if !skip_adding_history_step {
|
if !skip_adding_history_step {
|
||||||
// Does not add a history step if the name was not changed
|
// Does not add a history step if the name was not changed
|
||||||
responses.add(DocumentMessage::EndTransaction);
|
responses.add(DocumentMessage::EndTransaction);
|
||||||
|
|
@ -1831,9 +1839,10 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||||
responses.add(DocumentMessage::RenderScrollbars);
|
responses.add(DocumentMessage::RenderScrollbars);
|
||||||
responses.add(NodeGraphMessage::SendGraph);
|
responses.add(NodeGraphMessage::SendGraph);
|
||||||
responses.add(OverlaysMessage::Draw); // Redraw overlays to update artboard names
|
responses.add(OverlaysMessage::Draw); // Redraw overlays to update artboard names
|
||||||
|
responses.add(DataPanelMessage::Refresh);
|
||||||
}
|
}
|
||||||
NodeGraphMessage::SetDisplayNameImpl { node_id, alias } => {
|
NodeGraphMessage::SetDisplayNameImpl { node_id, network_path, alias } => {
|
||||||
network_interface.set_display_name(&node_id, alias, selection_network_path);
|
network_interface.set_display_name(&node_id, alias, &network_path);
|
||||||
}
|
}
|
||||||
NodeGraphMessage::SetImportExportName { name, index } => {
|
NodeGraphMessage::SetImportExportName { name, index } => {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
|
@ -1872,25 +1881,34 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||||
responses.add(DocumentMessage::AddTransaction);
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
|
|
||||||
for node_id in &node_ids {
|
for node_id in &node_ids {
|
||||||
responses.add(NodeGraphMessage::SetLocked { node_id: *node_id, locked });
|
responses.add(NodeGraphMessage::SetLocked {
|
||||||
|
node_id: *node_id,
|
||||||
|
network_path: selection_network_path.to_vec(),
|
||||||
|
locked,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids })
|
responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects {
|
||||||
|
node_ids,
|
||||||
|
network_path: selection_network_path.to_vec(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
NodeGraphMessage::ToggleLocked { node_id } => {
|
NodeGraphMessage::ToggleLocked { node_id, network_path } => {
|
||||||
let Some(node_metadata) = network_interface.document_network_metadata().persistent_metadata.node_metadata.get(&node_id) else {
|
let locked = !network_interface.is_locked(&node_id, &network_path);
|
||||||
log::error!("Cannot get node {node_id:?} in NodeGraphMessage::ToggleLocked");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let locked = !node_metadata.persistent_metadata.locked;
|
|
||||||
|
|
||||||
responses.add(DocumentMessage::AddTransaction);
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
responses.add(NodeGraphMessage::SetLocked { node_id, locked });
|
responses.add(NodeGraphMessage::SetLocked {
|
||||||
responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids: vec![node_id] })
|
node_id,
|
||||||
|
network_path: network_path.clone(),
|
||||||
|
locked,
|
||||||
|
});
|
||||||
|
responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects {
|
||||||
|
node_ids: vec![node_id],
|
||||||
|
network_path,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
NodeGraphMessage::SetLocked { node_id, locked } => {
|
NodeGraphMessage::SetLocked { node_id, network_path, locked } => {
|
||||||
network_interface.set_locked(&node_id, selection_network_path, locked);
|
network_interface.set_locked(&node_id, &network_path, locked);
|
||||||
}
|
}
|
||||||
NodeGraphMessage::ToggleSelectedIsPinned => {
|
NodeGraphMessage::ToggleSelectedIsPinned => {
|
||||||
let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {
|
let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {
|
||||||
|
|
@ -1906,7 +1924,10 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||||
for node_id in &node_ids {
|
for node_id in &node_ids {
|
||||||
responses.add(NodeGraphMessage::SetPinned { node_id: *node_id, pinned });
|
responses.add(NodeGraphMessage::SetPinned { node_id: *node_id, pinned });
|
||||||
}
|
}
|
||||||
responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids });
|
responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects {
|
||||||
|
node_ids,
|
||||||
|
network_path: selection_network_path.to_vec(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
NodeGraphMessage::ToggleSelectedVisibility => {
|
NodeGraphMessage::ToggleSelectedVisibility => {
|
||||||
let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {
|
let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {
|
||||||
|
|
@ -1920,31 +1941,46 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||||
|
|
||||||
responses.add(DocumentMessage::AddTransaction);
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
for node_id in &node_ids {
|
for node_id in &node_ids {
|
||||||
responses.add(NodeGraphMessage::SetVisibility { node_id: *node_id, visible });
|
responses.add(NodeGraphMessage::SetVisibility {
|
||||||
|
node_id: *node_id,
|
||||||
|
network_path: selection_network_path.to_vec(),
|
||||||
|
visible,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids });
|
responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects {
|
||||||
|
node_ids,
|
||||||
|
network_path: selection_network_path.to_vec(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
NodeGraphMessage::ToggleVisibility { node_id } => {
|
NodeGraphMessage::ToggleVisibility { node_id, network_path } => {
|
||||||
let visible = !network_interface.is_visible(&node_id, selection_network_path);
|
let visible = !network_interface.is_visible(&node_id, &network_path);
|
||||||
|
|
||||||
responses.add(DocumentMessage::AddTransaction);
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
responses.add(NodeGraphMessage::SetVisibility { node_id, visible });
|
responses.add(NodeGraphMessage::SetVisibility {
|
||||||
responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids: vec![node_id] });
|
node_id,
|
||||||
|
network_path: network_path.clone(),
|
||||||
|
visible,
|
||||||
|
});
|
||||||
|
responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects {
|
||||||
|
node_ids: vec![node_id],
|
||||||
|
network_path,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
NodeGraphMessage::SetPinned { node_id, pinned } => {
|
NodeGraphMessage::SetPinned { node_id, pinned } => {
|
||||||
network_interface.set_pinned(&node_id, selection_network_path, pinned);
|
network_interface.set_pinned(&node_id, selection_network_path, pinned);
|
||||||
}
|
}
|
||||||
NodeGraphMessage::SetVisibility { node_id, visible } => {
|
NodeGraphMessage::SetVisibility { node_id, network_path, visible } => {
|
||||||
network_interface.set_visibility(&node_id, selection_network_path, visible);
|
network_interface.set_visibility(&node_id, &network_path, visible);
|
||||||
}
|
}
|
||||||
NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids } => {
|
NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids, network_path } => {
|
||||||
if node_ids.iter().any(|node_id| network_interface.connected_to_output(node_id, selection_network_path)) {
|
if node_ids.iter().any(|node_id| network_interface.connected_to_output(node_id, &network_path)) {
|
||||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
}
|
}
|
||||||
responses.add(NodeGraphMessage::UpdateActionButtons);
|
responses.add(NodeGraphMessage::UpdateActionButtons);
|
||||||
responses.add(NodeGraphMessage::SendGraph);
|
responses.add(NodeGraphMessage::SendGraph);
|
||||||
|
|
||||||
responses.add(PropertiesPanelMessage::Refresh);
|
responses.add(PropertiesPanelMessage::Refresh);
|
||||||
|
responses.add(DataPanelMessage::Refresh);
|
||||||
}
|
}
|
||||||
NodeGraphMessage::UpdateBoxSelection => {
|
NodeGraphMessage::UpdateBoxSelection => {
|
||||||
if let Some((box_selection_start, _)) = self.box_selection_start {
|
if let Some((box_selection_start, _)) = self.box_selection_start {
|
||||||
|
|
@ -2397,6 +2433,7 @@ impl NodeGraphMessageHandler {
|
||||||
let mut properties = Vec::new();
|
let mut properties = Vec::new();
|
||||||
|
|
||||||
if let [node_id] = *nodes.as_slice() {
|
if let [node_id] = *nodes.as_slice() {
|
||||||
|
let network_path = context.selection_network_path.to_vec();
|
||||||
properties.push(LayoutGroup::row(vec![
|
properties.push(LayoutGroup::row(vec![
|
||||||
Separator::new(SeparatorStyle::Related).widget_instance(),
|
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||||
IconLabel::new("Node").tooltip_description("Name of the selected node.").widget_instance(),
|
IconLabel::new("Node").tooltip_description("Name of the selected node.").widget_instance(),
|
||||||
|
|
@ -2406,6 +2443,7 @@ impl NodeGraphMessageHandler {
|
||||||
.on_update(move |text_input| {
|
.on_update(move |text_input| {
|
||||||
NodeGraphMessage::SetDisplayName {
|
NodeGraphMessage::SetDisplayName {
|
||||||
node_id,
|
node_id,
|
||||||
|
network_path: network_path.clone(),
|
||||||
alias: text_input.value.clone(),
|
alias: text_input.value.clone(),
|
||||||
skip_adding_history_step: false,
|
skip_adding_history_step: false,
|
||||||
}
|
}
|
||||||
|
|
@ -2468,6 +2506,7 @@ impl NodeGraphMessageHandler {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let layer_network_path = context.selection_network_path.to_vec();
|
||||||
let mut layer_properties = vec![LayoutGroup::row(vec![
|
let mut layer_properties = vec![LayoutGroup::row(vec![
|
||||||
Separator::new(SeparatorStyle::Related).widget_instance(),
|
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||||
IconLabel::new("Layer").tooltip_description("Name of the selected layer.").widget_instance(),
|
IconLabel::new("Layer").tooltip_description("Name of the selected layer.").widget_instance(),
|
||||||
|
|
@ -2477,6 +2516,7 @@ impl NodeGraphMessageHandler {
|
||||||
.on_update(move |text_input| {
|
.on_update(move |text_input| {
|
||||||
NodeGraphMessage::SetDisplayName {
|
NodeGraphMessage::SetDisplayName {
|
||||||
node_id: layer,
|
node_id: layer,
|
||||||
|
network_path: layer_network_path.clone(),
|
||||||
alias: text_input.value.clone(),
|
alias: text_input.value.clone(),
|
||||||
skip_adding_history_step: false,
|
skip_adding_history_step: false,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1016,7 +1016,7 @@ pub fn document_migration_reset_node_definition(document_serialized_content: &st
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The `source_node_id` proto node was removed in favor of `parent_layer` + `write_attribute`.
|
// The `source_node_id` proto node was removed in favor of `path_of_subgraph` + `write_attribute`.
|
||||||
// Documents that still reference it inside their Merge or Artboard layer networks need those layer definitions
|
// Documents that still reference it inside their Merge or Artboard layer networks need those layer definitions
|
||||||
// reset to the current default so the new internal plumbing replaces the obsolete node.
|
// reset to the current default so the new internal plumbing replaces the obsolete node.
|
||||||
if document_serialized_content.contains("graphic_nodes::graphic::SourceNodeIdNode")
|
if document_serialized_content.contains("graphic_nodes::graphic::SourceNodeIdNode")
|
||||||
|
|
|
||||||
|
|
@ -775,6 +775,7 @@ impl EditorWrapper {
|
||||||
let layer = LayerNodeIdentifier::new_unchecked(NodeId(id));
|
let layer = LayerNodeIdentifier::new_unchecked(NodeId(id));
|
||||||
let message = NodeGraphMessage::SetDisplayName {
|
let message = NodeGraphMessage::SetDisplayName {
|
||||||
node_id: layer.to_node(),
|
node_id: layer.to_node(),
|
||||||
|
network_path: Vec::new(),
|
||||||
alias: name,
|
alias: name,
|
||||||
skip_adding_history_step: false,
|
skip_adding_history_step: false,
|
||||||
};
|
};
|
||||||
|
|
@ -912,7 +913,7 @@ impl EditorWrapper {
|
||||||
#[wasm_bindgen(js_name = toggleNodeVisibilityLayerPanel)]
|
#[wasm_bindgen(js_name = toggleNodeVisibilityLayerPanel)]
|
||||||
pub fn toggle_node_visibility_layer(&self, id: u64) {
|
pub fn toggle_node_visibility_layer(&self, id: u64) {
|
||||||
let node_id = NodeId(id);
|
let node_id = NodeId(id);
|
||||||
let message = NodeGraphMessage::ToggleVisibility { node_id };
|
let message = NodeGraphMessage::ToggleVisibility { node_id, network_path: Vec::new() };
|
||||||
self.dispatch(message);
|
self.dispatch(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -931,7 +932,10 @@ impl EditorWrapper {
|
||||||
/// Toggle lock state of a layer from the layer list
|
/// Toggle lock state of a layer from the layer list
|
||||||
#[wasm_bindgen(js_name = toggleLayerLock)]
|
#[wasm_bindgen(js_name = toggleLayerLock)]
|
||||||
pub fn toggle_layer_lock(&self, node_id: u64) {
|
pub fn toggle_layer_lock(&self, node_id: u64) {
|
||||||
let message = NodeGraphMessage::ToggleLocked { node_id: NodeId(node_id) };
|
let message = NodeGraphMessage::ToggleLocked {
|
||||||
|
node_id: NodeId(node_id),
|
||||||
|
network_path: Vec::new(),
|
||||||
|
};
|
||||||
self.dispatch(message);
|
self.dispatch(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,6 @@ 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::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Option<f64>]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Option<f64>]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Option<NodeId>]),
|
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<String>]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<String>]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<NodeId>]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<NodeId>]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<f64>]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<f64>]),
|
||||||
|
|
@ -173,7 +172,6 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Raster<GPU>>]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Raster<GPU>>]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<f64>]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<f64>]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<Color>]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<Color>]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<NodeId>]),
|
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Graphic]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Graphic]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => glam::f32::Vec2]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => glam::f32::Vec2]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => glam::f32::Affine2]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => glam::f32::Affine2]),
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,7 @@ fn flatten_graphic_table<T>(content: Table<Graphic>, extract_variant: fn(Graphic
|
||||||
|
|
||||||
fn flatten_recursive<T>(output: &mut Table<T>, current_graphic_table: Table<Graphic>, extract_variant: fn(Graphic) -> Option<Table<T>>) {
|
fn flatten_recursive<T>(output: &mut Table<T>, current_graphic_table: Table<Graphic>, extract_variant: fn(Graphic) -> Option<Table<T>>) {
|
||||||
for current_graphic_row in current_graphic_table.into_iter() {
|
for current_graphic_row in current_graphic_table.into_iter() {
|
||||||
let layer: Option<NodeId> = current_graphic_row.attribute_cloned_or_default("editor:layer");
|
let layer_path: Table<NodeId> = current_graphic_row.attribute_cloned_or_default("editor:layer");
|
||||||
let current_transform: DAffine2 = current_graphic_row.attribute_cloned_or_default("transform");
|
let current_transform: DAffine2 = current_graphic_row.attribute_cloned_or_default("transform");
|
||||||
let current_alpha_blending: AlphaBlending = current_graphic_row.attribute_cloned_or_default("alpha_blending");
|
let current_alpha_blending: AlphaBlending = current_graphic_row.attribute_cloned_or_default("alpha_blending");
|
||||||
|
|
||||||
|
|
@ -168,7 +168,7 @@ fn flatten_graphic_table<T>(content: Table<Graphic>, extract_variant: fn(Graphic
|
||||||
|
|
||||||
attributes.insert("transform", current_transform * row_transform);
|
attributes.insert("transform", current_transform * row_transform);
|
||||||
attributes.insert("alpha_blending", compose_alpha_blending(current_alpha_blending, row_alpha_blending));
|
attributes.insert("alpha_blending", compose_alpha_blending(current_alpha_blending, row_alpha_blending));
|
||||||
attributes.insert("editor:layer", layer);
|
attributes.insert("editor:layer", layer_path.clone());
|
||||||
|
|
||||||
output.push(TableRow::from_parts(element, attributes));
|
output.push(TableRow::from_parts(element, attributes));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -412,7 +412,8 @@ impl Render for Graphic {
|
||||||
metadata.upstream_footprints.insert(element_id, footprint);
|
metadata.upstream_footprints.insert(element_id, footprint);
|
||||||
// TODO: Find a way to handle more than the first row
|
// TODO: Find a way to handle more than the first row
|
||||||
if !table.is_empty() {
|
if !table.is_empty() {
|
||||||
let layer: Option<NodeId> = table.attribute_cloned_or_default("editor:layer", 0);
|
let layer_path: Table<NodeId> = table.attribute_cloned_or_default("editor:layer", 0);
|
||||||
|
let layer = layer_path.iter_element_values().next_back().copied();
|
||||||
let transform: DAffine2 = table.attribute_cloned_or_default("transform", 0);
|
let transform: DAffine2 = table.attribute_cloned_or_default("transform", 0);
|
||||||
|
|
||||||
metadata.first_element_source_id.insert(element_id, layer);
|
metadata.first_element_source_id.insert(element_id, layer);
|
||||||
|
|
@ -655,7 +656,8 @@ impl Render for Table<Artboard> {
|
||||||
|
|
||||||
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 index in 0..self.len() {
|
for index in 0..self.len() {
|
||||||
let layer: Option<NodeId> = self.attribute_cloned_or_default("editor:layer", index);
|
let layer_path: Table<NodeId> = self.attribute_cloned_or_default("editor:layer", index);
|
||||||
|
let layer = layer_path.iter_element_values().next_back().copied();
|
||||||
self.element(index).unwrap().collect_metadata(metadata, footprint, layer);
|
self.element(index).unwrap().collect_metadata(metadata, footprint, layer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -805,7 +807,8 @@ impl Render for Table<Graphic> {
|
||||||
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 index in 0..self.len() {
|
for index in 0..self.len() {
|
||||||
let row_transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
let row_transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||||
let layer: Option<NodeId> = self.attribute_cloned_or_default("editor:layer", index);
|
let layer_path: Table<NodeId> = self.attribute_cloned_or_default("editor:layer", index);
|
||||||
|
let layer = layer_path.iter_element_values().next_back().copied();
|
||||||
let element = self.element(index).unwrap();
|
let element = self.element(index).unwrap();
|
||||||
|
|
||||||
let mut footprint = footprint;
|
let mut footprint = footprint;
|
||||||
|
|
@ -860,9 +863,9 @@ impl Render for Table<Graphic> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {
|
fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {
|
||||||
let (elements, layers) = self.element_and_attribute_slices_mut::<Option<NodeId>>("editor:layer");
|
let (elements, layers) = self.element_and_attribute_slices_mut::<Table<NodeId>>("editor:layer");
|
||||||
for (element, layer) in elements.iter_mut().zip(layers.iter()) {
|
for (element, layer) in elements.iter_mut().zip(layers.iter()) {
|
||||||
element.new_ids_from_hash(*layer);
|
element.new_ids_from_hash(layer.iter_element_values().next_back().copied());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1327,7 +1330,8 @@ impl Render for Table<Vector> {
|
||||||
for index in 0..self.len() {
|
for index in 0..self.len() {
|
||||||
let Some(vector) = self.element(index) else { continue };
|
let Some(vector) = self.element(index) else { continue };
|
||||||
let transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
let transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||||
let layer: Option<NodeId> = self.attribute_cloned_or_default("editor:layer", index);
|
let layer_path: Table<NodeId> = self.attribute_cloned_or_default("editor:layer", index);
|
||||||
|
let layer = layer_path.iter_element_values().next_back().copied();
|
||||||
|
|
||||||
if let Some(element_id) = caller_element_id.or(layer) {
|
if let Some(element_id) = caller_element_id.or(layer) {
|
||||||
// When recovering element_id from the row's editor:layer tag (because the caller
|
// When recovering element_id from the row's editor:layer tag (because the caller
|
||||||
|
|
|
||||||
|
|
@ -314,7 +314,7 @@ async fn brush(
|
||||||
|
|
||||||
let transform: DAffine2 = actual_image.attribute_cloned_or_default("transform");
|
let transform: DAffine2 = actual_image.attribute_cloned_or_default("transform");
|
||||||
let alpha_blending: AlphaBlending = actual_image.attribute_cloned_or_default("alpha_blending");
|
let alpha_blending: AlphaBlending = actual_image.attribute_cloned_or_default("alpha_blending");
|
||||||
let layer: Option<NodeId> = actual_image.attribute_cloned_or_default("editor:layer");
|
let layer: Table<NodeId> = actual_image.attribute_cloned_or_default("editor:layer");
|
||||||
|
|
||||||
*image.element_mut(0).unwrap() = actual_image.into_element();
|
*image.element_mut(0).unwrap() = actual_image.into_element();
|
||||||
image.set_attribute("transform", 0, transform);
|
image.set_attribute("transform", 0, transform);
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ async fn context_modification<T>(
|
||||||
Context -> DAffine2,
|
Context -> DAffine2,
|
||||||
Context -> Footprint,
|
Context -> Footprint,
|
||||||
Context -> DVec2,
|
Context -> DVec2,
|
||||||
Context -> Option<NodeId>,
|
|
||||||
Context -> Table<String>,
|
Context -> Table<String>,
|
||||||
Context -> Table<NodeId>,
|
Context -> Table<NodeId>,
|
||||||
Context -> Table<f64>,
|
Context -> Table<f64>,
|
||||||
|
|
|
||||||
|
|
@ -209,14 +209,16 @@ where
|
||||||
result_table
|
result_table
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the NodeId of the user-facing parent layer node that encapsulates this sub-network.
|
/// Returns the path identifying the subgraph (network) that contains this proto node — i.e. the input `node_path`
|
||||||
/// Used as the value source for stamping the `editor:layer` attribute on each row of a layer's output,
|
/// with its own trailing entry dropped. The terminating element of the returned path is the document node whose
|
||||||
/// which lets editor tools (e.g. selection, click target routing) trace data back to its owning layer.
|
/// encapsulated network we live in, so the path doubles as a unique reference to that node at any nesting depth.
|
||||||
#[node_macro::node(category(""))]
|
/// Used as the value source for stamping the `editor:layer` attribute on each row of a layer's output, which lets
|
||||||
pub fn parent_layer(_: impl Ctx, node_path: Table<NodeId>) -> Option<NodeId> {
|
/// editor tools (e.g. selection, click target routing) trace data back to its owning layer regardless of whether
|
||||||
// Get the penultimate element of the node path, or None if the path is too short
|
/// the layer is at the root document network or nested inside a custom subgraph.
|
||||||
let index = node_path.len().wrapping_sub(2);
|
#[node_macro::node(name("Path of Subgraph"), category(""))]
|
||||||
node_path.element(index).copied()
|
pub fn path_of_subgraph(_: impl Ctx, node_path: Table<NodeId>) -> Table<NodeId> {
|
||||||
|
let len = node_path.len();
|
||||||
|
node_path.into_iter().take(len.saturating_sub(1)).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes a per-row attribute column on the input table. The value-producing input is evaluated once per row,
|
/// Writes a per-row attribute column on the input table. The value-producing input is evaluated once per row,
|
||||||
|
|
@ -241,13 +243,13 @@ async fn write_attribute<T: AnyHash + Clone + Send + Sync + core_types::CacheHas
|
||||||
name: String,
|
name: String,
|
||||||
/// The node that produces the per-row value. Called once per row with the row index in context.
|
/// The node that produces the per-row value. Called once per row with the row index in context.
|
||||||
#[implementations(
|
#[implementations(
|
||||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Option<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Table<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Option<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Table<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Option<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Table<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Option<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Table<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Option<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Table<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Option<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Table<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||||
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Option<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table<String>, Context -> DVec2, Context -> DAffine2, Context -> Table<NodeId>, Context -> Table<Color>, Context -> Table<GradientStops>,
|
||||||
)]
|
)]
|
||||||
value: impl Node<'n, Context<'static>, Output = U>,
|
value: impl Node<'n, Context<'static>, Output = U>,
|
||||||
) -> Table<T> {
|
) -> Table<T> {
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,7 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
|
||||||
(0..image.len())
|
(0..image.len())
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let row_transform: DAffine2 = image.attribute_cloned_or_default("transform", i);
|
let row_transform: DAffine2 = image.attribute_cloned_or_default("transform", i);
|
||||||
let layer: Option<NodeId> = image.attribute_cloned_or_default("editor:layer", i);
|
let layer: Table<NodeId> = image.attribute_cloned_or_default("editor:layer", i);
|
||||||
let alpha_blending: AlphaBlending = image.attribute_cloned_or_default("alpha_blending", i);
|
let alpha_blending: AlphaBlending = image.attribute_cloned_or_default("alpha_blending", i);
|
||||||
make_row(parent_transform * row_transform, layer, alpha_blending)
|
make_row(parent_transform * row_transform, layer, alpha_blending)
|
||||||
})
|
})
|
||||||
|
|
@ -223,7 +223,7 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
|
||||||
(0..image.len())
|
(0..image.len())
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let row_transform: DAffine2 = image.attribute_cloned_or_default("transform", i);
|
let row_transform: DAffine2 = image.attribute_cloned_or_default("transform", i);
|
||||||
let layer: Option<NodeId> = image.attribute_cloned_or_default("editor:layer", i);
|
let layer: Table<NodeId> = image.attribute_cloned_or_default("editor:layer", i);
|
||||||
let alpha_blending: AlphaBlending = image.attribute_cloned_or_default("alpha_blending", i);
|
let alpha_blending: AlphaBlending = image.attribute_cloned_or_default("alpha_blending", i);
|
||||||
make_row(parent_transform * row_transform, layer, alpha_blending)
|
make_row(parent_transform * row_transform, layer, alpha_blending)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,14 @@ async fn path_modify(_ctx: impl Ctx, mut vector: Table<Vector>, modification: Bo
|
||||||
}
|
}
|
||||||
modification.apply(vector.element_mut(0).expect("push should give one item"));
|
modification.apply(vector.element_mut(0).expect("push should give one item"));
|
||||||
|
|
||||||
// Update the source node id (penultimate element in the path, identifying the user-facing layer node)
|
// Set the path to the encapsulating subgraph (drop our own trailing entry from `node_path`),
|
||||||
let this_node_path = {
|
// matching the `path_of_subgraph` proto so editor tools can route data back to the parent layer.
|
||||||
let index = node_path.len().wrapping_sub(2);
|
let subgraph_path: Table<NodeId> = {
|
||||||
node_path.element(index).copied()
|
let len = node_path.len();
|
||||||
|
node_path.into_iter().take(len.saturating_sub(1)).collect()
|
||||||
};
|
};
|
||||||
let existing: Option<NodeId> = vector.attribute_cloned_or_default("editor:layer", 0);
|
let existing: Table<NodeId> = vector.attribute_cloned_or_default("editor:layer", 0);
|
||||||
vector.set_attribute("editor:layer", 0, existing.or(this_node_path));
|
vector.set_attribute("editor:layer", 0, if existing.is_empty() { subgraph_path } else { existing });
|
||||||
|
|
||||||
if vector.len() > 1 {
|
if vector.len() > 1 {
|
||||||
warn!("The path modify ran on {} vector rows. Only the first can be modified.", vector.len());
|
warn!("The path modify ran on {} vector rows. Only the first can be modified.", vector.len());
|
||||||
|
|
|
||||||
|
|
@ -1296,8 +1296,8 @@ pub async fn flatten_path<T: IntoGraphicTable + 'n + Send>(_: impl Ctx, #[implem
|
||||||
// Concatenate every vector element's subpaths into the single output compound path
|
// Concatenate every vector element's subpaths into the single output compound path
|
||||||
for index in 0..flattened.len() {
|
for index in 0..flattened.len() {
|
||||||
let Some(element) = flattened.element(index) else { continue };
|
let Some(element) = flattened.element(index) else { continue };
|
||||||
let node_id: Option<NodeId> = flattened.attribute_cloned_or_default("editor:layer", index);
|
let layer_path: Table<NodeId> = flattened.attribute_cloned_or_default("editor:layer", index);
|
||||||
let node_id = node_id.map(|node_id| node_id.0).unwrap_or_default();
|
let node_id = layer_path.iter_element_values().next_back().map(|node_id| node_id.0).unwrap_or_default();
|
||||||
|
|
||||||
let mut hasher = DefaultHasher::new();
|
let mut hasher = DefaultHasher::new();
|
||||||
(index, node_id).hash(&mut hasher);
|
(index, node_id).hash(&mut hasher);
|
||||||
|
|
@ -1318,8 +1318,8 @@ pub async fn flatten_path<T: IntoGraphicTable + 'n + Send>(_: impl Ctx, #[implem
|
||||||
// Adopt the last input row's layer so the editor can also bucket clicks under a contributing child layer
|
// Adopt the last input row's layer so the editor can also bucket clicks under a contributing child layer
|
||||||
if !flattened.is_empty() {
|
if !flattened.is_empty() {
|
||||||
let primary = flattened.len() - 1;
|
let primary = flattened.len() - 1;
|
||||||
let layer: Option<NodeId> = flattened.attribute_cloned_or_default("editor:layer", primary);
|
let layer_path: Table<NodeId> = flattened.attribute_cloned_or_default("editor:layer", primary);
|
||||||
output_table.set_attribute("editor:layer", 0, layer);
|
output_table.set_attribute("editor:layer", 0, layer_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
output_table
|
output_table
|
||||||
|
|
@ -2529,13 +2529,13 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
|
||||||
// The result is a synthesis of source and target, so adopt whichever endpoint the result is closer to as
|
// The result is a synthesis of source and target, so adopt whichever endpoint the result is closer to as
|
||||||
// the click-target identity (so the editor can route clicks back to one of the contributing layers)
|
// the click-target identity (so the editor can route clicks back to one of the contributing layers)
|
||||||
let primary_index = if time < 0.5 { source_index } else { target_index };
|
let primary_index = if time < 0.5 { source_index } else { target_index };
|
||||||
let layer: Option<NodeId> = content.attribute_cloned_or_default("editor:layer", primary_index);
|
let layer_path: Table<NodeId> = content.attribute_cloned_or_default("editor:layer", primary_index);
|
||||||
|
|
||||||
Table::new_from_row(
|
Table::new_from_row(
|
||||||
TableRow::new_from_element(vector)
|
TableRow::new_from_element(vector)
|
||||||
.with_attribute("transform", lerped_transform)
|
.with_attribute("transform", lerped_transform)
|
||||||
.with_attribute("alpha_blending", vector_alpha_blending)
|
.with_attribute("alpha_blending", vector_alpha_blending)
|
||||||
.with_attribute("editor:layer", layer)
|
.with_attribute("editor:layer", layer_path)
|
||||||
.with_attribute("editor:merged_layers", graphic_table_content),
|
.with_attribute("editor:merged_layers", graphic_table_content),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue