Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions editor/src/messages/frontend/frontend_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::utility_types::{
BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, NodeGraphErrorDiagnostic, Transform,
};
use crate::messages::portfolio::document::utility_types::nodes::{JsRawBuffer, LayerPanelEntry, RawBuffer};
use crate::messages::portfolio::document::utility_types::nodes::{LayerPanelEntry, LayerStructureEntry};
use crate::messages::portfolio::document::utility_types::wires::{WirePath, WirePathUpdate};
use crate::messages::prelude::*;
use glam::IVec2;
Expand Down Expand Up @@ -236,11 +236,7 @@ pub enum FrontendMessage {
},
UpdateDocumentLayerStructure {
#[serde(rename = "dataBuffer")]
data_buffer: RawBuffer,
},
UpdateDocumentLayerStructureJs {
#[serde(rename = "dataBuffer")]
data_buffer: JsRawBuffer,
data_buffer: Vec<LayerStructureEntry>,
},
UpdateDocumentRulers {
origin: (f64, f64),
Expand Down
156 changes: 95 additions & 61 deletions editor/src/messages/portfolio/document/document_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::messages::portfolio::document::properties_panel::properties_panel_mes
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, PTZ};
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeTemplate};
use crate::messages::portfolio::document::utility_types::nodes::RawBuffer;
use crate::messages::portfolio::document::utility_types::nodes::LayerStructureEntry;
use crate::messages::portfolio::utility_types::{PanelType, PersistentData};
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils::{self, get_blend_mode, get_fill, get_opacity};
Expand Down Expand Up @@ -317,7 +317,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
DocumentMessage::ClearLayersPanel => {
// Send an empty layer list
if layers_panel_open {
let data_buffer: RawBuffer = Self::default().serialize_root();
let data_buffer: Vec<LayerStructureEntry> = Self::default().serialize_root();
responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer });
}

Expand Down Expand Up @@ -380,7 +380,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
DocumentMessage::DocumentStructureChanged => {
if layers_panel_open {
self.network_interface.load_structure();
let data_buffer: RawBuffer = self.serialize_root();
let data_buffer: Vec<LayerStructureEntry> = self.serialize_root();

self.update_layers_panel_control_bar_widgets(layers_panel_open, responses);
self.update_layers_panel_bottom_bar_widgets(layers_panel_open, responses);
Expand Down Expand Up @@ -1892,69 +1892,26 @@ impl DocumentMessageHandler {
}

/// Called recursively by the entry function [`serialize_root`].
fn serialize_structure(&self, folder: LayerNodeIdentifier, structure_section: &mut Vec<u64>, data_section: &mut Vec<u64>, path: &mut Vec<LayerNodeIdentifier>) {
let mut space = 0;
for layer_node in folder.children(self.metadata()) {
data_section.push(layer_node.to_node().0);
space += 1;
fn serialize_structure_flat(&self, folder: LayerNodeIdentifier, depth: u32, entries: &mut Vec<LayerStructureEntry>) {
let mut children = folder.children(self.metadata()).peekable();
while let Some(layer_node) = children.next() {
let is_last_in_parent = children.peek().is_none();
entries.push(LayerStructureEntry {
id: layer_node.to_node(),
depth,
is_last_in_parent,
});
if layer_node.has_children(self.metadata()) && !self.collapsed.0.contains(&layer_node) {
path.push(layer_node);

// TODO: Skip if folder is not expanded.
structure_section.push(space);
self.serialize_structure(layer_node, structure_section, data_section, path);
space = 0;

path.pop();
self.serialize_structure_flat(layer_node, depth + 1, entries);
}
}
structure_section.push(space | (1 << 63));
}

/// Serializes the layer structure into a condensed 1D structure.
///
/// # Format
/// It is a string of numbers broken into three sections:
///
/// | Data | Description | Length |
/// |------------------------------------------------------------------------------------------------------------------------------ |---------------------------------------------------------------|------------------|
/// | `4,` `2, 1, -2, -0,` `16533113728871998040,3427872634365736244,18115028555707261608,15878401910454357952,449479075714955186` | Encoded example data | |
/// | _____________________________________________________________________________________________________________________________ | _____________________________________________________________ | ________________ |
/// | **Length** section: `4` | Length of the **Structure** section (`L` = `structure.len()`) | First value |
/// | **Structure** section: `2, 1, -2, -0` | The **Structure** section | Next `L` values |
/// | **Data** section: `16533113728871998040, 3427872634365736244, 18115028555707261608, 15878401910454357952, 449479075714955186` | The **Data** section (layer IDs) | Remaining values |
///
/// The data section lists the layer IDs for all folders/layers in the tree as read from top to bottom.
/// The structure section lists signed numbers. The sign indicates a folder indentation change (`+` is down a level, `-` is up a level).
/// The numbers in the structure block encode the indentation. For example:
/// - `2` means read two elements from the data section, then place a `[`.
/// - `-x` means read `x` elements from the data section and then insert a `]`.
///
/// ```text
/// 2 V 1 V -2 A -0 A
/// 16533113728871998040,3427872634365736244, 18115028555707261608, 15878401910454357952,449479075714955186
/// 16533113728871998040,3427872634365736244,[ 18115028555707261608,[15878401910454357952,449479075714955186] ]
/// ```
///
/// Resulting layer panel:
/// ```text
/// 16533113728871998040
/// 3427872634365736244
/// [3427872634365736244,18115028555707261608]
/// [3427872634365736244,18115028555707261608,15878401910454357952]
/// [3427872634365736244,18115028555707261608,449479075714955186]
/// ```
pub fn serialize_root(&self) -> RawBuffer {
let mut structure_section = vec![NodeId(0).0];
let mut data_section = Vec::new();
self.serialize_structure(LayerNodeIdentifier::ROOT_PARENT, &mut structure_section, &mut data_section, &mut vec![]);

// Remove the ROOT element. Prepend `L`, the length (excluding the ROOT) of the structure section (which happens to be where the ROOT element was).
structure_section[0] = structure_section.len() as u64 - 1;
// Append the data section to the end.
structure_section.extend(data_section);

structure_section.as_slice().into()
/// Serializes the layer structure into a flat list with depth information.
pub fn serialize_root(&self) -> Vec<LayerStructureEntry> {
let mut entries = Vec::new();
self.serialize_structure_flat(LayerNodeIdentifier::ROOT_PARENT, 0, &mut entries);
entries
}

pub fn undo_with_history(&mut self, viewport: &ViewportMessageHandler, responses: &mut VecDeque<Message>) {
Expand Down Expand Up @@ -3229,6 +3186,83 @@ impl Iterator for ClickXRayIter<'_> {
mod document_message_handler_tests {
use super::*;
use crate::test_utils::test_prelude::*;
use std::collections::HashSet;

#[tokio::test]
async fn test_serialize_root_empty_document() {
let mut editor = EditorTestUtils::create();
editor.new_document().await;

let entries = editor.active_document().serialize_root();
assert!(entries.is_empty());
}

#[tokio::test]
async fn test_serialize_root_flat_structure() {
let mut editor = EditorTestUtils::create();
editor.new_document().await;

editor.handle_message(DocumentMessage::CreateEmptyFolder).await;
let folder = editor.active_document().metadata().all_layers().next().unwrap();

let mut excluded = HashSet::from([folder.to_node()]);

editor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await;
let rect1 = editor.active_document().metadata().all_layers().find(|layer| !excluded.contains(&layer.to_node())).unwrap();
excluded.insert(rect1.to_node());

editor.drag_tool(ToolType::Rectangle, 100., 100., 200., 200., ModifierKeys::empty()).await;
let rect2 = editor.active_document().metadata().all_layers().find(|layer| !excluded.contains(&layer.to_node())).unwrap();

editor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![rect1.to_node()] }).await;
editor.handle_message(DocumentMessage::MoveSelectedLayersTo { parent: folder, insert_index: 0 }).await;

editor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![rect2.to_node()] }).await;
editor.handle_message(DocumentMessage::MoveSelectedLayersTo { parent: folder, insert_index: 1 }).await;

let entries = editor.active_document().serialize_root();
assert_eq!(entries.len(), 3);

assert_eq!(entries[0].id, folder.to_node());
assert_eq!(entries[0].depth, 0);
assert!(entries[0].is_last_in_parent);

assert_eq!(entries[1].id, rect1.to_node());
assert_eq!(entries[1].depth, 1);
assert!(!entries[1].is_last_in_parent);

assert_eq!(entries[2].id, rect2.to_node());
assert_eq!(entries[2].depth, 1);
assert!(entries[2].is_last_in_parent);
}

#[tokio::test]
async fn test_serialize_root_excludes_collapsed_children() {
let mut editor = EditorTestUtils::create();
editor.new_document().await;

editor.handle_message(DocumentMessage::CreateEmptyFolder).await;
let folder = editor.active_document().metadata().all_layers().next().unwrap();

editor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await;
let rect = editor.active_document().metadata().all_layers().find(|layer| layer.to_node() != folder.to_node()).unwrap();

editor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![rect.to_node()] }).await;
editor.handle_message(DocumentMessage::MoveSelectedLayersTo { parent: folder, insert_index: 0 }).await;

editor
.handle_message(DocumentMessage::ToggleLayerExpansion {
id: folder.to_node(),
recursive: false,
})
.await;

let entries = editor.active_document().serialize_root();
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].id, folder.to_node());
assert_eq!(entries[0].depth, 0);
assert!(entries[0].is_last_in_parent);
}

#[tokio::test]
async fn test_layer_selection_with_shift_and_ctrl() {
Expand Down
30 changes: 6 additions & 24 deletions editor/src/messages/portfolio/document/utility_types/nodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,14 @@ use super::network_interface::NodeNetworkInterface;
use crate::messages::tool::common_functionality::graph_modification_utils;
use glam::DVec2;
use graph_craft::document::{NodeId, NodeNetwork};
use serde::ser::SerializeStruct;

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]
pub struct RawBuffer(Vec<u8>);

impl From<&[u64]> for RawBuffer {
fn from(iter: &[u64]) -> Self {
let v_from_raw: Vec<u8> = iter.iter().flat_map(|x| x.to_ne_bytes()).collect();
Self(v_from_raw)
}
}
#[derive(Debug, Clone, serde::Deserialize, PartialEq, Eq, specta::Type)]
pub struct JsRawBuffer(Vec<u8>);

impl From<RawBuffer> for JsRawBuffer {
fn from(buffer: RawBuffer) -> Self {
Self(buffer.0)
}
}
impl serde::Serialize for JsRawBuffer {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut buffer = serializer.serialize_struct("Buffer", 2)?;
buffer.serialize_field("pointer", &(self.0.as_ptr() as usize))?;
buffer.serialize_field("length", &(self.0.len()))?;
buffer.end()
}
pub struct LayerStructureEntry {
#[serde(rename = "layerId")]
pub id: NodeId,
pub depth: u32,
#[serde(rename = "isLastInParent")]
pub is_last_in_parent: bool,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]
Expand Down
Loading
Loading