use layers::PolyLine; use crate::{ layers::{self, Folder, Layer, LayerData, LayerDataTypes, Line, Rect, Shape}, response::{LayerPanelEntry, LayerType}, DocumentError, DocumentResponse, LayerId, Operation, }; #[derive(Debug, Clone, PartialEq)] pub struct Document { pub root: layers::Folder, pub work: Folder, pub work_mount_path: Vec, pub work_operations: Vec, pub work_mounted: bool, } impl Default for Document { fn default() -> Self { Self { root: Folder::default(), work: Folder::default(), work_mount_path: Vec::new(), work_operations: Vec::new(), work_mounted: false, } } } fn split_path(path: &[LayerId]) -> Result<(&[LayerId], LayerId), DocumentError> { let id = path.last().ok_or(DocumentError::InvalidPath)?; let folder_path = &path[0..path.len() - 1]; Ok((folder_path, *id)) } impl Document { /// Renders the layer graph with the root `path` as an SVG string. /// This operation merges currently mounted folder and document_folder /// contents together. pub fn render(&self, path: &mut Vec) -> String { if !self.work_mount_path.as_slice().starts_with(path) { match &self.layer(path).unwrap().data { LayerDataTypes::Folder(_) => (), element => { path.pop(); return element.render(); } } } if path.as_slice() == self.work_mount_path { let mut out = self.document_folder(path).unwrap().render(); out += self.work.render().as_str(); path.pop(); return out; } let mut out = String::with_capacity(30); for element in self.folder(path).unwrap().layer_ids.iter() { path.push(*element); out += self.render(path).as_str(); } out } /// Wrapper around render, that returns the whole document as a Response. pub fn render_root(&self) -> DocumentResponse { DocumentResponse::UpdateCanvas { document: self.render(&mut vec![]) } } fn is_mounted(&self, mount_path: &[LayerId], path: &[LayerId]) -> bool { path.starts_with(mount_path) && self.work_mounted } /// Returns a reference to the requested folder. Fails if the path does not exist, /// or if the requested layer is not of type folder. /// This function respects mounted folders and will thus not contain the layers already /// present in the document if a temporary folder is mounted on top. pub fn folder(&self, mut path: &[LayerId]) -> Result<&Folder, DocumentError> { let mut root = &self.root; if self.is_mounted(self.work_mount_path.as_slice(), path) { path = &path[self.work_mount_path.len()..]; root = &self.work; } for id in path { root = root.folder(*id).ok_or(DocumentError::LayerNotFound)?; } Ok(root) } /// Returns a mutable reference to the requested folder. Fails if the path does not exist, /// or if the requested layer is not of type folder. /// This function respects mounted folders and will thus not contain the layers already /// present in the document if a temporary folder is mounted on top. pub fn folder_mut(&mut self, mut path: &[LayerId]) -> Result<&mut Folder, DocumentError> { let mut root = if self.is_mounted(self.work_mount_path.as_slice(), path) { path = &path[self.work_mount_path.len()..]; &mut self.work } else { &mut self.root }; for id in path { root = root.folder_mut(*id).ok_or(DocumentError::LayerNotFound)?; } Ok(root) } /// Returns a reference to the requested folder. Fails if the path does not exist, /// or if the requested layer is not of type folder. /// This function does **not** respect mounted folders and will always return the current /// state of the document, disregarding any temporary modifications. pub fn document_folder(&self, path: &[LayerId]) -> Result<&Folder, DocumentError> { let mut root = &self.root; for id in path { root = root.folder(*id).ok_or(DocumentError::LayerNotFound)?; } Ok(root) } /// Returns a mutable reference to the requested folder. Fails if the path does not exist, /// or if the requested layer is not of type folder. /// This function does **not** respect mounted folders and will always return the current /// state of the document, disregarding any temporary modifications. pub fn document_folder_mut(&mut self, path: &[LayerId]) -> Result<&mut Folder, DocumentError> { let mut root = &mut self.root; for id in path { root = root.folder_mut(*id).ok_or(DocumentError::LayerNotFound)?; } Ok(root) } /// Returns a reference to the layer struct at the specified `path`. pub fn layer(&self, path: &[LayerId]) -> Result<&Layer, DocumentError> { let (path, id) = split_path(path)?; self.folder(path)?.layer(id).ok_or(DocumentError::LayerNotFound) } /// Returns a mutable reference to the layer struct at the specified `path`. pub fn layer_mut(&mut self, path: &[LayerId]) -> Result<&mut Layer, DocumentError> { let (path, id) = split_path(path)?; self.folder_mut(path)?.layer_mut(id).ok_or(DocumentError::LayerNotFound) } /// Replaces the layer at the specified `path` with `layer`. pub fn set_layer(&mut self, path: &[LayerId], layer: Layer) -> Result<(), DocumentError> { let mut folder = &mut self.root; if let Ok((path, id)) = split_path(path) { folder = self.folder_mut(path)?; if let Some(folder_layer) = folder.layer_mut(id) { *folder_layer = layer; return Ok(()); } } folder.add_layer(layer, -1).ok_or(DocumentError::IndexOutOfBounds)?; Ok(()) } /// Adds a new layer to the folder specified by `path`. /// Passing a negative `insert_index` indexes relative to the end. /// -1 is equivalent to adding the layer to the top. pub fn add_layer(&mut self, path: &[LayerId], layer: Layer, insert_index: isize) -> Result { let folder = self.folder_mut(path)?; folder.add_layer(layer, insert_index).ok_or(DocumentError::IndexOutOfBounds) } /// Deletes the layer specified by `path`. pub fn delete(&mut self, path: &[LayerId]) -> Result<(), DocumentError> { let (path, id) = split_path(path)?; self.document_folder_mut(path)?.remove_layer(id)?; 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 l_type = |layer: &LayerDataTypes| match layer { LayerDataTypes::Folder(_) => LayerType::Folder, _ => LayerType::Shape, }; let translate = |layer: &Layer| LayerPanelEntry { name: layer.name.clone().unwrap_or_else(|| String::from("UnnamedFolder")), visible: layer.visible, layer_type: l_type(&layer.data), }; let entries = folder.layers().iter().map(|layer| translate(layer)).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> { self.work_operations.push(operation.clone()); let responses = match operation { Operation::AddCircle { path, insert_index, cx, cy, r, style } => { self.add_layer(&path, Layer::new(LayerDataTypes::Circle(layers::Circle::new(kurbo::Point::new(cx, cy), r, style))), insert_index)?; Some(vec![self.render_root()]) } Operation::AddRect { path, insert_index, x0, y0, x1, y1, style, } => { self.add_layer( &path, Layer::new(LayerDataTypes::Rect(Rect::new(kurbo::Point::new(x0, y0), kurbo::Point::new(x1, y1), style))), insert_index, )?; Some(vec![self.render_root()]) } Operation::AddLine { path, insert_index, x0, y0, x1, y1, style, } => { self.add_layer( &path, Layer::new(LayerDataTypes::Line(Line::new(kurbo::Point::new(x0, y0), kurbo::Point::new(x1, y1), style))), insert_index, )?; Some(vec![self.render_root()]) } Operation::AddPen { path, insert_index, points, style } => { let points: Vec = points.into_iter().map(|it| it.into()).collect(); let polyline = PolyLine::new(points, style); self.add_layer(&path, Layer::new(LayerDataTypes::PolyLine(polyline)), insert_index)?; Some(vec![self.render_root()]) } Operation::AddShape { path, insert_index, x0, y0, x1, y1, sides, style, } => { let s = Shape::new(kurbo::Point::new(x0, y0), kurbo::Vec2 { x: x0 - x1, y: y0 - y1 }, sides, style); self.add_layer(&path, Layer::new(LayerDataTypes::Shape(s)), insert_index)?; Some(vec![self.render_root()]) } Operation::DeleteLayer { path } => { self.delete(&path)?; Some(vec![self.render_root()]) } Operation::AddFolder { path } => { self.set_layer(&path, Layer::new(LayerDataTypes::Folder(Folder::default())))?; Some(vec![self.render_root()]) } Operation::MountWorkingFolder { path } => { self.work_operations.clear(); self.work_mount_path = path; self.work = Folder::default(); self.work_mounted = true; None } Operation::DiscardWorkingFolder => { self.work_operations.clear(); self.work_mount_path = vec![]; self.work = Folder::default(); self.work_mounted = false; None } Operation::ClearWorkingFolder => { self.work_operations.clear(); self.work = Folder::default(); None } Operation::CommitTransaction => { let mut ops = Vec::new(); let mut path: Vec = vec![]; std::mem::swap(&mut path, &mut self.work_mount_path); std::mem::swap(&mut ops, &mut self.work_operations); let len = ops.len() - 1; self.work_mounted = false; self.work_mount_path = vec![]; self.work = Folder::default(); let mut responses = vec![]; for operation in ops.into_iter().take(len) { if let Some(op_responses) = self.handle_operation(operation)? { for response in op_responses { if !matches!(response, DocumentResponse::UpdateCanvas { .. }) { responses.push(response); } } } } let children = self.layer_panel(path.as_slice())?; Some(vec![self.render_root(), DocumentResponse::ExpandFolder { path, children }]) } }; Ok(responses) } }