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>
This commit is contained in:
caleb
2021-12-24 16:04:58 -07:00
committed by Keavon Chambers
parent d2b0411295
commit 1594b9c61d
14 changed files with 158 additions and 68 deletions

View File

@@ -16,8 +16,8 @@ use kurbo::PathSeg;
use log::warn;
use serde::{Deserialize, Serialize};
use graphene::layers::BlendMode;
use graphene::{document::Document as GrapheneDocument, layers::LayerDataType, DocumentError, LayerId};
use graphene::layers::{style::ViewMode, BlendMode, LayerDataType};
use graphene::{document::Document as GrapheneDocument, DocumentError, LayerId};
use graphene::{DocumentResponse, Operation as DocumentOperation};
type DocumentSave = (GrapheneDocument, HashMap<Vec<LayerId>, LayerData>);
@@ -68,6 +68,7 @@ pub struct DocumentMessageHandler {
movement_handler: MovementMessageHandler,
transform_layer_handler: TransformLayerMessageHandler,
pub snapping_enabled: bool,
pub view_mode: ViewMode,
}
impl Default for DocumentMessageHandler {
@@ -83,6 +84,7 @@ impl Default for DocumentMessageHandler {
movement_handler: MovementMessageHandler::default(),
transform_layer_handler: TransformLayerMessageHandler::default(),
snapping_enabled: true,
view_mode: ViewMode::default(),
}
}
}
@@ -122,6 +124,9 @@ pub enum DocumentMessage {
ExportDocument,
SaveDocument,
RenderDocument,
DirtyRenderDocument,
DirtyRenderDocumentInOutlineView,
SetViewMode(ViewMode),
Undo,
Redo,
DocumentHistoryBackward,
@@ -162,6 +167,7 @@ impl DocumentMessageHandler {
movement_handler: MovementMessageHandler::default(),
transform_layer_handler: TransformLayerMessageHandler::default(),
snapping_enabled: true,
view_mode: ViewMode::default(),
};
document.graphene_document.root.transform = document.layerdata(&[]).calculate_offset_transform(ipp.viewport_bounds.size() / 2.);
document
@@ -479,7 +485,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
size.x,
size.y,
"\n",
self.graphene_document.render_root()
self.graphene_document.render_root(self.view_mode)
),
name,
}
@@ -570,6 +576,10 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
responses.push_front(DocumentOperation::DeleteLayer { path }.into());
}
}
SetViewMode(mode) => {
self.view_mode = mode;
responses.push_front(DocumentMessage::DirtyRenderDocument.into());
}
DuplicateSelectedLayers => {
self.backup(responses);
for path in self.selected_layers_sorted() {
@@ -659,7 +669,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
responses.push_back(FolderChanged(vec![]).into());
}
FolderChanged(path) => {
let _ = self.graphene_document.render_root();
let _ = self.graphene_document.render_root(self.view_mode);
responses.extend([LayerChanged(path).into(), DocumentStructureChanged.into()]);
}
DocumentStructureChanged => {
@@ -672,7 +682,6 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
(!overlay).then(|| FrontendMessage::UpdateLayer { data: entry }.into())
}));
}
DispatchOperation(op) => match self.graphene_document.handle_operation(&op) {
Ok(Some(document_responses)) => {
for response in document_responses {
@@ -702,7 +711,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
RenderDocument => {
responses.push_back(
FrontendMessage::UpdateCanvas {
document: self.graphene_document.render_root(),
document: self.graphene_document.render_root(self.view_mode),
}
.into(),
);
@@ -720,8 +729,8 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
let scrollbar_size = viewport_size / bounds_length;
let log = root_layerdata.scale.log2();
let ruler_inverval = if log < 0. { 100. * 2_f64.powf(-log.ceil()) } else { 100. / 2_f64.powf(log.ceil()) };
let ruler_spacing = ruler_inverval * root_layerdata.scale;
let ruler_interval = if log < 0. { 100. * 2_f64.powf(-log.ceil()) } else { 100. / 2_f64.powf(log.ceil()) };
let ruler_spacing = ruler_interval * root_layerdata.scale;
let ruler_origin = self.graphene_document.root.transform.transform_point2(DVec2::ZERO);
@@ -738,12 +747,22 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
FrontendMessage::UpdateRulers {
origin: ruler_origin.into(),
spacing: ruler_spacing,
interval: ruler_inverval,
interval: ruler_interval,
}
.into(),
);
}
DirtyRenderDocument => {
// Mark all non-overlay caches as dirty
GrapheneDocument::visit_all_shapes(&mut self.graphene_document.root, &mut |_| {});
responses.push_back(DocumentMessage::RenderDocument.into());
}
DirtyRenderDocumentInOutlineView => {
if self.view_mode == ViewMode::Outline {
responses.push_front(DocumentMessage::DirtyRenderDocument.into());
}
}
NudgeSelectedLayers(x, y) => {
self.backup(responses);
for path in self.selected_layers().map(|path| path.to_vec()) {

View File

@@ -1,10 +1,7 @@
use crate::consts::VIEWPORT_ROTATE_SNAP_INTERVAL;
use glam::{DAffine2, DVec2};
use graphene::layers::{BlendMode, LayerDataType};
use graphene::{
layers::{Layer, LayerData as DocumentLayerData},
LayerId,
};
use graphene::layers::{style::ViewMode, BlendMode, Layer, LayerData as DocumentLayerData, LayerDataType};
use graphene::LayerId;
use serde::{
ser::{SerializeSeq, SerializeStruct},
Deserialize, Serialize,
@@ -64,7 +61,7 @@ pub fn layer_panel_entry(layer_data: &LayerData, transform: DAffine2, layer: &La
let arr = arr.iter().map(|x| (*x).into()).collect::<Vec<(f64, f64)>>();
let mut thumbnail = String::new();
layer.data.clone().render(&mut thumbnail, &mut vec![transform]);
layer.data.clone().render(&mut thumbnail, &mut vec![transform], ViewMode::Normal);
let transform = transform.to_cols_array().iter().map(ToString::to_string).collect::<Vec<_>>().join(",");
let thumbnail = if let [(x_min, y_min), (x_max, y_max)] = arr.as_slice() {
format!(

View File

@@ -1,16 +1,15 @@
pub use super::layer_panel::*;
use super::LayerData;
pub use crate::document::layer_panel::*;
use crate::document::{DocumentMessage, LayerData};
use crate::message_prelude::*;
use crate::{
consts::{VIEWPORT_SCROLL_RATE, VIEWPORT_ZOOM_LEVELS, VIEWPORT_ZOOM_MOUSE_RATE, VIEWPORT_ZOOM_SCALE_MAX, VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_WHEEL_RATE},
input::{mouse::ViewportBounds, mouse::ViewportPosition, InputPreprocessor},
};
use glam::DVec2;
use graphene::document::Document;
use graphene::layers::style::ViewMode;
use graphene::Operation as DocumentOperation;
use glam::DVec2;
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
@@ -132,21 +131,27 @@ impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreproces
layerdata.scale = new.clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX);
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: layerdata.scale }.into());
responses.push_back(ToolMessage::SelectedLayersChanged.into());
responses.push_back(DocumentMessage::DirtyRenderDocumentInOutlineView.into());
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
}
IncreaseCanvasZoom => {
// TODO: Eliminate redundant code by making this call SetCanvasZoom
layerdata.scale = *VIEWPORT_ZOOM_LEVELS.iter().find(|scale| **scale > layerdata.scale).unwrap_or(&layerdata.scale);
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: layerdata.scale }.into());
responses.push_back(ToolMessage::SelectedLayersChanged.into());
responses.push_back(DocumentMessage::DirtyRenderDocumentInOutlineView.into());
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
}
DecreaseCanvasZoom => {
// TODO: Eliminate redundant code by making this call SetCanvasZoom
layerdata.scale = *VIEWPORT_ZOOM_LEVELS.iter().rev().find(|scale| **scale < layerdata.scale).unwrap_or(&layerdata.scale);
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: layerdata.scale }.into());
responses.push_back(ToolMessage::SelectedLayersChanged.into());
responses.push_back(DocumentMessage::DirtyRenderDocumentInOutlineView.into());
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
}
WheelCanvasZoom => {
// TODO: Eliminate redundant code by making this call SetCanvasZoom
let scroll = ipp.mouse.scroll_delta.scroll_delta();
let mouse = ipp.mouse.position;
let viewport_bounds = ipp.viewport_bounds.size();
@@ -165,6 +170,7 @@ impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreproces
layerdata.translation += transformed_delta;
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: layerdata.scale }.into());
responses.push_back(ToolMessage::SelectedLayersChanged.into());
responses.push_back(DocumentMessage::DirtyRenderDocumentInOutlineView.into());
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
}
WheelCanvasTranslate { use_y_as_x } => {
@@ -199,6 +205,7 @@ impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreproces
layerdata.scale *= new_scale;
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: layerdata.scale }.into());
responses.push_back(ToolMessage::SelectedLayersChanged.into());
responses.push_back(DocumentMessage::DirtyRenderDocumentInOutlineView.into());
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
}
}