Files
Graphite/graphene/src/layers/folder.rs
caleb 1594b9c61d Implement outline view mode (#401)
* Created wasm binding to action's of the radio buttons which control the view mode
Added entry to DocumentMessage Enum

* draw in wireframe mode by changing parameters on each shape
added functions/changed behavior to do as above
not working yet
   - newly added shapes should be drawn in wireframe
   - setting fill to "none" on a path does not only draw an outline
      - maybe the stroke width is 0?

* Wire frame view mostly functional for ellipses
   - Need to implement for all shapes
   - BUG: shapes don't immediatley update upon changing view-mode

* Fixed: active document now updates after view mode swap

* The Pros:
   - wire frame mode effects all shapes correctly

The Cons:
   - wire frame mode effects everything, including things that maybe shouldn't be, like select boxes and pen lines

* wire frame view no longer effects overlay layers

* Fixed: While in wireframe view the pen tool will draw regular thickness lines.

* some commenting

* Fixed potential bug:
   In layer/file system with a Folder layer with a sub-layer that is also
   a Folder cache_dirty must be set in order for all shapes to update properly

* refactored code to use ViewMode enum names throughout

* Changed: All wireframe lines are blank
cargo fmt

* Wireframe thickness doesn't change as a result of zooming
   - Added DocumentMessage::ReRenderDocument, which marks layers as dirty and renders with the updated render-string
   - All "zoom" messages in the movement_handler send a re-render message
   - while in wireframe view, the "render-transform" of all shapes includes the root layer transform

Added getter/setter methods for graphene::Document::view_mode

* cargo fmt

* wireframe now has proper thickness after "Zoom Canvas to Fit all" action

* Refactored
   - Changed FrontendMessage::UpdateCanvas to RenderDocument message to allow for lazy evaluation
   - Created DocumentOperation::SetViewMode to be more consistent with existing code
   - removed log statement
   - Added constants for empty fill and thin-black stroke

* cargo fmt

* Removed ReRenderDocument message

* cargo fmt

* Fixes as suggested by TrueDoctor

* clean up merge
cargo fmt

* Refactor:
   moved view_mode to DocumentMessageHandler

* Polishing

* changed those two comments

* Remove unknown todo comment

Co-authored-by: Keavon Chambers <keavon@keavon.com>
2021-12-24 16:04:58 -07:00

126 lines
3.4 KiB
Rust

use glam::DVec2;
use crate::{layers::style::ViewMode, DocumentError, LayerId, Quad};
use super::{Layer, LayerData, LayerDataType};
use serde::{Deserialize, Serialize};
use std::fmt::Write;
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
pub struct Folder {
next_assignment_id: LayerId,
pub layer_ids: Vec<LayerId>,
layers: Vec<Layer>,
}
impl LayerData for Folder {
fn render(&mut self, svg: &mut String, transforms: &mut Vec<glam::DAffine2>, view_mode: ViewMode) {
for layer in &mut self.layers {
let _ = writeln!(svg, "{}", layer.render(transforms, view_mode));
}
}
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>) {
for (layer, layer_id) in self.layers().iter().zip(&self.layer_ids) {
path.push(*layer_id);
layer.intersects_quad(quad, path, intersections);
path.pop();
}
}
fn bounding_box(&self, transform: glam::DAffine2) -> Option<[DVec2; 2]> {
self.layers
.iter()
.filter_map(|layer| layer.data.bounding_box(transform * layer.transform))
.reduce(|a, b| [a[0].min(b[0]), a[1].max(b[1])])
}
}
impl Folder {
/// When a insertion id is provided, try to insert the layer with the given id.
/// If that id is already used, return None.
/// When no insertion id is provided, search for the next free id and insert it with that.
/// Negative values for insert_index represent distance from the end
pub fn add_layer(&mut self, layer: Layer, id: Option<LayerId>, insert_index: isize) -> Option<LayerId> {
let mut insert_index = insert_index as i128;
if insert_index < 0 {
insert_index = self.layers.len() as i128 + insert_index as i128 + 1;
}
if insert_index <= self.layers.len() as i128 && insert_index >= 0 {
if let Some(id) = id {
self.next_assignment_id = id;
}
if self.layer_ids.contains(&self.next_assignment_id) {
return None;
}
let id = self.next_assignment_id;
self.layers.insert(insert_index as usize, layer);
self.layer_ids.insert(insert_index as usize, id);
// Linear probing for collision avoidance
while self.layer_ids.contains(&self.next_assignment_id) {
self.next_assignment_id += 1;
}
Some(id)
} else {
None
}
}
pub fn remove_layer(&mut self, id: LayerId) -> Result<(), DocumentError> {
let pos = self.position_of_layer(id)?;
self.layers.remove(pos);
self.layer_ids.remove(pos);
Ok(())
}
/// Returns a list of layers in the folder
pub fn list_layers(&self) -> &[LayerId] {
self.layer_ids.as_slice()
}
pub fn layers(&self) -> &[Layer] {
self.layers.as_slice()
}
pub fn layers_mut(&mut self) -> &mut [Layer] {
self.layers.as_mut_slice()
}
pub fn layer(&self, id: LayerId) -> Option<&Layer> {
let pos = self.position_of_layer(id).ok()?;
Some(&self.layers[pos])
}
pub fn layer_mut(&mut self, id: LayerId) -> Option<&mut Layer> {
let pos = self.position_of_layer(id).ok()?;
Some(&mut self.layers[pos])
}
pub fn position_of_layer(&self, layer_id: LayerId) -> Result<usize, DocumentError> {
self.layer_ids.iter().position(|x| *x == layer_id).ok_or(DocumentError::LayerNotFound)
}
pub fn folder(&self, id: LayerId) -> Option<&Folder> {
match self.layer(id) {
Some(Layer {
data: LayerDataType::Folder(folder), ..
}) => Some(folder),
_ => None,
}
}
pub fn folder_mut(&mut self, id: LayerId) -> Option<&mut Folder> {
match self.layer_mut(id) {
Some(Layer {
data: LayerDataType::Folder(folder), ..
}) => Some(folder),
_ => None,
}
}
}