Skip to content
Open
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
10 changes: 3 additions & 7 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, LayerStructureNode};
use crate::messages::portfolio::document::utility_types::wires::{WirePath, WirePathUpdate};
use crate::messages::prelude::*;
use glam::IVec2;
Expand Down Expand Up @@ -235,12 +235,8 @@ pub enum FrontendMessage {
data: LayerPanelEntry,
},
UpdateDocumentLayerStructure {
#[serde(rename = "dataBuffer")]
data_buffer: RawBuffer,
},
UpdateDocumentLayerStructureJs {
#[serde(rename = "dataBuffer")]
data_buffer: JsRawBuffer,
#[serde(rename = "layerStructure")]
layer_structure: Vec<LayerStructureNode>,
},
UpdateDocumentRulers {
origin: (f64, f64),
Expand Down
92 changes: 24 additions & 68 deletions editor/src/messages/portfolio/document/document_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use super::node_graph::utility_types::Transform;
use super::utility_types::error::EditorError;
use super::utility_types::misc::{GroupFolderType, SNAP_FUNCTIONS_FOR_BOUNDING_BOXES, SNAP_FUNCTIONS_FOR_PATHS, SnappingOptions, SnappingState};
use super::utility_types::network_interface::{self, NodeNetworkInterface, TransactionStatus};
use super::utility_types::nodes::{CollapsedLayers, SelectedNodes};
use super::utility_types::nodes::{CollapsedLayers, LayerStructureNode, SelectedNodes};
use crate::application::{GRAPHITE_GIT_COMMIT_HASH, generate_uuid};
use crate::consts::{ASYMPTOTIC_EFFECT, COLOR_OVERLAY_GRAY, DEFAULT_DOCUMENT_NAME, FILE_EXTENSION, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ROTATE_SNAP_INTERVAL};
use crate::messages::input_mapper::utility_types::macros::action_shortcut;
Expand All @@ -19,7 +19,6 @@ 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::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,8 +316,8 @@ 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();
responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer });
let layer_structure = Self::default().layer_structure();
responses.add(FrontendMessage::UpdateDocumentLayerStructure { layer_structure });
}

// Clear the control bar
Expand Down Expand Up @@ -380,12 +379,12 @@ 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 layer_structure = self.layer_structure();

self.update_layers_panel_control_bar_widgets(layers_panel_open, responses);
self.update_layers_panel_bottom_bar_widgets(layers_panel_open, responses);

responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer });
responses.add(FrontendMessage::UpdateDocumentLayerStructure { layer_structure });
}
}
DocumentMessage::DrawArtboardOverlays { context: overlay_context } => {
Expand Down Expand Up @@ -1891,70 +1890,27 @@ impl DocumentMessageHandler {
Ok(document_message_handler)
}

/// 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;
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();
}
}
structure_section.push(space | (1 << 63));
/// Recursively builds the layer structure tree for a folder.
fn build_layer_structure(&self, folder: LayerNodeIdentifier) -> Vec<LayerStructureNode> {
folder
.children(self.metadata())
.map(|layer_node| {
let children = if layer_node.has_children(self.metadata()) && !self.collapsed.0.contains(&layer_node) {
self.build_layer_structure(layer_node)
} else {
Vec::new()
};
LayerStructureNode {
layer_id: layer_node.to_node(),
children,
}
})
.collect()
}

/// 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()
/// Returns the layer structure as a list of tree nodes, starting from the root.
pub fn layer_structure(&self) -> Vec<LayerStructureNode> {
self.build_layer_structure(LayerNodeIdentifier::ROOT_PARENT)
}

pub fn undo_with_history(&mut self, viewport: &ViewportMessageHandler, responses: &mut VecDeque<Message>) {
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;

/// Represents a node in the layer tree hierarchy, sent to the frontend.
/// Each node contains its layer ID and a list of its visible children.
#[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 LayerStructureNode {
#[serde(rename = "layerId")]
pub layer_id: NodeId,
pub children: Vec<LayerStructureNode>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]
Expand Down
82 changes: 11 additions & 71 deletions frontend/src/components/panels/Layers.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
import {
patchLayout,
UpdateDocumentLayerDetails,
UpdateDocumentLayerStructureJs,
UpdateDocumentLayerStructure,
UpdateLayersPanelControlBarLeftLayout,
UpdateLayersPanelControlBarRightLayout,
UpdateLayersPanelBottomBarLayout,
} from "@graphite/messages";
import type { DataBuffer, LayerPanelEntry, Layout } from "@graphite/messages";
import type { LayerPanelEntry, LayerStructureNode, Layout } from "@graphite/messages";
import type { NodeGraphState } from "@graphite/state-providers/node-graph";
import type { TooltipState } from "@graphite/state-providers/tooltip";
import { pasteFile } from "@graphite/utility-functions/files";
Expand Down Expand Up @@ -91,9 +91,8 @@
layersPanelBottomBarLayout = layersPanelBottomBarLayout;
});

editor.subscriptions.subscribeJsMessage(UpdateDocumentLayerStructureJs, (data) => {
const structure = newUpdateDocumentLayerStructure(data.dataBuffer);
rebuildLayerHierarchy(structure);
editor.subscriptions.subscribeJsMessage(UpdateDocumentLayerStructure, (data) => {
rebuildLayerHierarchy(data.layerStructure);
});

editor.subscriptions.subscribeJsMessage(UpdateDocumentLayerDetails, (data) => {
Expand All @@ -117,7 +116,7 @@
editor.subscriptions.unsubscribeJsMessage(UpdateLayersPanelControlBarLeftLayout);
editor.subscriptions.unsubscribeJsMessage(UpdateLayersPanelControlBarRightLayout);
editor.subscriptions.unsubscribeJsMessage(UpdateLayersPanelBottomBarLayout);
editor.subscriptions.unsubscribeJsMessage(UpdateDocumentLayerStructureJs);
editor.subscriptions.unsubscribeJsMessage(UpdateDocumentLayerStructure);
editor.subscriptions.unsubscribeJsMessage(UpdateDocumentLayerDetails);

removeEventListener("pointerup", draggingPointerUp);
Expand All @@ -130,65 +129,6 @@
removeEventListener("keyup", clippingKeyPress);
});

type DocumentLayerStructure = {
layerId: bigint;
children: DocumentLayerStructure[];
};

function newUpdateDocumentLayerStructure(dataBuffer: DataBuffer): DocumentLayerStructure {
const pointerNum = Number(dataBuffer.pointer);
const lengthNum = Number(dataBuffer.length);

const wasmMemoryBuffer = editor.raw.buffer;

// Decode the folder structure encoding
const encoding = new DataView(wasmMemoryBuffer, pointerNum, lengthNum);

// The structure section indicates how to read through the upcoming layer list and assign depths to each layer
const structureSectionLength = Number(encoding.getBigUint64(0, true));
const structureSectionMsbSigned = new DataView(wasmMemoryBuffer, pointerNum + 8, structureSectionLength * 8);

// The layer IDs section lists each layer ID sequentially in the tree, as it will show up in the panel
const layerIdsSection = new DataView(wasmMemoryBuffer, pointerNum + 8 + structureSectionLength * 8);

let layersEncountered = 0;
let currentFolder: DocumentLayerStructure = { layerId: BigInt(-1), children: [] };
const currentFolderStack = [currentFolder];

for (let i = 0; i < structureSectionLength; i += 1) {
const msbSigned = structureSectionMsbSigned.getBigUint64(i * 8, true);
const msbMask = BigInt(1) << BigInt(64 - 1);

// Set the MSB to 0 to clear the sign and then read the number as usual
const numberOfLayersAtThisDepth = msbSigned & ~msbMask;

// Store child folders in the current folder (until we are interrupted by an indent)
for (let j = 0; j < numberOfLayersAtThisDepth; j += 1) {
const layerId = layerIdsSection.getBigUint64(layersEncountered * 8, true);
layersEncountered += 1;

const childLayer: DocumentLayerStructure = { layerId, children: [] };
currentFolder.children.push(childLayer);
}

// Check the sign of the MSB, where a 1 is a negative (outward) indent
const subsequentDirectionOfDepthChange = (msbSigned & msbMask) === BigInt(0);
// Inward
if (subsequentDirectionOfDepthChange) {
currentFolderStack.push(currentFolder);
currentFolder = currentFolder.children[currentFolder.children.length - 1];
}
// Outward
else {
const popped = currentFolderStack.pop();
if (!popped) throw Error("Too many negative indents in the folder structure");
if (popped) currentFolder = popped;
}
}

return currentFolder;
}

function toggleNodeVisibilityLayerPanel(id: bigint) {
editor.handle.toggleNodeVisibilityLayerPanel(id);
}
Expand Down Expand Up @@ -515,32 +455,32 @@
dragInPanel = false;
}

function rebuildLayerHierarchy(updateDocumentLayerStructure: DocumentLayerStructure) {
function rebuildLayerHierarchy(layerStructure: LayerStructureNode[]) {
const layerWithNameBeingEdited = layers.find((layer: LayerListingInfo) => layer.editingName);
const layerIdWithNameBeingEdited = layerWithNameBeingEdited?.entry.id;

// Clear the layer hierarchy before rebuilding it
layers = [];

// Build the new layer hierarchy
const recurse = (folder: DocumentLayerStructure) => {
folder.children.forEach((item, index) => {
const recurse = (children: LayerStructureNode[]) => {
children.forEach((item, index) => {
const mapping = layerCache.get(String(item.layerId));
if (mapping) {
mapping.id = item.layerId;
layers.push({
folderIndex: index,
bottomLayer: index === folder.children.length - 1,
bottomLayer: index === children.length - 1,
entry: mapping,
editingName: layerIdWithNameBeingEdited === item.layerId,
});
}

// Call self recursively if there are any children
if (item.children.length >= 1) recurse(item);
if (item.children.length >= 1) recurse(item.children);
});
};
recurse(updateDocumentLayerStructure);
recurse(layerStructure);
layers = layers;
}

Expand Down
12 changes: 6 additions & 6 deletions frontend/src/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -796,13 +796,13 @@ export class TriggerSaveActiveDocument extends JsMessage {

export class DocumentChanged extends JsMessage {}

export type DataBuffer = {
pointer: bigint;
length: bigint;
export type LayerStructureNode = {
layerId: bigint;
children: LayerStructureNode[];
};

export class UpdateDocumentLayerStructureJs extends JsMessage {
readonly dataBuffer!: DataBuffer;
export class UpdateDocumentLayerStructure extends JsMessage {
readonly layerStructure!: LayerStructureNode[];
}

export type TextAlign = "Left" | "Center" | "Right" | "JustifyLeft";
Expand Down Expand Up @@ -1717,7 +1717,7 @@ export const messageMakers: Record<string, MessageMaker> = {
UpdateDocumentArtwork,
UpdateDocumentBarLayout,
UpdateDocumentLayerDetails,
UpdateDocumentLayerStructureJs,
UpdateDocumentLayerStructure,
UpdateDocumentRulers,
UpdateDocumentScrollbars,
UpdateExportReorderIndex,
Expand Down
6 changes: 1 addition & 5 deletions frontend/wasm/src/editor_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ impl EditorHandle {
}

// Sends a FrontendMessage to JavaScript
fn send_frontend_message_to_js(&self, mut message: FrontendMessage) {
fn send_frontend_message_to_js(&self, message: FrontendMessage) {
if let FrontendMessage::UpdateImageData { ref image_data } = message {
let new_hash = calculate_hash(image_data);
let prev_hash = IMAGE_DATA_HASH.load(Ordering::Relaxed);
Expand All @@ -166,10 +166,6 @@ impl EditorHandle {
return;
}

if let FrontendMessage::UpdateDocumentLayerStructure { data_buffer } = message {
message = FrontendMessage::UpdateDocumentLayerStructureJs { data_buffer: data_buffer.into() };
}

let message_type = message.to_discriminant().local_name();

let serializer = serde_wasm_bindgen::Serializer::new().serialize_large_number_types_as_bigints(true);
Expand Down
Loading