Skip to content

Commit d41883a

Browse files
authored
Add Layers panel support for displaying multiple groups with instances of the same children layers (#3982)
* Add Layers panel support for displaying multiple groups with instances of the same children layers * Fix insert folder box drawing
1 parent 86e41a1 commit d41883a

File tree

6 files changed

+341
-133
lines changed

6 files changed

+341
-133
lines changed

editor/src/messages/portfolio/document/document_message.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ pub enum DocumentMessage {
197197
undo_count: usize,
198198
},
199199
ToggleLayerExpansion {
200-
id: NodeId,
200+
instance_path: Vec<NodeId>,
201201
recursive: bool,
202202
},
203203
ToggleSelectedVisibility,

editor/src/messages/portfolio/document/document_message_handler.rs

Lines changed: 239 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
4242
use graphene_std::vector::misc::dvec2_to_point;
4343
use graphene_std::vector::style::RenderMode;
4444
use kurbo::{Affine, BezPath, Line, PathSeg};
45+
use std::collections::HashSet;
4546
use std::path::PathBuf;
4647
use std::sync::Arc;
4748
use std::time::Duration;
@@ -84,8 +85,8 @@ pub struct DocumentMessageHandler {
8485
//
8586
// Contains the NodeNetwork and acts an an interface to manipulate the NodeNetwork with custom setters in order to keep NetworkMetadata in sync
8687
pub network_interface: NodeNetworkInterface,
87-
/// List of the [`LayerNodeIdentifier`]s that are currently collapsed by the user in the Layers panel.
88-
/// Collapsed means that the expansion arrow isn't set to show the children of these layers.
88+
/// Tracks which layer instances are collapsed in the Layers panel, keyed by instance path.
89+
#[serde(deserialize_with = "deserialize_collapsed_layers", default)]
8990
pub collapsed: CollapsedLayers,
9091
/// The full Git commit hash of the Graphite repository that was used to build the editor.
9192
/// We save this to provide a hint about which version of the editor was used to create the document.
@@ -317,7 +318,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
317318
DocumentMessage::ClearLayersPanel => {
318319
// Send an empty layer list
319320
if layers_panel_open {
320-
let layer_structure = Self::default().build_layer_structure(LayerNodeIdentifier::ROOT_PARENT);
321+
let layer_structure = Self::default().build_layer_structure();
321322
responses.add(FrontendMessage::UpdateDocumentLayerStructure { layer_structure });
322323
}
323324

@@ -380,7 +381,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
380381
DocumentMessage::DocumentStructureChanged => {
381382
if layers_panel_open {
382383
self.network_interface.load_structure();
383-
let layer_structure = self.build_layer_structure(LayerNodeIdentifier::ROOT_PARENT);
384+
let layer_structure = self.build_layer_structure();
384385

385386
self.update_layers_panel_control_bar_widgets(layers_panel_open, responses);
386387
self.update_layers_panel_bottom_bar_widgets(layers_panel_open, responses);
@@ -1167,25 +1168,27 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
11671168
responses.add(OverlaysMessage::Draw);
11681169
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
11691170
}
1170-
DocumentMessage::ToggleLayerExpansion { id, recursive } => {
1171-
let layer = LayerNodeIdentifier::new(id, &self.network_interface);
1172-
let metadata = self.metadata();
1173-
1174-
let is_collapsed = self.collapsed.0.contains(&layer);
1171+
DocumentMessage::ToggleLayerExpansion { instance_path, recursive } => {
1172+
let is_collapsed = self.collapsed.0.contains(&instance_path);
11751173

11761174
if is_collapsed {
11771175
if recursive {
1178-
let children: HashSet<_> = layer.descendants(metadata).collect();
1179-
self.collapsed.0.retain(|collapsed_layer| !children.contains(collapsed_layer) && collapsed_layer != &layer);
1176+
// Remove this path and all descendant paths (paths that start with this one)
1177+
self.collapsed.0.retain(|path| !path.starts_with(&instance_path));
11801178
} else {
1181-
self.collapsed.0.retain(|collapsed_layer| collapsed_layer != &layer);
1179+
self.collapsed.0.retain(|path| *path != instance_path);
11821180
}
11831181
} else {
11841182
if recursive {
1185-
let children_to_add: Vec<_> = layer.descendants(metadata).filter(|child| !self.collapsed.0.contains(child)).collect();
1186-
self.collapsed.0.extend(children_to_add);
1183+
// Collapse all expanded descendant instances by collecting their paths from the structure tree
1184+
let descendant_paths = self.collect_descendant_instance_paths(&instance_path);
1185+
for path in descendant_paths {
1186+
if !self.collapsed.0.contains(&path) {
1187+
self.collapsed.0.push(path);
1188+
}
1189+
}
11871190
}
1188-
self.collapsed.0.push(layer);
1191+
self.collapsed.0.push(instance_path);
11891192
}
11901193

11911194
responses.add(NodeGraphMessage::SendGraph);
@@ -1740,22 +1743,218 @@ impl DocumentMessageHandler {
17401743
Ok(document_message_handler)
17411744
}
17421745

1743-
/// Recursively builds the layer structure tree for a folder.
1744-
fn build_layer_structure(&self, folder: LayerNodeIdentifier) -> Vec<LayerStructureEntry> {
1745-
folder
1746-
.children(self.metadata())
1747-
.map(|layer_node| {
1748-
let children = if layer_node.has_children(self.metadata()) && !self.collapsed.0.contains(&layer_node) {
1749-
self.build_layer_structure(layer_node)
1750-
} else {
1751-
Vec::new()
1752-
};
1753-
LayerStructureEntry {
1754-
layer_id: layer_node.to_node(),
1755-
children,
1746+
/// Builds the layer structure tree by traversing the node graph directly.
1747+
/// Unlike the canonical `structure` field of [`DocumentMetadata`] (which stores single-parent relationships), this allows
1748+
/// the same layer to appear under multiple parents when the graph feeds the same child content into separate parent layers.
1749+
fn build_layer_structure(&self) -> Vec<LayerStructureEntry> {
1750+
let network = &self.network_interface;
1751+
1752+
let Some(root_node) = network.root_node(&[]) else { return Vec::new() };
1753+
let Some(first_root_layer_id) = network
1754+
.upstream_flow_back_from_nodes(vec![root_node.node_id], &[], FlowType::PrimaryFlow)
1755+
.find(|node_id| network.is_layer(node_id, &[]))
1756+
else {
1757+
return Vec::new();
1758+
};
1759+
1760+
let selected_layers: HashSet<NodeId> = network.selected_nodes().selected_layers(self.metadata()).map(LayerNodeIdentifier::to_node).collect();
1761+
1762+
let ancestors = HashSet::new();
1763+
let instance_path = Vec::new();
1764+
let mut root_entries = Vec::new();
1765+
1766+
// The first root layer is the topmost entry
1767+
root_entries.push(self.build_layer_entry(first_root_layer_id, &ancestors, &selected_layers, &instance_path));
1768+
1769+
// Layers in the primary flow (input[0] chain) from the first root layer are root-level siblings
1770+
let mut root_ancestors = HashSet::new();
1771+
root_ancestors.insert(first_root_layer_id);
1772+
1773+
for sibling_id in network.upstream_flow_back_from_nodes(vec![first_root_layer_id], &[], FlowType::PrimaryFlow).skip(1) {
1774+
if network.is_layer(&sibling_id, &[]) && !root_ancestors.contains(&sibling_id) {
1775+
root_entries.push(self.build_layer_entry(sibling_id, &root_ancestors, &selected_layers, &instance_path));
1776+
}
1777+
}
1778+
1779+
root_entries
1780+
}
1781+
1782+
/// Builds a single `LayerStructureEntry` for the given layer, including its `children_present` flag,
1783+
/// `descendant_selected` flag, and (if expanded) its children collected from the graph.
1784+
fn build_layer_entry(&self, layer_id: NodeId, ancestors: &HashSet<NodeId>, selected_layers: &HashSet<NodeId>, parent_instance_path: &[NodeId]) -> LayerStructureEntry {
1785+
let mut instance_path = parent_instance_path.to_vec();
1786+
instance_path.push(layer_id);
1787+
1788+
let mut child_ancestors = ancestors.clone();
1789+
child_ancestors.insert(layer_id);
1790+
1791+
let children_present = self.has_layer_children_in_graph(layer_id, &child_ancestors);
1792+
1793+
let collapsed = self.collapsed.0.contains(&instance_path);
1794+
1795+
let children = if children_present && !collapsed {
1796+
self.collect_layer_children(layer_id, &child_ancestors, selected_layers, &instance_path)
1797+
} else {
1798+
Vec::new()
1799+
};
1800+
1801+
// Compute whether any descendant is selected (checking expanded children and, if collapsed, via graph traversal)
1802+
let descendant_selected = if !children.is_empty() {
1803+
children.iter().any(|child| child.descendant_selected || selected_layers.contains(&child.layer_id))
1804+
} else if children_present {
1805+
// Layer is collapsed but has children, so check via graph traversal
1806+
self.has_selected_descendant_in_graph(layer_id, &child_ancestors, selected_layers)
1807+
} else {
1808+
false
1809+
};
1810+
1811+
LayerStructureEntry {
1812+
layer_id,
1813+
children,
1814+
children_present,
1815+
descendant_selected,
1816+
}
1817+
}
1818+
1819+
/// Checks whether a layer has any child layers reachable via horizontal flow in the graph.
1820+
fn has_layer_children_in_graph(&self, layer_id: NodeId, child_ancestors: &HashSet<NodeId>) -> bool {
1821+
let network = &self.network_interface;
1822+
1823+
network
1824+
.upstream_flow_back_from_nodes(vec![layer_id], &[], FlowType::HorizontalFlow)
1825+
.skip(1)
1826+
.any(|id| network.is_layer(&id, &[]) && !child_ancestors.contains(&id))
1827+
}
1828+
1829+
/// Checks whether any descendant layer in the graph (via horizontal + primary flow) is selected.
1830+
/// Used when a layer is collapsed to determine if the ancestor-of-selected indicator should show.
1831+
fn has_selected_descendant_in_graph(&self, layer_id: NodeId, ancestors: &HashSet<NodeId>, selected_layers: &HashSet<NodeId>) -> bool {
1832+
let network = &self.network_interface;
1833+
1834+
// Find child layers via horizontal flow
1835+
let mut stack: Vec<NodeId> = network
1836+
.upstream_flow_back_from_nodes(vec![layer_id], &[], FlowType::HorizontalFlow)
1837+
.skip(1)
1838+
.filter(|node_id| network.is_layer(node_id, &[]) && !ancestors.contains(node_id))
1839+
.collect();
1840+
1841+
let mut visited = ancestors.clone();
1842+
1843+
// Iteratively explore all descendant layers via a depth-first traversal
1844+
while let Some(current_id) = stack.pop() {
1845+
// Skip already-visited layers to avoid infinite loops from graph cycles
1846+
if !visited.insert(current_id) {
1847+
continue;
1848+
}
1849+
1850+
// Found a selected descendant, the ancestor indicator should be shown
1851+
if selected_layers.contains(&current_id) {
1852+
return true;
1853+
}
1854+
1855+
// Check this layer's children via horizontal flow
1856+
for node_id in network.upstream_flow_back_from_nodes(vec![current_id], &[], FlowType::HorizontalFlow).skip(1) {
1857+
if network.is_layer(&node_id, &[]) && !visited.contains(&node_id) {
1858+
stack.push(node_id);
17561859
}
1757-
})
1758-
.collect()
1860+
}
1861+
1862+
// Check stacked siblings via primary flow
1863+
for node_id in network.upstream_flow_back_from_nodes(vec![current_id], &[], FlowType::PrimaryFlow).skip(1) {
1864+
if network.is_layer(&node_id, &[]) && !visited.contains(&node_id) {
1865+
stack.push(node_id);
1866+
}
1867+
}
1868+
}
1869+
1870+
false
1871+
}
1872+
1873+
/// Collects the child entries for a given layer by traversing its horizontal and primary flows.
1874+
/// The horizontal flow (a layer's secondary input chain) finds nested content layers, and the
1875+
/// primary flow from those (their stack's top output) finds stacked siblings at the same depth.
1876+
/// `ancestors` contains layer IDs in the current path from root, used for cycle prevention.
1877+
fn collect_layer_children(&self, layer_id: NodeId, ancestors: &HashSet<NodeId>, selected_layers: &HashSet<NodeId>, instance_path: &[NodeId]) -> Vec<LayerStructureEntry> {
1878+
let network = &self.network_interface;
1879+
1880+
// Find the first nested layer via horizontal flow (content inside this layer)
1881+
let Some(nested_id) = network
1882+
.upstream_flow_back_from_nodes(vec![layer_id], &[], FlowType::HorizontalFlow)
1883+
.skip(1)
1884+
.find(|id| network.is_layer(id, &[]))
1885+
else {
1886+
return Vec::new();
1887+
};
1888+
1889+
// Cycle detected, this layer is already an ancestor in the current branch
1890+
if ancestors.contains(&nested_id) {
1891+
return Vec::new();
1892+
}
1893+
1894+
// The nested layer is the first child at this depth level
1895+
let mut children = vec![self.build_layer_entry(nested_id, ancestors, selected_layers, instance_path)];
1896+
1897+
// Primary flow from the nested layer finds stacked siblings (more children of this layer)
1898+
for sibling_id in network.upstream_flow_back_from_nodes(vec![nested_id], &[], FlowType::PrimaryFlow).skip(1) {
1899+
if network.is_layer(&sibling_id, &[]) && !ancestors.contains(&sibling_id) {
1900+
children.push(self.build_layer_entry(sibling_id, ancestors, selected_layers, instance_path));
1901+
}
1902+
}
1903+
1904+
children
1905+
}
1906+
1907+
/// Collects instance paths for all descendant layers of the given instance path by traversing the graph.
1908+
/// Used for recursive collapse to find all expandable descendants.
1909+
fn collect_descendant_instance_paths(&self, instance_path: &[NodeId]) -> Vec<Vec<NodeId>> {
1910+
let Some(&layer_id) = instance_path.last() else { return Vec::new() };
1911+
let network = &self.network_interface;
1912+
1913+
let mut paths = Vec::new();
1914+
let mut stack: Vec<(NodeId, Vec<NodeId>)> = Vec::new();
1915+
1916+
// Seed with child layers via horizontal flow
1917+
for node_id in network.upstream_flow_back_from_nodes(vec![layer_id], &[], FlowType::HorizontalFlow).skip(1) {
1918+
if network.is_layer(&node_id, &[]) {
1919+
let mut child_path = instance_path.to_vec();
1920+
child_path.push(node_id);
1921+
stack.push((node_id, child_path));
1922+
}
1923+
}
1924+
1925+
let mut visited = HashSet::new();
1926+
1927+
// Depth-first traversal collecting all unique descendant instance paths
1928+
while let Some((current_id, current_path)) = stack.pop() {
1929+
// Skip paths we've already visited to prevent cycles
1930+
if !visited.insert(current_path.clone()) {
1931+
continue;
1932+
}
1933+
1934+
// Record this descendant's instance path for collapsing
1935+
paths.push(current_path.clone());
1936+
1937+
// Add nested content layers found via horizontal flow
1938+
for node_id in network.upstream_flow_back_from_nodes(vec![current_id], &[], FlowType::HorizontalFlow).skip(1) {
1939+
if network.is_layer(&node_id, &[]) {
1940+
let mut child_path = current_path.clone();
1941+
child_path.push(node_id);
1942+
stack.push((node_id, child_path));
1943+
}
1944+
}
1945+
1946+
// Add stacked sibling layers found via primary flow
1947+
for node_id in network.upstream_flow_back_from_nodes(vec![current_id], &[], FlowType::PrimaryFlow).skip(1) {
1948+
if network.is_layer(&node_id, &[]) {
1949+
// Siblings share the same parent path (everything up to the last element of current_path)
1950+
let mut sibling_path = current_path[..current_path.len() - 1].to_vec();
1951+
sibling_path.push(node_id);
1952+
stack.push((node_id, sibling_path));
1953+
}
1954+
}
1955+
}
1956+
1957+
paths
17591958
}
17601959

17611960
pub fn undo_with_history(&mut self, viewport: &ViewportMessageHandler, responses: &mut VecDeque<Message>) {
@@ -3221,6 +3420,16 @@ impl Iterator for ClickXRayIter<'_> {
32213420
}
32223421
}
32233422

3423+
/// Deserializes `CollapsedLayers` with backwards compatibility for the old format
3424+
/// (flat list of layer node IDs) by consuming the entire value first, then attempting
3425+
/// to interpret it as the new format. Falls back to an empty default for old documents.
3426+
fn deserialize_collapsed_layers<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<CollapsedLayers, D::Error> {
3427+
use serde::Deserialize;
3428+
// Buffer the entire value to avoid leaving the deserializer in a bad state on type mismatch
3429+
let value = serde_json::Value::deserialize(deserializer)?;
3430+
Ok(serde_json::from_value(value).unwrap_or_default())
3431+
}
3432+
32243433
#[cfg(test)]
32253434
mod document_message_handler_tests {
32263435
use super::*;

0 commit comments

Comments
 (0)