Add the Spreadsheet panel to inspect node output data (#2442)

* Inspect node ouput stub

* Fix compile error in tests

* Create a table

* Clickable tables

* Add vector data support

* Checkbox to enable the panel

* Remove Instances table ID column; style the spreadsheet

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
James Lindsay
2025-03-18 23:06:05 -07:00
committed by GitHub
co-authored by Keavon Chambers
parent 6292dea103
commit 43275b7a1e
24 changed files with 771 additions and 108 deletions
@@ -40,6 +40,29 @@ impl LayoutMessageHandler {
LayoutGroup::Section { layout, .. } => {
stack.extend(layout.iter().enumerate().map(|(index, val)| ([widget_path.as_slice(), &[index]].concat(), val)));
}
LayoutGroup::Table { rows } => {
for (row_index, cell) in rows.iter().enumerate() {
for (cell_index, entry) in cell.iter().enumerate() {
// Return if this is the correct ID
if entry.widget_id == widget_id {
widget_path.push(row_index);
widget_path.push(cell_index);
return Some((entry, widget_path));
}
if let Widget::PopoverButton(popover) = &entry.widget {
stack.extend(
popover
.popover_layout
.iter()
.enumerate()
.map(|(child, val)| ([widget_path.as_slice(), &[row_index, cell_index, child]].concat(), val)),
);
}
}
}
}
}
}
None
@@ -405,6 +428,7 @@ impl LayoutMessageHandler {
LayoutTarget::MenuBar => unreachable!("Menu bar is not diffed"),
LayoutTarget::NodeGraphControlBar => FrontendMessage::UpdateNodeGraphControlBarLayout { layout_target, diff },
LayoutTarget::PropertiesSections => FrontendMessage::UpdatePropertyPanelSectionsLayout { layout_target, diff },
LayoutTarget::Spreadsheet => FrontendMessage::UpdateSpreadsheetLayout { layout_target, diff },
LayoutTarget::ToolOptions => FrontendMessage::UpdateToolOptionsLayout { layout_target, diff },
LayoutTarget::ToolShelf => FrontendMessage::UpdateToolShelfLayout { layout_target, diff },
LayoutTarget::WorkingColors => FrontendMessage::UpdateWorkingColorsLayout { layout_target, diff },
@@ -39,6 +39,8 @@ pub enum LayoutTarget {
NodeGraphControlBar,
/// The body of the Properties panel containing many collapsable sections.
PropertiesSections,
/// The spredsheet panel allows for the visualisation of data in the graph.
Spreadsheet,
/// The bar directly above the canvas, left-aligned and to the right of the document mode dropdown.
ToolOptions,
/// The vertical buttons for all of the tools on the left of the canvas.
@@ -166,14 +168,14 @@ impl WidgetLayout {
pub fn iter(&self) -> WidgetIter<'_> {
WidgetIter {
stack: self.layout.iter().collect(),
current_slice: None,
..Default::default()
}
}
pub fn iter_mut(&mut self) -> WidgetIterMut<'_> {
WidgetIterMut {
stack: self.layout.iter_mut().collect(),
current_slice: None,
..Default::default()
}
}
@@ -205,6 +207,7 @@ impl WidgetLayout {
#[derive(Debug, Default)]
pub struct WidgetIter<'a> {
pub stack: Vec<&'a LayoutGroup>,
pub table: Vec<&'a WidgetHolder>,
pub current_slice: Option<&'a [WidgetHolder]>,
}
@@ -212,9 +215,13 @@ impl<'a> Iterator for WidgetIter<'a> {
type Item = &'a WidgetHolder;
fn next(&mut self) -> Option<Self::Item> {
if let Some(item) = self.current_slice.and_then(|slice| slice.first()) {
self.current_slice = Some(&self.current_slice.unwrap()[1..]);
let widget = self.table.pop().or_else(|| {
let (first, rest) = self.current_slice.take()?.split_first()?;
self.current_slice = Some(rest);
Some(first)
});
if let Some(item) = widget {
if let WidgetHolder { widget: Widget::PopoverButton(p), .. } = item {
self.stack.extend(p.popover_layout.iter());
return self.next();
@@ -232,6 +239,10 @@ impl<'a> Iterator for WidgetIter<'a> {
self.current_slice = Some(widgets);
self.next()
}
Some(LayoutGroup::Table { rows }) => {
self.table.extend(rows.iter().flatten().rev());
self.next()
}
Some(LayoutGroup::Section { layout, .. }) => {
for layout_row in layout {
self.stack.push(layout_row);
@@ -246,6 +257,7 @@ impl<'a> Iterator for WidgetIter<'a> {
#[derive(Debug, Default)]
pub struct WidgetIterMut<'a> {
pub stack: Vec<&'a mut LayoutGroup>,
pub table: Vec<&'a mut WidgetHolder>,
pub current_slice: Option<&'a mut [WidgetHolder]>,
}
@@ -253,16 +265,20 @@ impl<'a> Iterator for WidgetIterMut<'a> {
type Item = &'a mut WidgetHolder;
fn next(&mut self) -> Option<Self::Item> {
if let Some((first, rest)) = self.current_slice.take().and_then(|slice| slice.split_first_mut()) {
let widget = self.table.pop().or_else(|| {
let (first, rest) = self.current_slice.take()?.split_first_mut()?;
self.current_slice = Some(rest);
Some(first)
});
if let WidgetHolder { widget: Widget::PopoverButton(p), .. } = first {
if let Some(widget) = widget {
if let WidgetHolder { widget: Widget::PopoverButton(p), .. } = widget {
self.stack.extend(p.popover_layout.iter_mut());
return self.next();
}
return Some(first);
};
return Some(widget);
}
match self.stack.pop() {
Some(LayoutGroup::Column { widgets }) => {
@@ -273,6 +289,10 @@ impl<'a> Iterator for WidgetIterMut<'a> {
self.current_slice = Some(widgets);
self.next()
}
Some(LayoutGroup::Table { rows }) => {
self.table.extend(rows.iter_mut().flatten().rev());
self.next()
}
Some(LayoutGroup::Section { layout, .. }) => {
for layout_row in layout {
self.stack.push(layout_row);
@@ -298,6 +318,11 @@ pub enum LayoutGroup {
#[serde(rename = "rowWidgets")]
widgets: Vec<WidgetHolder>,
},
#[serde(rename = "table")]
Table {
#[serde(rename = "tableWidgets")]
rows: Vec<Vec<WidgetHolder>>,
},
// TODO: Move this from being a child of `enum LayoutGroup` to being a child of `enum Layout`
#[serde(rename = "section")]
Section { name: String, visible: bool, pinned: bool, id: u64, layout: SubLayout },
@@ -432,7 +457,7 @@ impl LayoutGroup {
pub fn iter_mut(&mut self) -> WidgetIterMut<'_> {
WidgetIterMut {
stack: vec![self],
current_slice: None,
..Default::default()
}
}
}