diff --git a/client/web/wasm/src/document.rs b/client/web/wasm/src/document.rs index f73b5894..0974f607 100644 --- a/client/web/wasm/src/document.rs +++ b/client/web/wasm/src/document.rs @@ -104,11 +104,12 @@ pub fn export_document() -> Result<(), JsValue> { EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::ExportDocument)).map_err(convert_error) } -/// Select a layer from the layer list +/// Update the list of selected layers. The layer paths have to be stored in one array and are separated by LayerId::MAX #[wasm_bindgen] -pub fn select_layer(path: Vec) -> Result<(), JsValue> { +pub fn select_layers(paths: Vec) -> Result<(), JsValue> { + let paths = paths.split(|id| *id == LayerId::MAX).map(|path| path.to_vec()).collect(); EDITOR_STATE - .with(|editor| editor.borrow_mut().handle_message(DocumentMessage::SelectLayer(path))) + .with(|editor| editor.borrow_mut().handle_message(DocumentMessage::SelectLayers(paths))) .map_err(convert_error) } diff --git a/core/document/src/document.rs b/core/document/src/document.rs index 8e8011fc..fb0338d6 100644 --- a/core/document/src/document.rs +++ b/core/document/src/document.rs @@ -1,6 +1,5 @@ use crate::{ layers::{self, Folder, Layer, LayerData, LayerDataTypes, Line, PolyLine, Rect, Shape}, - response::LayerPanelEntry, DocumentError, DocumentResponse, LayerId, Operation, }; @@ -168,19 +167,6 @@ impl Document { Ok(()) } - /// Returns a list of `LayerPanelEntry`s intended for display purposes. These don't contain - /// any actual data, but rather metadata such as visibility and names of the layers. - pub fn layer_panel(&self, path: &[LayerId]) -> Result, DocumentError> { - let folder = self.document_folder(path)?; - let entries = folder - .layers() - .iter() - .zip(folder.layer_ids.iter()) - .map(|(layer, id)| LayerPanelEntry::from_layer(layer, [path, &[*id]].concat())) - .collect(); - Ok(entries) - } - /// Mutate the document by applying the `operation` to it. If the operation necessitates a /// reaction from the frontend, responses may be returned. pub fn handle_operation(&mut self, operation: Operation) -> Result>, DocumentError> { @@ -255,14 +241,12 @@ impl Document { self.delete(&path)?; let (path, _) = split_path(path.as_slice()).unwrap_or_else(|_| (&[], 0)); - let children = self.layer_panel(path)?; - Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::ExpandFolder { path: path.to_vec(), children }]) + Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::FolderChanged { path: path.to_vec() }]) } Operation::AddFolder { path } => { self.set_layer(&path, Layer::new(LayerDataTypes::Folder(Folder::default())))?; - let children = self.layer_panel(path.as_slice())?; - Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::ExpandFolder { path: path.clone(), children }]) + Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::FolderChanged { path: path.clone() }]) } Operation::MountWorkingFolder { path } => { self.work_mount_path = path.clone(); @@ -298,17 +282,15 @@ impl Document { } } - let children = self.layer_panel(path.as_slice())?; - // TODO: Return `responses` and add deduplication in the future - Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::ExpandFolder { path, children }]) + Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::FolderChanged { path }]) } Operation::ToggleVisibility { path } => { let _ = self.layer_mut(&path).map(|layer| { layer.visible = !layer.visible; layer.cache_dirty = true; }); - let children = self.layer_panel(&path.as_slice()[..path.len() - 1])?; - Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::ExpandFolder { path: vec![], children }]) + let path = path.as_slice()[..path.len() - 1].to_vec(); + Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::FolderChanged { path }]) } }; if !matches!( diff --git a/core/document/src/layers/folder.rs b/core/document/src/layers/folder.rs index e47978af..71896e2c 100644 --- a/core/document/src/layers/folder.rs +++ b/core/document/src/layers/folder.rs @@ -9,7 +9,6 @@ pub struct Folder { next_assignment_id: LayerId, pub layer_ids: Vec, layers: Vec, - pub collapsed: bool, } impl LayerData for Folder { @@ -88,7 +87,6 @@ impl Default for Folder { layer_ids: vec![], layers: vec![], next_assignment_id: 0, - collapsed: false, } } } diff --git a/core/document/src/response.rs b/core/document/src/response.rs index 5d30ce60..d86c1844 100644 --- a/core/document/src/response.rs +++ b/core/document/src/response.rs @@ -1,91 +1,19 @@ -use crate::{ - layers::{Layer, LayerDataTypes}, - LayerId, -}; +use crate::LayerId; use serde::{Deserialize, Serialize}; use std::fmt; -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] -pub struct LayerPanelEntry { - pub name: String, - pub visible: bool, - pub layer_type: LayerType, - pub collapsed: bool, - pub path: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] -pub enum LayerType { - Folder, - Shape, - Circle, - Rect, - Line, - PolyLine, - Ellipse, -} - -impl fmt::Display for LayerType { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - let name = match self { - LayerType::Folder => "Folder", - LayerType::Shape => "Shape", - LayerType::Rect => "Rectangle", - LayerType::Line => "Line", - LayerType::Circle => "Circle", - LayerType::PolyLine => "Polyline", - LayerType::Ellipse => "Ellipse", - }; - - formatter.write_str(name) - } -} - -impl From<&LayerDataTypes> for LayerType { - fn from(data: &LayerDataTypes) -> Self { - use LayerDataTypes::*; - match data { - Folder(_) => LayerType::Folder, - Shape(_) => LayerType::Shape, - Circle(_) => LayerType::Circle, - Rect(_) => LayerType::Rect, - Line(_) => LayerType::Line, - PolyLine(_) => LayerType::PolyLine, - Ellipse(_) => LayerType::Ellipse, - } - } -} - -impl LayerPanelEntry { - pub fn from_layer(layer: &Layer, path: Vec) -> Self { - let layer_type: LayerType = (&layer.data).into(); - let name = layer.name.clone().unwrap_or_else(|| format!("Unnamed {}", layer_type)); - let collapsed = if let LayerDataTypes::Folder(f) = &layer.data { f.collapsed } else { true }; - Self { - name, - visible: layer.visible, - layer_type, - collapsed, - path, - } - } -} - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] #[repr(C)] -// TODO - Make Copy when possible pub enum DocumentResponse { DocumentChanged, - CollapseFolder { path: Vec }, - ExpandFolder { path: Vec, children: Vec }, + FolderChanged { path: Vec }, } impl fmt::Display for DocumentResponse { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { let name = match self { DocumentResponse::DocumentChanged { .. } => "DocumentChanged", - DocumentResponse::CollapseFolder { .. } => "CollapseFolder", - DocumentResponse::ExpandFolder { .. } => "ExpandFolder", + DocumentResponse::FolderChanged { .. } => "FolderChanged", }; formatter.write_str(name) diff --git a/core/editor/src/document/document_file.rs b/core/editor/src/document/document_file.rs index 56f41eaf..65d3e96c 100644 --- a/core/editor/src/document/document_file.rs +++ b/core/editor/src/document/document_file.rs @@ -1,7 +1,69 @@ -use document_core::document::Document as InteralDocument; +use crate::{frontend::layer_panel::*, EditorError}; +use document_core::{document::Document as InteralDocument, layers::Layer, LayerId}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] pub struct Document { pub document: InteralDocument, pub name: String, + pub layer_data: HashMap, LayerData>, +} + +impl Default for Document { + fn default() -> Self { + Self { + document: InteralDocument::default(), + name: String::from("Unnamed Document"), + layer_data: vec![(vec![], LayerData { selected: false, expanded: true })].into_iter().collect(), + } + } +} + +fn layer_data<'a>(layer_data: &'a mut HashMap, LayerData>, path: &[LayerId]) -> &'a mut LayerData { + if !layer_data.contains_key(path) { + layer_data.insert(path.to_vec(), LayerData::default()); + } + layer_data.get_mut(path).unwrap() +} + +pub fn layer_panel_entry(layer_data: &mut LayerData, layer: &Layer, path: Vec) -> LayerPanelEntry { + let layer_type: LayerType = (&layer.data).into(); + let name = layer.name.clone().unwrap_or_else(|| format!("Unnamed {}", layer_type)); + LayerPanelEntry { + name, + visible: layer.visible, + layer_type, + layer_data: *layer_data, + path, + } +} + +impl Document { + pub fn layer_data(&mut self, path: &[LayerId]) -> &mut LayerData { + layer_data(&mut self.layer_data, path) + } + + /// Returns a list of `LayerPanelEntry`s intended for display purposes. These don't contain + /// any actual data, but rather metadata such as visibility and names of the layers. + pub fn layer_panel(&mut self, path: &[LayerId]) -> Result, EditorError> { + let folder = self.document.document_folder(path)?; + let self_layer_data = &mut self.layer_data; + let entries = folder + .layers() + .iter() + .zip(folder.layer_ids.iter()) + .map(|(layer, id)| { + let path = [path, &[*id]].concat(); + layer_panel_entry(layer_data(self_layer_data, &path), layer, path) + }) + .collect(); + Ok(entries) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Copy, Default)] +pub struct LayerData { + pub selected: bool, + pub expanded: bool, } diff --git a/core/editor/src/document/document_message_handler.rs b/core/editor/src/document/document_message_handler.rs index 432b4150..b61eadfc 100644 --- a/core/editor/src/document/document_message_handler.rs +++ b/core/editor/src/document/document_message_handler.rs @@ -8,7 +8,7 @@ use std::collections::VecDeque; #[derive(PartialEq, Clone, Debug)] pub enum DocumentMessage { DispatchOperation(DocumentOperation), - SelectLayer(Vec), + SelectLayers(Vec>), DeleteLayer(Vec), AddFolder(Vec), RenameLayer(Vec, String), @@ -49,6 +49,13 @@ impl DocumentMessageHandler { document_responses.retain(|response| !matches!(response, DocumentResponse::DocumentChanged)); document_responses.len() != len } + fn handle_folder_changed(&mut self, path: Vec) -> Option { + let document = self.active_document_mut(); + document.layer_data(&path).expanded.then(|| { + let children = document.layer_panel(path.as_slice()).expect("The provided Path was not valid"); + FrontendMessage::ExpandFolder { path, children }.into() + }) + } } impl Default for DocumentMessageHandler { @@ -84,6 +91,17 @@ impl MessageHandler for DocumentMessageHandler { ToggleLayerVisibility(path) => { responses.push_back(DocumentOperation::ToggleVisibility { path }.into()); } + ToggleLayerExpansion(path) => { + self.active_document_mut().layer_data(&path).expanded ^= true; + responses.extend(self.handle_folder_changed(path)); + } + SelectLayers(paths) => { + for path in paths { + self.active_document_mut().layer_data(&path).selected ^= true; + responses.extend(self.handle_folder_changed(path)); + // TODO: Add deduplication + } + } Undo => { // this is a temporary fix and will be addressed by #123 if let Some(id) = self.active_document().document.root.list_layers().last() { @@ -93,7 +111,15 @@ impl MessageHandler for DocumentMessageHandler { DispatchOperation(op) => { if let Ok(Some(mut document_responses)) = self.active_document_mut().document.handle_operation(op) { let canvas_dirty = self.filter_document_responses(&mut document_responses); - responses.extend(document_responses.into_iter().map(Into::into)); + responses.extend( + document_responses + .into_iter() + .map(|response| match response { + DocumentResponse::FolderChanged { path } => self.handle_folder_changed(path), + DocumentResponse::DocumentChanged => unreachable!(), + }) + .flatten(), + ); if canvas_dirty { responses.push_back(RenderDocument.into()) } diff --git a/core/editor/src/document/mod.rs b/core/editor/src/document/mod.rs index 5ded8ad5..940b4ab1 100644 --- a/core/editor/src/document/mod.rs +++ b/core/editor/src/document/mod.rs @@ -2,7 +2,7 @@ mod document_file; mod document_message_handler; #[doc(inline)] -pub use document_file::Document; +pub use document_file::{Document, LayerData}; #[doc(inline)] pub use document_message_handler::{DocumentMessage, DocumentMessageDiscriminant, DocumentMessageHandler}; diff --git a/core/editor/src/frontend/frontend_message_handler.rs b/core/editor/src/frontend/frontend_message_handler.rs index fc81e7b1..9d2264b0 100644 --- a/core/editor/src/frontend/frontend_message_handler.rs +++ b/core/editor/src/frontend/frontend_message_handler.rs @@ -1,5 +1,5 @@ +use crate::frontend::layer_panel::LayerPanelEntry; use crate::message_prelude::*; -use document_core::{response::LayerPanelEntry, DocumentResponse, LayerId}; use serde::{Deserialize, Serialize}; pub type Callback = Box; @@ -16,22 +16,6 @@ pub enum FrontendMessage { DisableTextInput, } -impl From for Message { - fn from(response: DocumentResponse) -> Self { - let frontend: FrontendMessage = response.into(); - frontend.into() - } -} -impl From for FrontendMessage { - fn from(response: DocumentResponse) -> Self { - match response { - DocumentResponse::ExpandFolder { path, children } => Self::ExpandFolder { path, children }, - DocumentResponse::CollapseFolder { path } => Self::CollapseFolder { path }, - _ => unimplemented!("The frontend does not handle {:?}", response), - } - } -} - pub struct FrontendMessageHandler { callback: crate::Callback, } diff --git a/core/editor/src/frontend/layer_panel.rs b/core/editor/src/frontend/layer_panel.rs new file mode 100644 index 00000000..f60f2156 --- /dev/null +++ b/core/editor/src/frontend/layer_panel.rs @@ -0,0 +1,55 @@ +use crate::document::LayerData; +use document_core::{layers::LayerDataTypes, LayerId}; +use serde::{Deserialize, Serialize}; +use std::fmt; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub struct LayerPanelEntry { + pub name: String, + pub visible: bool, + pub layer_type: LayerType, + pub layer_data: LayerData, + pub path: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub enum LayerType { + Folder, + Shape, + Circle, + Rect, + Line, + PolyLine, + Ellipse, +} + +impl fmt::Display for LayerType { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + let name = match self { + LayerType::Folder => "Folder", + LayerType::Shape => "Shape", + LayerType::Rect => "Rectangle", + LayerType::Line => "Line", + LayerType::Circle => "Circle", + LayerType::PolyLine => "Polyline", + LayerType::Ellipse => "Ellipse", + }; + + formatter.write_str(name) + } +} + +impl From<&LayerDataTypes> for LayerType { + fn from(data: &LayerDataTypes) -> Self { + use LayerDataTypes::*; + match data { + Folder(_) => LayerType::Folder, + Shape(_) => LayerType::Shape, + Circle(_) => LayerType::Circle, + Rect(_) => LayerType::Rect, + Line(_) => LayerType::Line, + PolyLine(_) => LayerType::PolyLine, + Ellipse(_) => LayerType::Ellipse, + } + } +} diff --git a/core/editor/src/frontend/mod.rs b/core/editor/src/frontend/mod.rs index 15fcc7a0..be966411 100644 --- a/core/editor/src/frontend/mod.rs +++ b/core/editor/src/frontend/mod.rs @@ -1,3 +1,4 @@ pub mod frontend_message_handler; +pub mod layer_panel; pub use frontend_message_handler::{Callback, FrontendMessage, FrontendMessageDiscriminant, FrontendMessageHandler}; diff --git a/core/editor/src/lib.rs b/core/editor/src/lib.rs index 8fbdf00b..18717e05 100644 --- a/core/editor/src/lib.rs +++ b/core/editor/src/lib.rs @@ -65,6 +65,7 @@ pub mod message_prelude { pub use super::tool::tools::rectangle::{RectangleMessage, RectangleMessageDiscriminant}; pub use super::tool::tools::select::{SelectMessage, SelectMessageDiscriminant}; pub use super::tool::tools::shape::{ShapeMessage, ShapeMessageDiscriminant}; + pub use crate::LayerId; pub use graphite_proc_macros::*; pub use std::collections::VecDeque; }