mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-21 21:48:12 +08:00
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:
committed by
Keavon Chambers
co-authored by
Keavon Chambers
parent
d2b0411295
commit
1594b9c61d
@@ -0,0 +1,5 @@
|
||||
use crate::color::Color;
|
||||
|
||||
// RENDERING
|
||||
pub const LAYER_OUTLINE_STROKE_COLOR: Color = Color::BLACK;
|
||||
pub const LAYER_OUTLINE_STROKE_WIDTH: f32 = 1.;
|
||||
@@ -8,7 +8,7 @@ use glam::{DAffine2, DVec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
layers::{self, Folder, Layer, LayerData, LayerDataType, Shape},
|
||||
layers::{self, style::ViewMode, Folder, Layer, LayerData, LayerDataType, Shape},
|
||||
DocumentError, DocumentResponse, LayerId, Operation, Quad,
|
||||
};
|
||||
|
||||
@@ -36,8 +36,8 @@ impl Document {
|
||||
}
|
||||
|
||||
/// Wrapper around render, that returns the whole document as a Response.
|
||||
pub fn render_root(&mut self) -> String {
|
||||
self.root.render(&mut vec![]);
|
||||
pub fn render_root(&mut self, mode: ViewMode) -> String {
|
||||
self.root.render(&mut vec![], mode);
|
||||
self.root.cache.clone()
|
||||
}
|
||||
|
||||
@@ -203,6 +203,28 @@ impl Document {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Visit each layer recursively, applies modify_shape to each non-overlay Shape
|
||||
pub fn visit_all_shapes<F: FnMut(&mut Shape)>(layer: &mut Layer, modify_shape: &mut F) -> bool {
|
||||
match layer.data {
|
||||
LayerDataType::Shape(ref mut shape) => {
|
||||
if !layer.overlay {
|
||||
modify_shape(shape);
|
||||
|
||||
// This layer should be updated on next render pass
|
||||
layer.cache_dirty = true;
|
||||
}
|
||||
}
|
||||
LayerDataType::Folder(ref mut folder) => {
|
||||
for sub_layer in folder.layers_mut() {
|
||||
if Document::visit_all_shapes(sub_layer, modify_shape) {
|
||||
layer.cache_dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
layer.cache_dirty
|
||||
}
|
||||
|
||||
/// 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.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use glam::DVec2;
|
||||
|
||||
use crate::{DocumentError, LayerId, Quad};
|
||||
use crate::{layers::style::ViewMode, DocumentError, LayerId, Quad};
|
||||
|
||||
use super::{Layer, LayerData, LayerDataType};
|
||||
|
||||
@@ -15,9 +15,9 @@ pub struct Folder {
|
||||
}
|
||||
|
||||
impl LayerData for Folder {
|
||||
fn render(&mut self, svg: &mut String, transforms: &mut Vec<glam::DAffine2>) {
|
||||
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));
|
||||
let _ = writeln!(svg, "{}", layer.render(transforms, view_mode));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod style;
|
||||
use style::ViewMode;
|
||||
|
||||
use glam::DAffine2;
|
||||
use glam::{DMat2, DVec2};
|
||||
@@ -18,7 +19,7 @@ use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Write;
|
||||
|
||||
pub trait LayerData {
|
||||
fn render(&mut self, svg: &mut String, transforms: &mut Vec<glam::DAffine2>);
|
||||
fn render(&mut self, svg: &mut String, transforms: &mut Vec<glam::DAffine2>, view_mode: ViewMode);
|
||||
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>);
|
||||
fn bounding_box(&self, transform: glam::DAffine2) -> Option<[DVec2; 2]>;
|
||||
}
|
||||
@@ -46,12 +47,14 @@ impl LayerDataType {
|
||||
}
|
||||
|
||||
impl LayerData for LayerDataType {
|
||||
fn render(&mut self, svg: &mut String, transforms: &mut Vec<glam::DAffine2>) {
|
||||
self.inner_mut().render(svg, transforms)
|
||||
fn render(&mut self, svg: &mut String, transforms: &mut Vec<glam::DAffine2>, view_mode: ViewMode) {
|
||||
self.inner_mut().render(svg, transforms, view_mode)
|
||||
}
|
||||
|
||||
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>) {
|
||||
self.inner().intersects_quad(quad, path, intersections)
|
||||
}
|
||||
|
||||
fn bounding_box(&self, transform: glam::DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.inner().bounding_box(transform)
|
||||
}
|
||||
@@ -102,14 +105,14 @@ impl Layer {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&mut self, transforms: &mut Vec<DAffine2>) -> &str {
|
||||
pub fn render(&mut self, transforms: &mut Vec<DAffine2>, view_mode: ViewMode) -> &str {
|
||||
if !self.visible {
|
||||
return "";
|
||||
}
|
||||
if self.cache_dirty {
|
||||
transforms.push(self.transform);
|
||||
self.thumbnail_cache.clear();
|
||||
self.data.render(&mut self.thumbnail_cache, transforms);
|
||||
self.data.render(&mut self.thumbnail_cache, transforms, if self.overlay { ViewMode::Normal } else { view_mode });
|
||||
|
||||
self.cache.clear();
|
||||
let _ = writeln!(self.cache, r#"<g transform="matrix("#);
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
use glam::DAffine2;
|
||||
use glam::DMat2;
|
||||
use glam::DVec2;
|
||||
|
||||
use kurbo::Affine;
|
||||
use kurbo::BezPath;
|
||||
use kurbo::Shape as KurboShape;
|
||||
|
||||
use crate::intersection::intersect_quad_bez_path;
|
||||
use crate::layers::{
|
||||
style,
|
||||
style::{PathStyle, ViewMode},
|
||||
LayerData,
|
||||
};
|
||||
use crate::LayerId;
|
||||
use crate::Quad;
|
||||
use kurbo::BezPath;
|
||||
|
||||
use super::style;
|
||||
use super::style::PathStyle;
|
||||
use super::LayerData;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Write;
|
||||
@@ -30,9 +30,9 @@ pub struct Shape {
|
||||
}
|
||||
|
||||
impl LayerData for Shape {
|
||||
fn render(&mut self, svg: &mut String, transforms: &mut Vec<DAffine2>) {
|
||||
fn render(&mut self, svg: &mut String, transforms: &mut Vec<DAffine2>, view_mode: ViewMode) {
|
||||
let mut path = self.path.clone();
|
||||
let transform = self.transform(transforms);
|
||||
let transform = self.transform(transforms, view_mode);
|
||||
let inverse = transform.inverse();
|
||||
if !inverse.is_finite() {
|
||||
let _ = write!(svg, "<!-- SVG shape has an invalid transform -->");
|
||||
@@ -45,7 +45,7 @@ impl LayerData for Shape {
|
||||
let _ = svg.write_str(&(entry.to_string() + if i != 5 { "," } else { "" }));
|
||||
});
|
||||
let _ = svg.write_str(r#")">"#);
|
||||
let _ = write!(svg, r#"<path d="{}" {} />"#, path.to_svg(), self.style.render());
|
||||
let _ = write!(svg, r#"<path d="{}" {} />"#, path.to_svg(), self.style.render(view_mode));
|
||||
let _ = svg.write_str("</g>");
|
||||
}
|
||||
|
||||
@@ -69,10 +69,11 @@ impl LayerData for Shape {
|
||||
}
|
||||
|
||||
impl Shape {
|
||||
pub fn transform(&self, transforms: &[DAffine2]) -> DAffine2 {
|
||||
let start = match self.render_index {
|
||||
-1 => 0,
|
||||
x => (transforms.len() as i32 - x).max(0) as usize,
|
||||
pub fn transform(&self, transforms: &[DAffine2], mode: ViewMode) -> DAffine2 {
|
||||
let start = match (mode, self.render_index) {
|
||||
(ViewMode::Outline, _) => 0,
|
||||
(_, -1) => 0,
|
||||
(_, x) => (transforms.len() as i32 - x).max(0) as usize,
|
||||
};
|
||||
transforms.iter().skip(start).cloned().reduce(|a, b| a * b).unwrap_or(DAffine2::IDENTITY)
|
||||
}
|
||||
@@ -82,7 +83,7 @@ impl Shape {
|
||||
path: bez_path,
|
||||
style,
|
||||
render_index: 1,
|
||||
solid: solid,
|
||||
solid,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,29 @@
|
||||
use crate::color::Color;
|
||||
use crate::consts::{LAYER_OUTLINE_STROKE_COLOR, LAYER_OUTLINE_STROKE_WIDTH};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const OPACITY_PRECISION: usize = 3;
|
||||
|
||||
fn format_opacity(name: &str, opacity: f32) -> String {
|
||||
if (opacity - 1.).abs() > 10f32.powi(-(OPACITY_PRECISION as i32)) {
|
||||
if (opacity - 1.).abs() > 10_f32.powi(-(OPACITY_PRECISION as i32)) {
|
||||
format!(r#" {}-opacity="{:.precision$}""#, name, opacity, precision = OPACITY_PRECISION)
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
|
||||
pub enum ViewMode {
|
||||
Normal,
|
||||
Outline,
|
||||
Pixels,
|
||||
}
|
||||
impl Default for ViewMode {
|
||||
fn default() -> Self {
|
||||
ViewMode::Normal
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct Fill {
|
||||
@@ -22,7 +36,7 @@ impl Fill {
|
||||
pub fn color(&self) -> Option<Color> {
|
||||
self.color
|
||||
}
|
||||
pub fn none() -> Self {
|
||||
pub const fn none() -> Self {
|
||||
Self { color: None }
|
||||
}
|
||||
pub fn render(&self) -> String {
|
||||
@@ -41,7 +55,7 @@ pub struct Stroke {
|
||||
}
|
||||
|
||||
impl Stroke {
|
||||
pub fn new(color: Color, width: f32) -> Self {
|
||||
pub const fn new(color: Color, width: f32) -> Self {
|
||||
Self { color, width }
|
||||
}
|
||||
pub fn color(&self) -> Color {
|
||||
@@ -83,17 +97,18 @@ impl PathStyle {
|
||||
pub fn clear_stroke(&mut self) {
|
||||
self.stroke = None;
|
||||
}
|
||||
pub fn render(&self) -> String {
|
||||
format!(
|
||||
"{}{}",
|
||||
match self.fill {
|
||||
Some(fill) => fill.render(),
|
||||
None => String::new(),
|
||||
},
|
||||
match self.stroke {
|
||||
Some(stroke) => stroke.render(),
|
||||
None => String::new(),
|
||||
},
|
||||
)
|
||||
|
||||
pub fn render(&self, view_mode: ViewMode) -> String {
|
||||
let fill_attribute = match (view_mode, self.fill) {
|
||||
(ViewMode::Outline, _) => Fill::none().render(),
|
||||
(_, Some(fill)) => fill.render(),
|
||||
(_, None) => String::new(),
|
||||
};
|
||||
let stroke_attribute = match (view_mode, self.stroke) {
|
||||
(ViewMode::Outline, _) => Stroke::new(LAYER_OUTLINE_STROKE_COLOR, LAYER_OUTLINE_STROKE_WIDTH).render(),
|
||||
(_, Some(stroke)) => stroke.render(),
|
||||
(_, None) => String::new(),
|
||||
};
|
||||
format!("{}{}", fill_attribute, stroke_attribute)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod color;
|
||||
pub mod consts;
|
||||
pub mod document;
|
||||
pub mod intersection;
|
||||
pub mod layers;
|
||||
|
||||
Reference in New Issue
Block a user