Refactor font loading from per-document to the portfolio (#659)

* Cleanup default font loading

* Refactor fonts

* Fix menulist mouse navigation

* Format

* Formatting

* Move default font into consts.rs

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube
2022-05-26 16:27:33 -07:00
committed by Keavon Chambers
co-authored by Keavon Chambers
parent 7a9c3c958d
commit 469e40d4e9
60 changed files with 826 additions and 842 deletions
+2
View File
@@ -41,6 +41,7 @@ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerDetails), MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerDetails),
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerTreeStructure), MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerTreeStructure),
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateOpenDocumentsList), MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateOpenDocumentsList),
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontLoad),
MessageDiscriminant::Tool(ToolMessageDiscriminant::DocumentIsDirty), MessageDiscriminant::Tool(ToolMessageDiscriminant::DocumentIsDirty),
]; ];
@@ -108,6 +109,7 @@ impl Dispatcher {
( (
self.message_handlers.portfolio_message_handler.active_document(), self.message_handlers.portfolio_message_handler.active_document(),
&self.message_handlers.input_preprocessor_message_handler, &self.message_handlers.input_preprocessor_message_handler,
self.message_handlers.portfolio_message_handler.font_cache(),
), ),
&mut self.message_queue, &mut self.message_queue,
); );
+5
View File
@@ -1,4 +1,5 @@
use graphene::color::Color; use graphene::color::Color;
use graphene::layers::text_layer::Font;
// Viewport // Viewport
pub const VIEWPORT_ZOOM_WHEEL_RATE: f64 = 1. / 600.; pub const VIEWPORT_ZOOM_WHEEL_RATE: f64 = 1. / 600.;
@@ -65,6 +66,10 @@ pub const FILE_SAVE_SUFFIX: &str = ".graphite";
// Colors // Colors
pub const COLOR_ACCENT: Color = Color::from_unsafe(0x00 as f32 / 255., 0xA8 as f32 / 255., 0xFF as f32 / 255.); pub const COLOR_ACCENT: Color = Color::from_unsafe(0x00 as f32 / 255., 0xA8 as f32 / 255., 0xFF as f32 / 255.);
// Fonts
pub const DEFAULT_FONT_FAMILY: &str = "Merriweather";
pub const DEFAULT_FONT_STYLE: &str = "Normal (400)";
// Document // Document
pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.8"; // Remember to save a simple document and replace the test file at: editor\src\communication\graphite-test-document.graphite pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.8"; // Remember to save a simple document and replace the test file at: editor\src\communication\graphite-test-document.graphite
pub const VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR: f32 = 1.05; pub const VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR: f32 = 1.05;
@@ -1,5 +1,7 @@
use crate::{layout::widgets::*, message_prelude::FrontendMessage}; use crate::{layout::widgets::*, message_prelude::FrontendMessage};
use std::fmt::Write;
/// A dialog to notify users of an unfinished issue, optionally with an issue number. /// A dialog to notify users of an unfinished issue, optionally with an issue number.
pub struct ComingSoon { pub struct ComingSoon {
pub issue: Option<i32>, pub issue: Option<i32>,
@@ -16,7 +18,7 @@ impl PropertyHolder for ComingSoon {
..Default::default() ..Default::default()
}))]; }))];
if let Some(issue) = self.issue { if let Some(issue) = self.issue {
details += &format!("— but you can help add it!\nSee issue #{issue} on GitHub."); let _ = write!(details, "— but you can help add it!\nSee issue #{issue} on GitHub.");
buttons.push(WidgetHolder::new(Widget::TextButton(TextButton { buttons.push(WidgetHolder::new(Widget::TextButton(TextButton {
label: format!("Issue #{issue}"), label: format!("Issue #{issue}"),
min_width: 96, min_width: 96,
@@ -3,6 +3,7 @@ use crate::message_prelude::*;
use graphene::color::Color; use graphene::color::Color;
use graphene::document::Document as GrapheneDocument; use graphene::document::Document as GrapheneDocument;
use graphene::layers::style::{self, Fill, ViewMode}; use graphene::layers::style::{self, Fill, ViewMode};
use graphene::layers::text_layer::FontCache;
use graphene::DocumentResponse; use graphene::DocumentResponse;
use graphene::Operation as DocumentOperation; use graphene::Operation as DocumentOperation;
@@ -22,16 +23,16 @@ impl ArtboardMessageHandler {
} }
} }
impl MessageHandler<ArtboardMessage, ()> for ArtboardMessageHandler { impl MessageHandler<ArtboardMessage, &FontCache> for ArtboardMessageHandler {
#[remain::check] #[remain::check]
fn process_action(&mut self, message: ArtboardMessage, _: (), responses: &mut VecDeque<Message>) { fn process_action(&mut self, message: ArtboardMessage, font_cache: &FontCache, responses: &mut VecDeque<Message>) {
use ArtboardMessage::*; use ArtboardMessage::*;
#[remain::sorted] #[remain::sorted]
match message { match message {
// Sub-messages // Sub-messages
#[remain::unsorted] #[remain::unsorted]
DispatchOperation(operation) => match self.artboards_graphene_document.handle_operation(*operation) { DispatchOperation(operation) => match self.artboards_graphene_document.handle_operation(*operation, font_cache) {
Ok(Some(document_responses)) => { Ok(Some(document_responses)) => {
for response in document_responses { for response in document_responses {
match &response { match &response {
@@ -86,7 +87,7 @@ impl MessageHandler<ArtboardMessage, ()> for ArtboardMessageHandler {
} else { } else {
responses.push_back( responses.push_back(
FrontendMessage::UpdateDocumentArtboards { FrontendMessage::UpdateDocumentArtboards {
svg: self.artboards_graphene_document.render_root(ViewMode::Normal), svg: self.artboards_graphene_document.render_root(ViewMode::Normal, font_cache),
} }
.into(), .into(),
); );
-8
View File
@@ -72,18 +72,10 @@ pub enum DocumentMessage {
FolderChanged { FolderChanged {
affected_folder_path: Vec<LayerId>, affected_folder_path: Vec<LayerId>,
}, },
FontLoaded {
font_file_url: String,
data: Vec<u8>,
is_default: bool,
},
GroupSelectedLayers, GroupSelectedLayers,
LayerChanged { LayerChanged {
affected_layer_path: Vec<LayerId>, affected_layer_path: Vec<LayerId>,
}, },
LoadFont {
font_file_url: String,
},
MoveSelectedLayersTo { MoveSelectedLayersTo {
folder_path: Vec<LayerId>, folder_path: Vec<LayerId>,
insert_index: isize, insert_index: isize,
+47 -67
View File
@@ -23,6 +23,7 @@ use graphene::layers::blend_mode::BlendMode;
use graphene::layers::folder_layer::FolderLayer; use graphene::layers::folder_layer::FolderLayer;
use graphene::layers::layer_info::LayerDataType; use graphene::layers::layer_info::LayerDataType;
use graphene::layers::style::{Fill, ViewMode}; use graphene::layers::style::{Fill, ViewMode};
use graphene::layers::text_layer::FontCache;
use graphene::{DocumentError, DocumentResponse, LayerId, Operation as DocumentOperation}; use graphene::{DocumentError, DocumentResponse, LayerId, Operation as DocumentOperation};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
@@ -136,12 +137,12 @@ impl DocumentMessageHandler {
&& self.name.starts_with(DEFAULT_DOCUMENT_NAME) && self.name.starts_with(DEFAULT_DOCUMENT_NAME)
} }
fn select_layer(&mut self, path: &[LayerId]) -> Option<Message> { fn select_layer(&mut self, path: &[LayerId], font_cache: &FontCache) -> Option<Message> {
println!("Select_layer fail: {:?}", self.all_layers_sorted()); println!("Select_layer fail: {:?}", self.all_layers_sorted());
if let Some(layer) = self.layer_metadata.get_mut(path) { if let Some(layer) = self.layer_metadata.get_mut(path) {
layer.selected = true; layer.selected = true;
let data = self.layer_panel_entry(path.to_vec()).ok()?; let data = self.layer_panel_entry(path.to_vec(), font_cache).ok()?;
(!path.is_empty()).then(|| FrontendMessage::UpdateDocumentLayerDetails { data }.into()) (!path.is_empty()).then(|| FrontendMessage::UpdateDocumentLayerDetails { data }.into())
} else { } else {
log::warn!("Tried to select non existing layer {:?}", path); log::warn!("Tried to select non existing layer {:?}", path);
@@ -149,17 +150,17 @@ impl DocumentMessageHandler {
} }
} }
pub fn selected_visible_layers_bounding_box(&self) -> Option<[DVec2; 2]> { pub fn selected_visible_layers_bounding_box(&self, font_cache: &FontCache) -> Option<[DVec2; 2]> {
let paths = self.selected_visible_layers(); let paths = self.selected_visible_layers();
self.graphene_document.combined_viewport_bounding_box(paths) self.graphene_document.combined_viewport_bounding_box(paths, font_cache)
} }
pub fn artboard_bounding_box_and_transform(&self, path: &[LayerId]) -> Option<([DVec2; 2], DAffine2)> { pub fn artboard_bounding_box_and_transform(&self, path: &[LayerId], font_cache: &FontCache) -> Option<([DVec2; 2], DAffine2)> {
self.artboard_message_handler.artboards_graphene_document.bounding_box_and_transform(path).unwrap_or(None) self.artboard_message_handler.artboards_graphene_document.bounding_box_and_transform(path, font_cache).unwrap_or(None)
} }
/// Create a new vector shape representation with the underlying kurbo data, VectorManipulatorShape /// Create a new vector shape representation with the underlying kurbo data, VectorManipulatorShape
pub fn selected_visible_layers_vector_shapes(&self, responses: &mut VecDeque<Message>) -> Vec<VectorShape> { pub fn selected_visible_layers_vector_shapes(&self, responses: &mut VecDeque<Message>, font_cache: &FontCache) -> Vec<VectorShape> {
let shapes = self.selected_layers().filter_map(|path_to_shape| { let shapes = self.selected_layers().filter_map(|path_to_shape| {
let viewport_transform = self.graphene_document.generate_transform_relative_to_viewport(path_to_shape).ok()?; let viewport_transform = self.graphene_document.generate_transform_relative_to_viewport(path_to_shape).ok()?;
let layer = self.graphene_document.layer(path_to_shape); let layer = self.graphene_document.layer(path_to_shape);
@@ -172,13 +173,7 @@ impl DocumentMessageHandler {
// TODO: Create VectorManipulatorShape when creating a kurbo shape as a stopgap, rather than on each new selection // TODO: Create VectorManipulatorShape when creating a kurbo shape as a stopgap, rather than on each new selection
match &layer.ok()?.data { match &layer.ok()?.data {
LayerDataType::Shape(shape) => Some(VectorShape::new(path_to_shape.to_vec(), viewport_transform, &shape.path, shape.closed, responses)), LayerDataType::Shape(shape) => Some(VectorShape::new(path_to_shape.to_vec(), viewport_transform, &shape.path, shape.closed, responses)),
LayerDataType::Text(text) => Some(VectorShape::new( LayerDataType::Text(text) => Some(VectorShape::new(path_to_shape.to_vec(), viewport_transform, &text.to_bez_path_nonmut(font_cache), true, responses)),
path_to_shape.to_vec(),
viewport_transform,
&text.to_bez_path_nonmut(&self.graphene_document.font_cache),
true,
responses,
)),
_ => None, _ => None,
} }
}); });
@@ -230,16 +225,16 @@ impl DocumentMessageHandler {
} }
/// Returns the bounding boxes for all visible layers and artboards, optionally excluding any paths. /// Returns the bounding boxes for all visible layers and artboards, optionally excluding any paths.
pub fn bounding_boxes<'a>(&'a self, ignore_document: Option<&'a Vec<Vec<LayerId>>>, ignore_artboard: Option<LayerId>) -> impl Iterator<Item = [DVec2; 2]> + 'a { pub fn bounding_boxes<'a>(&'a self, ignore_document: Option<&'a Vec<Vec<LayerId>>>, ignore_artboard: Option<LayerId>, font_cache: &'a FontCache) -> impl Iterator<Item = [DVec2; 2]> + 'a {
self.visible_layers() self.visible_layers()
.filter(move |path| ignore_document.map_or(true, |ignore_document| !ignore_document.iter().any(|ig| ig.as_slice() == *path))) .filter(move |path| ignore_document.map_or(true, |ignore_document| !ignore_document.iter().any(|ig| ig.as_slice() == *path)))
.filter_map(|path| self.graphene_document.viewport_bounding_box(path).ok()?) .filter_map(|path| self.graphene_document.viewport_bounding_box(path, font_cache).ok()?)
.chain( .chain(
self.artboard_message_handler self.artboard_message_handler
.artboard_ids .artboard_ids
.iter() .iter()
.filter(move |&&id| Some(id) != ignore_artboard) .filter(move |&&id| Some(id) != ignore_artboard)
.filter_map(|&path| self.artboard_message_handler.artboards_graphene_document.viewport_bounding_box(&[path]).ok()?), .filter_map(|&path| self.artboard_message_handler.artboards_graphene_document.viewport_bounding_box(&[path], font_cache).ok()?),
) )
} }
@@ -431,26 +426,26 @@ impl DocumentMessageHandler {
} }
// TODO: This should probably take a slice not a vec, also why does this even exist when `layer_panel_entry_from_path` also exists? // TODO: This should probably take a slice not a vec, also why does this even exist when `layer_panel_entry_from_path` also exists?
pub fn layer_panel_entry(&mut self, path: Vec<LayerId>) -> Result<LayerPanelEntry, EditorError> { pub fn layer_panel_entry(&mut self, path: Vec<LayerId>, font_cache: &FontCache) -> Result<LayerPanelEntry, EditorError> {
let data: LayerMetadata = *self let data: LayerMetadata = *self
.layer_metadata .layer_metadata
.get_mut(&path) .get_mut(&path)
.ok_or_else(|| EditorError::Document(format!("Could not get layer metadata for {:?}", path)))?; .ok_or_else(|| EditorError::Document(format!("Could not get layer metadata for {:?}", path)))?;
let layer = self.graphene_document.layer(&path)?; let layer = self.graphene_document.layer(&path)?;
let entry = layer_panel_entry(&data, self.graphene_document.multiply_transforms(&path)?, layer, path, &self.graphene_document.font_cache); let entry = layer_panel_entry(&data, self.graphene_document.multiply_transforms(&path)?, layer, path, font_cache);
Ok(entry) Ok(entry)
} }
/// Returns a list of `LayerPanelEntry`s intended for display purposes. These don't contain /// Returns a list of `LayerPanelEntry`s intended for display purposes. These don't contain
/// any actual data, but rather attributes such as visibility and names of the layers. /// any actual data, but rather attributes such as visibility and names of the layers.
pub fn layer_panel(&mut self, path: &[LayerId]) -> Result<Vec<LayerPanelEntry>, EditorError> { pub fn layer_panel(&mut self, path: &[LayerId], font_cache: &FontCache) -> Result<Vec<LayerPanelEntry>, EditorError> {
let folder = self.graphene_document.folder(path)?; let folder = self.graphene_document.folder(path)?;
let paths: Vec<Vec<LayerId>> = folder.layer_ids.iter().map(|id| [path, &[*id]].concat()).collect(); let paths: Vec<Vec<LayerId>> = folder.layer_ids.iter().map(|id| [path, &[*id]].concat()).collect();
let entries = paths.iter().rev().filter_map(|path| self.layer_panel_entry_from_path(path)).collect(); let entries = paths.iter().rev().filter_map(|path| self.layer_panel_entry_from_path(path, font_cache)).collect();
Ok(entries) Ok(entries)
} }
pub fn layer_panel_entry_from_path(&self, path: &[LayerId]) -> Option<LayerPanelEntry> { pub fn layer_panel_entry_from_path(&self, path: &[LayerId], font_cache: &FontCache) -> Option<LayerPanelEntry> {
let layer_metadata = self.layer_metadata(path); let layer_metadata = self.layer_metadata(path);
let transform = self let transform = self
.graphene_document .graphene_document
@@ -458,7 +453,7 @@ impl DocumentMessageHandler {
.ok()?; .ok()?;
let layer = self.graphene_document.layer(path).ok()?; let layer = self.graphene_document.layer(path).ok()?;
Some(layer_panel_entry(layer_metadata, transform, layer, path.to_vec(), &self.graphene_document.font_cache)) Some(layer_panel_entry(layer_metadata, transform, layer, path.to_vec(), font_cache))
} }
/// When working with an insert index, deleting the layers may cause the insert index to point to a different location (if the layer being deleted was located before the insert index). /// When working with an insert index, deleting the layers may cause the insert index to point to a different location (if the layer being deleted was located before the insert index).
@@ -473,16 +468,16 @@ impl DocumentMessageHandler {
} }
/// Calculates the bounding box of all layers in the document /// Calculates the bounding box of all layers in the document
pub fn all_layer_bounds(&self) -> Option<[DVec2; 2]> { pub fn all_layer_bounds(&self, font_cache: &FontCache) -> Option<[DVec2; 2]> {
self.graphene_document.viewport_bounding_box(&[]).ok().flatten() self.graphene_document.viewport_bounding_box(&[], font_cache).ok().flatten()
} }
/// Calculates the document bounds used for scrolling and centring (the layer bounds or the artboard (if applicable)) /// Calculates the document bounds used for scrolling and centring (the layer bounds or the artboard (if applicable))
pub fn document_bounds(&self) -> Option<[DVec2; 2]> { pub fn document_bounds(&self, font_cache: &FontCache) -> Option<[DVec2; 2]> {
if self.artboard_message_handler.is_infinite_canvas() { if self.artboard_message_handler.is_infinite_canvas() {
self.all_layer_bounds() self.all_layer_bounds(font_cache)
} else { } else {
self.artboard_message_handler.artboards_graphene_document.viewport_bounding_box(&[]).ok().flatten() self.artboard_message_handler.artboards_graphene_document.viewport_bounding_box(&[], font_cache).ok().flatten()
} }
} }
@@ -523,13 +518,6 @@ impl DocumentMessageHandler {
} }
} }
// TODO: Loading the default font should happen on a per-application basis, not a per-document basis
pub fn load_default_font(&self, responses: &mut VecDeque<Message>) {
if !self.graphene_document.font_cache.has_default() {
responses.push_back(FrontendMessage::TriggerFontLoadDefault.into())
}
}
pub fn update_document_widgets(&self, responses: &mut VecDeque<Message>) { pub fn update_document_widgets(&self, responses: &mut VecDeque<Message>) {
let document_bar_layout = WidgetLayout::new(vec![LayoutRow::Row { let document_bar_layout = WidgetLayout::new(vec![LayoutRow::Row {
widgets: vec![ widgets: vec![
@@ -719,7 +707,7 @@ impl DocumentMessageHandler {
); );
} }
pub fn update_layer_tree_options_bar_widgets(&self, responses: &mut VecDeque<Message>) { pub fn update_layer_tree_options_bar_widgets(&self, responses: &mut VecDeque<Message>, font_cache: &FontCache) {
let mut opacity = None; let mut opacity = None;
let mut opacity_is_mixed = false; let mut opacity_is_mixed = false;
@@ -728,7 +716,7 @@ impl DocumentMessageHandler {
self.layer_metadata self.layer_metadata
.keys() .keys()
.filter_map(|path| self.layer_panel_entry_from_path(path)) .filter_map(|path| self.layer_panel_entry_from_path(path, font_cache))
.filter(|layer_panel_entry| layer_panel_entry.layer_metadata.selected) .filter(|layer_panel_entry| layer_panel_entry.layer_metadata.selected)
.flat_map(|layer_panel_entry| self.graphene_document.layer(layer_panel_entry.path.as_slice())) .flat_map(|layer_panel_entry| self.graphene_document.layer(layer_panel_entry.path.as_slice()))
.for_each(|layer| { .for_each(|layer| {
@@ -834,16 +822,16 @@ impl DocumentMessageHandler {
} }
} }
impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for DocumentMessageHandler { impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCache)> for DocumentMessageHandler {
#[remain::check] #[remain::check]
fn process_action(&mut self, message: DocumentMessage, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) { fn process_action(&mut self, message: DocumentMessage, (ipp, font_cache): (&InputPreprocessorMessageHandler, &FontCache), responses: &mut VecDeque<Message>) {
use DocumentMessage::*; use DocumentMessage::*;
#[remain::sorted] #[remain::sorted]
match message { match message {
// Sub-messages // Sub-messages
#[remain::unsorted] #[remain::unsorted]
DispatchOperation(op) => match self.graphene_document.handle_operation(*op) { DispatchOperation(op) => match self.graphene_document.handle_operation(*op, font_cache) {
Ok(Some(document_responses)) => { Ok(Some(document_responses)) => {
for response in document_responses { for response in document_responses {
match &response { match &response {
@@ -877,7 +865,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
}, },
#[remain::unsorted] #[remain::unsorted]
Artboard(message) => { Artboard(message) => {
self.artboard_message_handler.process_action(message, (), responses); self.artboard_message_handler.process_action(message, font_cache, responses);
} }
#[remain::unsorted] #[remain::unsorted]
Movement(message) => { Movement(message) => {
@@ -885,13 +873,13 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
} }
#[remain::unsorted] #[remain::unsorted]
Overlays(message) => { Overlays(message) => {
self.overlays_message_handler.process_action(message, self.overlays_visible, responses); self.overlays_message_handler.process_action(message, (self.overlays_visible, font_cache), responses);
// responses.push_back(OverlaysMessage::RenderOverlays.into()); // responses.push_back(OverlaysMessage::RenderOverlays.into());
} }
#[remain::unsorted] #[remain::unsorted]
TransformLayers(message) => { TransformLayers(message) => {
self.transform_layer_handler self.transform_layer_handler
.process_action(message, (&mut self.layer_metadata, &mut self.graphene_document, ipp), responses); .process_action(message, (&mut self.layer_metadata, &mut self.graphene_document, ipp, font_cache), responses);
} }
#[remain::unsorted] #[remain::unsorted]
PropertiesPanel(message) => { PropertiesPanel(message) => {
@@ -900,6 +888,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
PropertiesPanelMessageHandlerData { PropertiesPanelMessageHandlerData {
artwork_document: &self.graphene_document, artwork_document: &self.graphene_document,
artboard_document: &self.artboard_message_handler.artboards_graphene_document, artboard_document: &self.artboard_message_handler.artboards_graphene_document,
font_cache,
}, },
responses, responses,
); );
@@ -912,7 +901,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
} }
AddSelectedLayers { additional_layers } => { AddSelectedLayers { additional_layers } => {
for layer_path in &additional_layers { for layer_path in &additional_layers {
responses.extend(self.select_layer(layer_path)); responses.extend(self.select_layer(layer_path, font_cache));
} }
let selected_paths: Vec<Vec<u64>> = self.selected_layers().map(|path| path.to_vec()).collect(); let selected_paths: Vec<Vec<u64>> = self.selected_layers().map(|path| path.to_vec()).collect();
@@ -932,13 +921,13 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
responses.push_back(FolderChanged { affected_folder_path: vec![] }.into()); responses.push_back(FolderChanged { affected_folder_path: vec![] }.into());
responses.push_back(DocumentMessage::SelectionChanged.into()); responses.push_back(DocumentMessage::SelectionChanged.into());
self.update_layer_tree_options_bar_widgets(responses); self.update_layer_tree_options_bar_widgets(responses, font_cache);
} }
AlignSelectedLayers { axis, aggregate } => { AlignSelectedLayers { axis, aggregate } => {
self.backup(responses); self.backup(responses);
let (paths, boxes): (Vec<_>, Vec<_>) = self let (paths, boxes): (Vec<_>, Vec<_>) = self
.selected_layers() .selected_layers()
.filter_map(|path| self.graphene_document.viewport_bounding_box(path).ok()?.map(|b| (path, b))) .filter_map(|path| self.graphene_document.viewport_bounding_box(path, font_cache).ok()?.map(|b| (path, b)))
.unzip(); .unzip();
let axis = match axis { let axis = match axis {
@@ -946,7 +935,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
AlignAxis::Y => DVec2::Y, AlignAxis::Y => DVec2::Y,
}; };
let lerp = |bbox: &[DVec2; 2]| bbox[0].lerp(bbox[1], 0.5); let lerp = |bbox: &[DVec2; 2]| bbox[0].lerp(bbox[1], 0.5);
if let Some(combined_box) = self.graphene_document.combined_viewport_bounding_box(self.selected_layers()) { if let Some(combined_box) = self.graphene_document.combined_viewport_bounding_box(self.selected_layers(), font_cache) {
let aggregated = match aggregate { let aggregated = match aggregate {
AlignAggregate::Min => combined_box[0], AlignAggregate::Min => combined_box[0],
AlignAggregate::Max => combined_box[1], AlignAggregate::Max => combined_box[1],
@@ -1054,13 +1043,13 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
// Calculates the bounding box of the region to be exported // Calculates the bounding box of the region to be exported
let bbox = match bounds { let bbox = match bounds {
crate::frontend::utility_types::ExportBounds::AllArtwork => self.all_layer_bounds(), crate::frontend::utility_types::ExportBounds::AllArtwork => self.all_layer_bounds(font_cache),
crate::frontend::utility_types::ExportBounds::Artboard(id) => self crate::frontend::utility_types::ExportBounds::Artboard(id) => self
.artboard_message_handler .artboard_message_handler
.artboards_graphene_document .artboards_graphene_document
.layer(&[id]) .layer(&[id])
.ok() .ok()
.and_then(|layer| layer.aabounding_box(&self.graphene_document.font_cache)), .and_then(|layer| layer.aabounding_box(font_cache)),
} }
.unwrap_or_default(); .unwrap_or_default();
let size = bbox[1] - bbox[0]; let size = bbox[1] - bbox[0];
@@ -1071,7 +1060,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
false => file_name + file_suffix, false => file_name + file_suffix,
}; };
let rendered = self.graphene_document.render_root(self.view_mode); let rendered = self.graphene_document.render_root(self.view_mode, font_cache);
let document = format!( let document = format!(
r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="{} {} {} {}" width="{}px" height="{}">{}{}</svg>"#, r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="{} {} {} {}" width="{}px" height="{}">{}{}</svg>"#,
bbox[0].x, bbox[0].y, size.x, size.y, size.x, size.y, "\n", rendered bbox[0].x, bbox[0].y, size.x, size.y, size.x, size.y, "\n", rendered
@@ -1094,7 +1083,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
FlipAxis::X => DVec2::new(-1., 1.), FlipAxis::X => DVec2::new(-1., 1.),
FlipAxis::Y => DVec2::new(1., -1.), FlipAxis::Y => DVec2::new(1., -1.),
}; };
if let Some([min, max]) = self.graphene_document.combined_viewport_bounding_box(self.selected_layers()) { if let Some([min, max]) = self.graphene_document.combined_viewport_bounding_box(self.selected_layers(), font_cache) {
let center = (max + min) / 2.; let center = (max + min) / 2.;
let bbox_trans = DAffine2::from_translation(-center); let bbox_trans = DAffine2::from_translation(-center);
for path in self.selected_layers() { for path in self.selected_layers() {
@@ -1111,14 +1100,10 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
} }
} }
FolderChanged { affected_folder_path } => { FolderChanged { affected_folder_path } => {
let _ = self.graphene_document.render_root(self.view_mode); let _ = self.graphene_document.render_root(self.view_mode, font_cache);
let affected_layer_path = affected_folder_path; let affected_layer_path = affected_folder_path;
responses.extend([LayerChanged { affected_layer_path }.into(), DocumentStructureChanged.into()]); responses.extend([LayerChanged { affected_layer_path }.into(), DocumentStructureChanged.into()]);
} }
FontLoaded { font_file_url, data, is_default } => {
self.graphene_document.font_cache.insert(font_file_url, data, is_default);
responses.push_back(DocumentMessage::DirtyRenderDocument.into());
}
GroupSelectedLayers => { GroupSelectedLayers => {
let mut new_folder_path = self.graphene_document.shallowest_common_folder(self.selected_layers()).unwrap_or(&[]).to_vec(); let mut new_folder_path = self.graphene_document.shallowest_common_folder(self.selected_layers()).unwrap_or(&[]).to_vec();
@@ -1149,16 +1134,11 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
); );
} }
LayerChanged { affected_layer_path } => { LayerChanged { affected_layer_path } => {
if let Ok(layer_entry) = self.layer_panel_entry(affected_layer_path.clone()) { if let Ok(layer_entry) = self.layer_panel_entry(affected_layer_path.clone(), font_cache) {
responses.push_back(FrontendMessage::UpdateDocumentLayerDetails { data: layer_entry }.into()); responses.push_back(FrontendMessage::UpdateDocumentLayerDetails { data: layer_entry }.into());
} }
responses.push_back(PropertiesPanelMessage::CheckSelectedWasUpdated { path: affected_layer_path }.into()); responses.push_back(PropertiesPanelMessage::CheckSelectedWasUpdated { path: affected_layer_path }.into());
self.update_layer_tree_options_bar_widgets(responses); self.update_layer_tree_options_bar_widgets(responses, font_cache);
}
LoadFont { font_file_url } => {
if !self.graphene_document.font_cache.loaded_font(&font_file_url) {
responses.push_front(FrontendMessage::TriggerFontLoad { font_file_url }.into());
}
} }
MoveSelectedLayersTo { MoveSelectedLayersTo {
folder_path, folder_path,
@@ -1240,7 +1220,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
RenderDocument => { RenderDocument => {
responses.push_back( responses.push_back(
FrontendMessage::UpdateDocumentArtwork { FrontendMessage::UpdateDocumentArtwork {
svg: self.graphene_document.render_root(self.view_mode), svg: self.graphene_document.render_root(self.view_mode, font_cache),
} }
.into(), .into(),
); );
@@ -1250,7 +1230,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
let scale = 0.5 + ASYMPTOTIC_EFFECT + document_transform_scale * SCALE_EFFECT; let scale = 0.5 + ASYMPTOTIC_EFFECT + document_transform_scale * SCALE_EFFECT;
let viewport_size = ipp.viewport_bounds.size(); let viewport_size = ipp.viewport_bounds.size();
let viewport_mid = ipp.viewport_bounds.center(); let viewport_mid = ipp.viewport_bounds.center();
let [bounds1, bounds2] = self.document_bounds().unwrap_or([viewport_mid; 2]); let [bounds1, bounds2] = self.document_bounds(font_cache).unwrap_or([viewport_mid; 2]);
let bounds1 = bounds1.min(viewport_mid) - viewport_size * scale; let bounds1 = bounds1.min(viewport_mid) - viewport_size * scale;
let bounds2 = bounds2.max(viewport_mid) + viewport_size * scale; let bounds2 = bounds2.max(viewport_mid) + viewport_size * scale;
let bounds_length = (bounds2 - bounds1) * (1. + SCROLLBAR_SPACING); let bounds_length = (bounds2 - bounds1) * (1. + SCROLLBAR_SPACING);
@@ -1423,7 +1403,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
responses.push_back(LayerChanged { affected_layer_path: layer_path }.into()) responses.push_back(LayerChanged { affected_layer_path: layer_path }.into())
} }
SetLayerName { layer_path, name } => { SetLayerName { layer_path, name } => {
if let Some(layer) = self.layer_panel_entry_from_path(&layer_path) { if let Some(layer) = self.layer_panel_entry_from_path(&layer_path, font_cache) {
// Only save the history state if the name actually changed to something different // Only save the history state if the name actually changed to something different
if layer.name != name { if layer.name != name {
self.backup(responses); self.backup(responses);
@@ -1532,7 +1512,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
self.layer_metadata.insert(layer_path, layer_metadata); self.layer_metadata.insert(layer_path, layer_metadata);
} }
ZoomCanvasToFitAll => { ZoomCanvasToFitAll => {
if let Some(bounds) = self.document_bounds() { if let Some(bounds) = self.document_bounds(font_cache) {
responses.push_back( responses.push_back(
MovementMessage::FitViewportToBounds { MovementMessage::FitViewportToBounds {
bounds, bounds,
+4 -4
View File
@@ -1,6 +1,6 @@
use graphene::document::FontCache;
use graphene::layers::layer_info::{Layer, LayerData, LayerDataType}; use graphene::layers::layer_info::{Layer, LayerData, LayerDataType};
use graphene::layers::style::ViewMode; use graphene::layers::style::ViewMode;
use graphene::layers::text_layer::FontCache;
use graphene::LayerId; use graphene::LayerId;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
@@ -8,7 +8,7 @@ use serde::ser::SerializeStruct;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Copy)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Copy)]
pub struct LayerMetadata { pub struct LayerMetadata {
pub selected: bool, pub selected: bool,
pub expanded: bool, pub expanded: bool,
@@ -54,7 +54,7 @@ pub fn layer_panel_entry(layer_metadata: &LayerMetadata, transform: DAffine2, la
} }
} }
#[derive(Debug, Clone, Deserialize, PartialEq)] #[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
pub struct RawBuffer(Vec<u8>); pub struct RawBuffer(Vec<u8>);
impl From<Vec<u64>> for RawBuffer { impl From<Vec<u64>> for RawBuffer {
@@ -81,7 +81,7 @@ impl Serialize for RawBuffer {
} }
} }
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct LayerPanelEntry { pub struct LayerPanelEntry {
pub name: String, pub name: String,
pub visible: bool, pub visible: bool,
@@ -2,22 +2,23 @@ use crate::message_prelude::*;
use graphene::document::Document as GrapheneDocument; use graphene::document::Document as GrapheneDocument;
use graphene::layers::style::ViewMode; use graphene::layers::style::ViewMode;
use graphene::layers::text_layer::FontCache;
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct OverlaysMessageHandler { pub struct OverlaysMessageHandler {
pub overlays_graphene_document: GrapheneDocument, pub overlays_graphene_document: GrapheneDocument,
} }
impl MessageHandler<OverlaysMessage, bool> for OverlaysMessageHandler { impl MessageHandler<OverlaysMessage, (bool, &FontCache)> for OverlaysMessageHandler {
#[remain::check] #[remain::check]
fn process_action(&mut self, message: OverlaysMessage, overlays_visible: bool, responses: &mut VecDeque<Message>) { fn process_action(&mut self, message: OverlaysMessage, (overlays_visible, font_cache): (bool, &FontCache), responses: &mut VecDeque<Message>) {
use OverlaysMessage::*; use OverlaysMessage::*;
#[remain::sorted] #[remain::sorted]
match message { match message {
// Sub-messages // Sub-messages
#[remain::unsorted] #[remain::unsorted]
DispatchOperation(operation) => match self.overlays_graphene_document.handle_operation(*operation) { DispatchOperation(operation) => match self.overlays_graphene_document.handle_operation(*operation, font_cache) {
Ok(_) => responses.push_back(OverlaysMessage::Rerender.into()), Ok(_) => responses.push_back(OverlaysMessage::Rerender.into()),
Err(e) => log::error!("OverlaysError: {:?}", e), Err(e) => log::error!("OverlaysError: {:?}", e),
}, },
@@ -30,7 +31,7 @@ impl MessageHandler<OverlaysMessage, bool> for OverlaysMessageHandler {
responses.push_back( responses.push_back(
FrontendMessage::UpdateDocumentOverlays { FrontendMessage::UpdateDocumentOverlays {
svg: if overlays_visible { svg: if overlays_visible {
self.overlays_graphene_document.render_root(ViewMode::Normal) self.overlays_graphene_document.render_root(ViewMode::Normal, font_cache)
} else { } else {
String::from("") String::from("")
}, },
+12
View File
@@ -1,6 +1,7 @@
use super::clipboards::Clipboard; use super::clipboards::Clipboard;
use crate::message_prelude::*; use crate::message_prelude::*;
use graphene::layers::text_layer::Font;
use graphene::LayerId; use graphene::LayerId;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -33,6 +34,17 @@ pub enum PortfolioMessage {
Cut { Cut {
clipboard: Clipboard, clipboard: Clipboard,
}, },
FontLoaded {
font_family: String,
font_style: String,
preview_url: String,
data: Vec<u8>,
is_default: bool,
},
LoadFont {
font: Font,
is_default: bool,
},
NewDocument, NewDocument,
NewDocumentWithName { NewDocumentWithName {
name: String, name: String,
@@ -7,6 +7,7 @@ use crate::layout::layout_message::LayoutTarget;
use crate::layout::widgets::PropertyHolder; use crate::layout::widgets::PropertyHolder;
use crate::{dialog, message_prelude::*}; use crate::{dialog, message_prelude::*};
use graphene::layers::text_layer::{Font, FontCache};
use graphene::Operation as DocumentOperation; use graphene::Operation as DocumentOperation;
use log::warn; use log::warn;
@@ -18,6 +19,7 @@ pub struct PortfolioMessageHandler {
document_ids: Vec<u64>, document_ids: Vec<u64>,
active_document_id: u64, active_document_id: u64,
copy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize], copy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize],
font_cache: FontCache,
} }
impl PortfolioMessageHandler { impl PortfolioMessageHandler {
@@ -72,15 +74,13 @@ impl PortfolioMessageHandler {
new_document new_document
.layer_metadata .layer_metadata
.keys() .keys()
.filter_map(|path| new_document.layer_panel_entry_from_path(path)) .filter_map(|path| new_document.layer_panel_entry_from_path(path, &self.font_cache))
.map(|entry| FrontendMessage::UpdateDocumentLayerDetails { data: entry }.into()) .map(|entry| FrontendMessage::UpdateDocumentLayerDetails { data: entry }.into())
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
); );
new_document.update_layer_tree_options_bar_widgets(responses); new_document.update_layer_tree_options_bar_widgets(responses, &self.font_cache);
new_document.load_image_data(responses, &new_document.graphene_document.root.data, Vec::new()); new_document.load_image_data(responses, &new_document.graphene_document.root.data, Vec::new());
// TODO: Loading the default font should happen on a per-application basis, not a per-document basis
new_document.load_default_font(responses);
self.documents.insert(document_id, new_document); self.documents.insert(document_id, new_document);
@@ -110,6 +110,10 @@ impl PortfolioMessageHandler {
fn document_index(&self, document_id: u64) -> usize { fn document_index(&self, document_id: u64) -> usize {
self.document_ids.iter().position(|id| id == &document_id).expect("Active document is missing from document ids") self.document_ids.iter().position(|id| id == &document_id).expect("Active document is missing from document ids")
} }
pub fn font_cache(&self) -> &FontCache {
&self.font_cache
}
} }
impl Default for PortfolioMessageHandler { impl Default for PortfolioMessageHandler {
@@ -125,6 +129,7 @@ impl Default for PortfolioMessageHandler {
document_ids: vec![starting_key], document_ids: vec![starting_key],
copy_buffer: [EMPTY_VEC; INTERNAL_CLIPBOARD_COUNT as usize], copy_buffer: [EMPTY_VEC; INTERNAL_CLIPBOARD_COUNT as usize],
active_document_id: starting_key, active_document_id: starting_key,
font_cache: Default::default(),
} }
} }
} }
@@ -139,7 +144,7 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessorMessageHandler> for Port
match message { match message {
// Sub-messages // Sub-messages
#[remain::unsorted] #[remain::unsorted]
Document(message) => self.active_document_mut().process_action(message, ipp, responses), Document(message) => self.documents.get_mut(&self.active_document_id).unwrap().process_action(message, (ipp, &self.font_cache), responses),
// Messages // Messages
AutoSaveActiveDocument => responses.push_back(PortfolioMessage::AutoSaveDocument { document_id: self.active_document_id }.into()), AutoSaveActiveDocument => responses.push_back(PortfolioMessage::AutoSaveDocument { document_id: self.active_document_id }.into()),
@@ -263,6 +268,21 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessorMessageHandler> for Port
responses.push_back(Copy { clipboard }.into()); responses.push_back(Copy { clipboard }.into());
responses.push_back(DeleteSelectedLayers.into()); responses.push_back(DeleteSelectedLayers.into());
} }
FontLoaded {
font_family,
font_style,
preview_url,
data,
is_default,
} => {
self.font_cache.insert(Font::new(font_family, font_style), preview_url, data, is_default);
responses.push_back(DocumentMessage::DirtyRenderDocument.into());
}
LoadFont { font, is_default } => {
if !self.font_cache.loaded_font(&font) {
responses.push_front(FrontendMessage::TriggerFontLoad { font, is_default }.into());
}
}
NewDocument => { NewDocument => {
let name = self.generate_new_document_name(); let name = self.generate_new_document_name();
let new_document = DocumentMessageHandler::with_name(name, ipp); let new_document = DocumentMessageHandler::with_name(name, ipp);
+10 -33
View File
@@ -9,43 +9,20 @@ use serde::{Deserialize, Serialize};
#[impl_message(Message, DocumentMessage, PropertiesPanel)] #[impl_message(Message, DocumentMessage, PropertiesPanel)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] #[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
pub enum PropertiesPanelMessage { pub enum PropertiesPanelMessage {
CheckSelectedWasDeleted { CheckSelectedWasDeleted { path: Vec<LayerId> },
path: Vec<LayerId>, CheckSelectedWasUpdated { path: Vec<LayerId> },
},
CheckSelectedWasUpdated {
path: Vec<LayerId>,
},
ClearSelection, ClearSelection,
ModifyFill { ModifyFill { fill: Fill },
fill: Fill, ModifyFont { font_family: String, font_style: String, size: f64 },
}, ModifyName { name: String },
ModifyFont { ModifyStroke { stroke: Stroke },
font_family: String, ModifyText { new_text: String },
font_style: String, ModifyTransform { value: f64, transform_op: TransformOp },
font_file: Option<String>,
size: f64,
},
ModifyName {
name: String,
},
ModifyStroke {
stroke: Stroke,
},
ModifyText {
new_text: String,
},
ModifyTransform {
value: f64,
transform_op: TransformOp,
},
ResendActiveProperties, ResendActiveProperties,
SetActiveLayers { SetActiveLayers { paths: Vec<Vec<LayerId>>, document: TargetDocument },
paths: Vec<Vec<LayerId>>,
document: TargetDocument,
},
} }
#[derive(PartialEq, Clone, Copy, Debug, Serialize, Deserialize)] #[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
pub enum TransformOp { pub enum TransformOp {
X, X,
Y, Y,
@@ -9,10 +9,10 @@ use crate::layout::widgets::{
use crate::message_prelude::*; use crate::message_prelude::*;
use graphene::color::Color; use graphene::color::Color;
use graphene::document::{Document as GrapheneDocument, FontCache}; use graphene::document::Document as GrapheneDocument;
use graphene::layers::layer_info::{Layer, LayerDataType}; use graphene::layers::layer_info::{Layer, LayerDataType};
use graphene::layers::style::{Fill, Gradient, GradientType, LineCap, LineJoin, Stroke}; use graphene::layers::style::{Fill, Gradient, GradientType, LineCap, LineJoin, Stroke};
use graphene::layers::text_layer::TextLayer; use graphene::layers::text_layer::{FontCache, TextLayer};
use graphene::{LayerId, Operation}; use graphene::{LayerId, Operation};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
@@ -111,12 +111,17 @@ impl PropertiesPanelMessageHandler {
pub struct PropertiesPanelMessageHandlerData<'a> { pub struct PropertiesPanelMessageHandlerData<'a> {
pub artwork_document: &'a GrapheneDocument, pub artwork_document: &'a GrapheneDocument,
pub artboard_document: &'a GrapheneDocument, pub artboard_document: &'a GrapheneDocument,
pub font_cache: &'a FontCache,
} }
impl<'a> MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageHandlerData<'a>> for PropertiesPanelMessageHandler { impl<'a> MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageHandlerData<'a>> for PropertiesPanelMessageHandler {
#[remain::check] #[remain::check]
fn process_action(&mut self, message: PropertiesPanelMessage, data: PropertiesPanelMessageHandlerData, responses: &mut VecDeque<Message>) { fn process_action(&mut self, message: PropertiesPanelMessage, data: PropertiesPanelMessageHandlerData, responses: &mut VecDeque<Message>) {
let PropertiesPanelMessageHandlerData { artwork_document, artboard_document } = data; let PropertiesPanelMessageHandlerData {
artwork_document,
artboard_document,
font_cache,
} = data;
let get_document = |document_selector: TargetDocument| match document_selector { let get_document = |document_selector: TargetDocument| match document_selector {
TargetDocument::Artboard => artboard_document, TargetDocument::Artboard => artboard_document,
TargetDocument::Artwork => artwork_document, TargetDocument::Artwork => artwork_document,
@@ -150,21 +155,10 @@ impl<'a> MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageHandlerDat
); );
self.active_selection = None; self.active_selection = None;
} }
ModifyFont { ModifyFont { font_family, font_style, size } => {
font_family,
font_style,
font_file,
size,
} => {
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer"); let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
responses.push_back(self.create_document_operation(Operation::ModifyFont { responses.push_back(self.create_document_operation(Operation::ModifyFont { path, font_family, font_style, size }));
path,
font_family,
font_style,
font_file,
size,
}));
responses.push_back(ResendActiveProperties.into()); responses.push_back(ResendActiveProperties.into());
} }
ModifyTransform { value, transform_op } => { ModifyTransform { value, transform_op } => {
@@ -181,8 +175,8 @@ impl<'a> MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageHandlerDat
}; };
let scale = match transform_op { let scale = match transform_op {
Width => layer.bounding_transform(&get_document(*target_document).font_cache).scale_x() / layer.transform.scale_x(), Width => layer.bounding_transform(font_cache).scale_x() / layer.transform.scale_x(),
Height => layer.bounding_transform(&get_document(*target_document).font_cache).scale_y() / layer.transform.scale_y(), Height => layer.bounding_transform(font_cache).scale_y() / layer.transform.scale_y(),
_ => 1., _ => 1.,
}; };
@@ -235,8 +229,8 @@ impl<'a> MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageHandlerDat
if let Some((path, target_document)) = self.active_selection.clone() { if let Some((path, target_document)) = self.active_selection.clone() {
let layer = get_document(target_document).layer(&path).unwrap(); let layer = get_document(target_document).layer(&path).unwrap();
match target_document { match target_document {
TargetDocument::Artboard => register_artboard_layer_properties(layer, responses, &get_document(target_document).font_cache), TargetDocument::Artboard => register_artboard_layer_properties(layer, responses, font_cache),
TargetDocument::Artwork => register_artwork_layer_properties(layer, responses, &get_document(target_document).font_cache), TargetDocument::Artwork => register_artwork_layer_properties(layer, responses, font_cache),
} }
} }
} }
@@ -677,9 +671,7 @@ fn node_section_transform(layer: &Layer, font_cache: &FontCache) -> LayoutRow {
} }
fn node_section_font(layer: &TextLayer) -> LayoutRow { fn node_section_font(layer: &TextLayer) -> LayoutRow {
let font_family = layer.font_family.clone(); let font = layer.font.clone();
let font_style = layer.font_style.clone();
let font_file = layer.font_file.clone();
let size = layer.size; let size = layer.size;
LayoutRow::Section { LayoutRow::Section {
name: "Font".into(), name: "Font".into(),
@@ -712,14 +704,12 @@ fn node_section_font(layer: &TextLayer) -> LayoutRow {
})), })),
WidgetHolder::new(Widget::FontInput(FontInput { WidgetHolder::new(Widget::FontInput(FontInput {
is_style_picker: false, is_style_picker: false,
font_family: layer.font_family.clone(), font_family: layer.font.font_family.clone(),
font_style: layer.font_style.clone(), font_style: layer.font.font_style.clone(),
font_file_url: String::new(),
on_update: WidgetCallback::new(move |font_input: &FontInput| { on_update: WidgetCallback::new(move |font_input: &FontInput| {
PropertiesPanelMessage::ModifyFont { PropertiesPanelMessage::ModifyFont {
font_family: font_input.font_family.clone(), font_family: font_input.font_family.clone(),
font_style: font_input.font_style.clone(), font_style: font_input.font_style.clone(),
font_file: Some(font_input.font_file_url.clone()),
size, size,
} }
.into() .into()
@@ -739,14 +729,12 @@ fn node_section_font(layer: &TextLayer) -> LayoutRow {
})), })),
WidgetHolder::new(Widget::FontInput(FontInput { WidgetHolder::new(Widget::FontInput(FontInput {
is_style_picker: true, is_style_picker: true,
font_family: layer.font_family.clone(), font_family: layer.font.font_family.clone(),
font_style: layer.font_style.clone(), font_style: layer.font.font_style.clone(),
font_file_url: String::new(),
on_update: WidgetCallback::new(move |font_input: &FontInput| { on_update: WidgetCallback::new(move |font_input: &FontInput| {
PropertiesPanelMessage::ModifyFont { PropertiesPanelMessage::ModifyFont {
font_family: font_input.font_family.clone(), font_family: font_input.font_family.clone(),
font_style: font_input.font_style.clone(), font_style: font_input.font_style.clone(),
font_file: Some(font_input.font_file_url.clone()),
size, size,
} }
.into() .into()
@@ -770,9 +758,8 @@ fn node_section_font(layer: &TextLayer) -> LayoutRow {
unit: " px".into(), unit: " px".into(),
on_update: WidgetCallback::new(move |number_input: &NumberInput| { on_update: WidgetCallback::new(move |number_input: &NumberInput| {
PropertiesPanelMessage::ModifyFont { PropertiesPanelMessage::ModifyFont {
font_family: font_family.clone(), font_family: font.font_family.clone(),
font_style: font_style.clone(), font_style: font.font_style.clone(),
font_file: font_file.clone(),
size: number_input.value.unwrap(), size: number_input.value.unwrap(),
} }
.into() .into()
@@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
#[remain::sorted] #[remain::sorted]
#[impl_message(Message, DocumentMessage, TransformLayers)] #[impl_message(Message, DocumentMessage, TransformLayers)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
pub enum TransformLayerMessage { pub enum TransformLayerMessage {
ApplyTransformOperation, ApplyTransformOperation,
BeginGrab, BeginGrab,
@@ -8,6 +8,7 @@ use crate::message_prelude::*;
use graphene::document::Document; use graphene::document::Document;
use glam::DVec2; use glam::DVec2;
use graphene::layers::text_layer::FontCache;
use std::collections::{HashMap, VecDeque}; use std::collections::{HashMap, VecDeque};
#[derive(Debug, Clone, Default, PartialEq)] #[derive(Debug, Clone, Default, PartialEq)]
@@ -25,18 +26,12 @@ pub struct TransformLayerMessageHandler {
pivot: DVec2, pivot: DVec2,
} }
impl MessageHandler<TransformLayerMessage, (&mut HashMap<Vec<LayerId>, LayerMetadata>, &mut Document, &InputPreprocessorMessageHandler)> for TransformLayerMessageHandler { type TransformData<'a> = (&'a mut HashMap<Vec<LayerId>, LayerMetadata>, &'a mut Document, &'a InputPreprocessorMessageHandler, &'a FontCache);
impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformLayerMessageHandler {
#[remain::check] #[remain::check]
fn process_action( fn process_action(&mut self, message: TransformLayerMessage, (layer_metadata, document, ipp, font_cache): TransformData, responses: &mut VecDeque<Message>) {
&mut self,
message: TransformLayerMessage,
data: (&mut HashMap<Vec<LayerId>, LayerMetadata>, &mut Document, &InputPreprocessorMessageHandler),
responses: &mut VecDeque<Message>,
) {
use TransformLayerMessage::*; use TransformLayerMessage::*;
let (layer_metadata, document, ipp) = data;
let selected_layers = layer_metadata.iter().filter_map(|(layer_path, data)| data.selected.then(|| layer_path)).collect::<Vec<_>>(); let selected_layers = layer_metadata.iter().filter_map(|(layer_path, data)| data.selected.then(|| layer_path)).collect::<Vec<_>>();
let mut selected = Selected::new(&mut self.original_transforms, &mut self.pivot, &selected_layers, responses, document); let mut selected = Selected::new(&mut self.original_transforms, &mut self.pivot, &selected_layers, responses, document);
@@ -45,7 +40,7 @@ impl MessageHandler<TransformLayerMessage, (&mut HashMap<Vec<LayerId>, LayerMeta
selected.revert_operation(); selected.revert_operation();
typing.clear(); typing.clear();
} else { } else {
*selected.pivot = selected.calculate_pivot(&document.font_cache); *selected.pivot = selected.calculate_pivot(font_cache);
} }
*mouse_position = ipp.mouse.position; *mouse_position = ipp.mouse.position;
@@ -128,7 +123,7 @@ impl MessageHandler<TransformLayerMessage, (&mut HashMap<Vec<LayerId>, LayerMeta
self.transform_operation.apply_transform_operation(&mut selected, self.snap); self.transform_operation.apply_transform_operation(&mut selected, self.snap);
} }
TransformOperation::Rotating(rotation) => { TransformOperation::Rotating(rotation) => {
let selected_pivot = selected.calculate_pivot(&document.font_cache); let selected_pivot = selected.calculate_pivot(font_cache);
let angle = { let angle = {
let start_offset = self.mouse_position - selected_pivot; let start_offset = self.mouse_position - selected_pivot;
let end_offset = ipp.mouse.position - selected_pivot; let end_offset = ipp.mouse.position - selected_pivot;
+4 -3
View File
@@ -1,7 +1,8 @@
use crate::consts::{ROTATE_SNAP_ANGLE, SCALE_SNAP_INTERVAL}; use crate::consts::{ROTATE_SNAP_ANGLE, SCALE_SNAP_INTERVAL};
use crate::message_prelude::*; use crate::message_prelude::*;
use graphene::document::{Document, FontCache}; use graphene::document::Document;
use graphene::layers::text_layer::FontCache;
use graphene::Operation as DocumentOperation; use graphene::Operation as DocumentOperation;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
@@ -9,7 +10,7 @@ use std::collections::{HashMap, VecDeque};
pub type OriginalTransforms = HashMap<Vec<LayerId>, DAffine2>; pub type OriginalTransforms = HashMap<Vec<LayerId>, DAffine2>;
#[derive(Debug, Clone, PartialEq, Copy)] #[derive(Debug, Clone, PartialEq, Eq, Copy)]
pub enum Axis { pub enum Axis {
Both, Both,
X, X,
@@ -270,7 +271,7 @@ impl<'a> Selected<'a> {
} }
} }
#[derive(Debug, Clone, PartialEq, Default)] #[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Typing { pub struct Typing {
pub digits: Vec<u8>, pub digits: Vec<u8>,
pub contains_decimal: bool, pub contains_decimal: bool,
+5 -5
View File
@@ -8,19 +8,19 @@ use std::fmt;
pub type DocumentSave = (GrapheneDocument, HashMap<Vec<LayerId>, LayerMetadata>); pub type DocumentSave = (GrapheneDocument, HashMap<Vec<LayerId>, LayerMetadata>);
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash)] #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, Hash)]
pub enum FlipAxis { pub enum FlipAxis {
X, X,
Y, Y,
} }
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash)] #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, Hash)]
pub enum AlignAxis { pub enum AlignAxis {
X, X,
Y, Y,
} }
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash)] #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, Hash)]
pub enum AlignAggregate { pub enum AlignAggregate {
Min, Min,
Max, Max,
@@ -28,13 +28,13 @@ pub enum AlignAggregate {
Average, Average,
} }
#[derive(PartialEq, Clone, Copy, Debug, Serialize, Deserialize)] #[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
pub enum TargetDocument { pub enum TargetDocument {
Artboard, Artboard,
Artwork, Artwork,
} }
#[derive(PartialEq, Clone, Copy, Debug, Serialize, Deserialize)] #[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
pub enum DocumentMode { pub enum DocumentMode {
DesignMode, DesignMode,
SelectMode, SelectMode,
+3 -2
View File
@@ -6,6 +6,8 @@ use crate::message_prelude::*;
use crate::misc::HintData; use crate::misc::HintData;
use crate::Color; use crate::Color;
use graphene::layers::text_layer::Font;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[remain::sorted] #[remain::sorted]
@@ -23,8 +25,7 @@ pub enum FrontendMessage {
TriggerAboutGraphiteLocalizedCommitDate { commit_date: String }, TriggerAboutGraphiteLocalizedCommitDate { commit_date: String },
TriggerFileDownload { document: String, name: String }, TriggerFileDownload { document: String, name: String },
TriggerFileUpload, TriggerFileUpload,
TriggerFontLoad { font_file_url: String }, TriggerFontLoad { font: Font, is_default: bool },
TriggerFontLoadDefault,
TriggerIndexedDbRemoveDocument { document_id: u64 }, TriggerIndexedDbRemoveDocument { document_id: u64 },
TriggerIndexedDbWriteDocument { document: String, details: FrontendDocumentDetails, version: String }, TriggerIndexedDbWriteDocument { document: String, details: FrontendDocumentDetails, version: String },
TriggerRasterDownload { document: String, name: String, mime: String, size: (f64, f64) }, TriggerRasterDownload { document: String, name: String, mime: String, size: (f64, f64) },
+2 -2
View File
@@ -1,14 +1,14 @@
use graphene::LayerId; use graphene::LayerId;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)] #[derive(PartialEq, Eq, Clone, Deserialize, Serialize, Debug)]
pub struct FrontendDocumentDetails { pub struct FrontendDocumentDetails {
pub is_saved: bool, pub is_saved: bool,
pub name: String, pub name: String,
pub id: u64, pub id: u64,
} }
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)] #[derive(PartialEq, Eq, Clone, Deserialize, Serialize, Debug)]
pub struct FrontendImageData { pub struct FrontendImageData {
pub path: Vec<LayerId>, pub path: Vec<LayerId>,
pub mime: String, pub mime: String,
+1 -1
View File
@@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
#[remain::sorted] #[remain::sorted]
#[impl_message(Message, Global)] #[impl_message(Message, Global)]
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)] #[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
pub enum GlobalMessage { pub enum GlobalMessage {
LogDebug, LogDebug,
LogInfo, LogInfo,
+1 -1
View File
@@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
#[remain::sorted] #[remain::sorted]
#[impl_message(Message, InputMapper)] #[impl_message(Message, InputMapper)]
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)] #[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
pub enum InputMapperMessage { pub enum InputMapperMessage {
// Sub-messages // Sub-messages
#[remain::unsorted] #[remain::unsorted]
+9 -4
View File
@@ -3,6 +3,8 @@ use super::widgets::WidgetLayout;
use crate::layout::widgets::Widget; use crate::layout::widgets::Widget;
use crate::message_prelude::*; use crate::message_prelude::*;
use graphene::layers::text_layer::Font;
use serde_json::Value; use serde_json::Value;
use std::collections::VecDeque; use std::collections::VecDeque;
@@ -95,17 +97,20 @@ impl MessageHandler<LayoutMessage, ()> for LayoutMessageHandler {
let update_value = value.as_object().expect("FontInput update was not of type: object"); let update_value = value.as_object().expect("FontInput update was not of type: object");
let font_family_value = update_value.get("fontFamily").expect("FontInput update does not have a fontFamily"); let font_family_value = update_value.get("fontFamily").expect("FontInput update does not have a fontFamily");
let font_style_value = update_value.get("fontStyle").expect("FontInput update does not have a fontStyle"); let font_style_value = update_value.get("fontStyle").expect("FontInput update does not have a fontStyle");
let font_file_url_value = update_value.get("fontFileUrl").expect("FontInput update does not have a fontFileUrl");
let font_family = font_family_value.as_str().expect("FontInput update fontFamily was not of type: string"); let font_family = font_family_value.as_str().expect("FontInput update fontFamily was not of type: string");
let font_style = font_style_value.as_str().expect("FontInput update fontStyle was not of type: string"); let font_style = font_style_value.as_str().expect("FontInput update fontStyle was not of type: string");
let font_file_url = font_file_url_value.as_str().expect("FontInput update fontFileUrl was not of type: string");
font_input.font_family = font_family.into(); font_input.font_family = font_family.into();
font_input.font_style = font_style.into(); font_input.font_style = font_style.into();
font_input.font_file_url = font_file_url.into();
responses.push_back(DocumentMessage::LoadFont { font_file_url: font_file_url.into() }.into()); responses.push_back(
PortfolioMessage::LoadFont {
font: Font::new(font_family.into(), font_style.into()),
is_default: false,
}
.into(),
);
let callback_message = (font_input.on_update.callback)(font_input); let callback_message = (font_input.on_update.callback)(font_input);
responses.push_back(callback_message); responses.push_back(callback_message);
} }
+6 -8
View File
@@ -255,14 +255,12 @@ pub struct FontInput {
pub font_family: String, pub font_family: String,
#[serde(rename = "fontStyle")] #[serde(rename = "fontStyle")]
pub font_style: String, pub font_style: String,
#[serde(rename = "fontFileUrl")]
pub font_file_url: String,
#[serde(skip)] #[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")] #[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<FontInput>, pub on_update: WidgetCallback<FontInput>,
} }
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
pub enum NumberInputIncrementBehavior { pub enum NumberInputIncrementBehavior {
Add, Add,
Multiply, Multiply,
@@ -275,7 +273,7 @@ impl Default for NumberInputIncrementBehavior {
} }
} }
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Separator { pub struct Separator {
pub direction: SeparatorDirection, pub direction: SeparatorDirection,
@@ -283,13 +281,13 @@ pub struct Separator {
pub separator_type: SeparatorType, pub separator_type: SeparatorType,
} }
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SeparatorDirection { pub enum SeparatorDirection {
Horizontal, Horizontal,
Vertical, Vertical,
} }
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SeparatorType { pub enum SeparatorType {
Related, Related,
Unrelated, Unrelated,
@@ -414,14 +412,14 @@ pub struct RadioEntryData {
pub on_update: WidgetCallback<()>, pub on_update: WidgetCallback<()>,
} }
#[derive(Clone, Serialize, Deserialize, Derivative, Debug, PartialEq)] #[derive(Clone, Serialize, Deserialize, Derivative, Debug, PartialEq, Eq)]
pub struct IconLabel { pub struct IconLabel {
pub icon: String, pub icon: String,
#[serde(rename = "gapAfter")] #[serde(rename = "gapAfter")]
pub gap_after: bool, pub gap_after: bool,
} }
#[derive(Clone, Serialize, Deserialize, Derivative, Debug, PartialEq, Default)] #[derive(Clone, Serialize, Deserialize, Derivative, Debug, PartialEq, Eq, Default)]
pub struct TextLabel { pub struct TextLabel {
pub value: String, pub value: String,
pub bold: bool, pub bold: bool,
+3 -11
View File
@@ -6,28 +6,20 @@ use crate::layout::widgets::{IconButton, LayoutRow, PropertyHolder, Separator, S
use crate::message_prelude::*; use crate::message_prelude::*;
use graphene::color::Color; use graphene::color::Color;
use graphene::layers::text_layer::FontCache;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::{HashMap, VecDeque}; use std::collections::{HashMap, VecDeque};
use std::fmt::{self, Debug}; use std::fmt::{self, Debug};
pub type ToolActionHandlerData<'a> = (&'a DocumentMessageHandler, &'a DocumentToolData, &'a InputPreprocessorMessageHandler); pub type ToolActionHandlerData<'a> = (&'a DocumentMessageHandler, &'a DocumentToolData, &'a InputPreprocessorMessageHandler, &'a FontCache);
pub trait Fsm { pub trait Fsm {
type ToolData; type ToolData;
type ToolOptions; type ToolOptions;
#[must_use] #[must_use]
fn transition( fn transition(self, message: ToolMessage, tool_data: &mut Self::ToolData, transition_data: ToolActionHandlerData, options: &Self::ToolOptions, messages: &mut VecDeque<Message>) -> Self;
self,
message: ToolMessage,
document: &DocumentMessageHandler,
tool_data: &DocumentToolData,
data: &mut Self::ToolData,
options: &Self::ToolOptions,
input: &InputPreprocessorMessageHandler,
messages: &mut VecDeque<Message>,
) -> Self;
fn update_hints(&self, responses: &mut VecDeque<Message>); fn update_hints(&self, responses: &mut VecDeque<Message>);
fn update_cursor(&self, responses: &mut VecDeque<Message>); fn update_cursor(&self, responses: &mut VecDeque<Message>);
@@ -6,6 +6,7 @@ use crate::layout::widgets::PropertyHolder;
use crate::message_prelude::*; use crate::message_prelude::*;
use graphene::color::Color; use graphene::color::Color;
use graphene::layers::text_layer::FontCache;
use std::collections::VecDeque; use std::collections::VecDeque;
@@ -14,12 +15,12 @@ pub struct ToolMessageHandler {
tool_state: ToolFsmState, tool_state: ToolFsmState,
} }
impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessorMessageHandler)> for ToolMessageHandler { impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessorMessageHandler, &FontCache)> for ToolMessageHandler {
#[remain::check] #[remain::check]
fn process_action(&mut self, message: ToolMessage, data: (&DocumentMessageHandler, &InputPreprocessorMessageHandler), responses: &mut VecDeque<Message>) { fn process_action(&mut self, message: ToolMessage, data: (&DocumentMessageHandler, &InputPreprocessorMessageHandler, &FontCache), responses: &mut VecDeque<Message>) {
use ToolMessage::*; use ToolMessage::*;
let (document, input) = data; let (document, input, font_cache) = data;
#[remain::sorted] #[remain::sorted]
match message { match message {
// Messages // Messages
@@ -41,11 +42,11 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessorMes
// Send the Abort state transition to the tool // Send the Abort state transition to the tool
let mut send_abort_to_tool = |tool_type, message: ToolMessage, update_hints_and_cursor: bool| { let mut send_abort_to_tool = |tool_type, message: ToolMessage, update_hints_and_cursor: bool| {
if let Some(tool) = tool_data.tools.get_mut(&tool_type) { if let Some(tool) = tool_data.tools.get_mut(&tool_type) {
tool.process_action(message, (document, document_data, input), responses); tool.process_action(message, (document, document_data, input, font_cache), responses);
if update_hints_and_cursor { if update_hints_and_cursor {
tool.process_action(ToolMessage::UpdateHints, (document, document_data, input), responses); tool.process_action(ToolMessage::UpdateHints, (document, document_data, input, font_cache), responses);
tool.process_action(ToolMessage::UpdateCursor, (document, document_data, input), responses); tool.process_action(ToolMessage::UpdateCursor, (document, document_data, input, font_cache), responses);
} }
} }
}; };
@@ -95,8 +96,12 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessorMes
tool_data.register_properties(responses, LayoutTarget::ToolShelf); tool_data.register_properties(responses, LayoutTarget::ToolShelf);
// Set initial hints and cursor // Set initial hints and cursor
tool_data.active_tool_mut().process_action(ToolMessage::UpdateHints, (document, document_data, input), responses); tool_data
tool_data.active_tool_mut().process_action(ToolMessage::UpdateCursor, (document, document_data, input), responses); .active_tool_mut()
.process_action(ToolMessage::UpdateHints, (document, document_data, input, font_cache), responses);
tool_data
.active_tool_mut()
.process_action(ToolMessage::UpdateCursor, (document, document_data, input, font_cache), responses);
} }
ResetColors => { ResetColors => {
let document_data = &mut self.tool_state.document_tool_data; let document_data = &mut self.tool_state.document_tool_data;
@@ -157,7 +162,7 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessorMes
if let Some(tool) = tool_data.tools.get_mut(&tool_type) { if let Some(tool) = tool_data.tools.get_mut(&tool_type) {
if tool_type == tool_data.active_tool_type { if tool_type == tool_data.active_tool_type {
tool.process_action(tool_message, (document, document_data, input), responses); tool.process_action(tool_message, (document, document_data, input, font_cache), responses);
} }
} }
} }
@@ -1,14 +1,12 @@
use crate::consts::SELECTION_TOLERANCE; use crate::consts::SELECTION_TOLERANCE;
use crate::document::utility_types::TargetDocument; use crate::document::utility_types::TargetDocument;
use crate::document::DocumentMessageHandler;
use crate::frontend::utility_types::MouseCursorIcon; use crate::frontend::utility_types::MouseCursorIcon;
use crate::input::keyboard::{Key, MouseMotion}; use crate::input::keyboard::{Key, MouseMotion};
use crate::input::InputPreprocessorMessageHandler;
use crate::layout::widgets::PropertyHolder; use crate::layout::widgets::PropertyHolder;
use crate::message_prelude::*; use crate::message_prelude::*;
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
use crate::viewport_tools::snapping::SnapHandler; use crate::viewport_tools::snapping::SnapHandler;
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; use crate::viewport_tools::tool::{Fsm, ToolActionHandlerData};
use graphene::intersection::Quad; use graphene::intersection::Quad;
@@ -25,7 +23,7 @@ pub struct ArtboardTool {
#[remain::sorted] #[remain::sorted]
#[impl_message(Message, ToolMessage, Artboard)] #[impl_message(Message, ToolMessage, Artboard)]
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)] #[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
pub enum ArtboardToolMessage { pub enum ArtboardToolMessage {
// Standard messages // Standard messages
#[remain::unsorted] #[remain::unsorted]
@@ -55,7 +53,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for ArtboardTool
return; return;
} }
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, &(), data.2, responses); let new_state = self.fsm_state.transition(action, &mut self.data, data, &(), responses);
if self.fsm_state != new_state { if self.fsm_state != new_state {
self.fsm_state = new_state; self.fsm_state = new_state;
@@ -99,11 +97,9 @@ impl Fsm for ArtboardToolFsmState {
fn transition( fn transition(
self, self,
event: ToolMessage, event: ToolMessage,
document: &DocumentMessageHandler, tool_data: &mut Self::ToolData,
_tool_data: &DocumentToolData, (document, _global_tool_data, input, font_cache): ToolActionHandlerData,
data: &mut Self::ToolData,
_tool_options: &Self::ToolOptions, _tool_options: &Self::ToolOptions,
input: &InputPreprocessorMessageHandler,
responses: &mut VecDeque<Message>, responses: &mut VecDeque<Message>,
) -> Self { ) -> Self {
if let ToolMessage::Artboard(event) = event { if let ToolMessage::Artboard(event) = event {
@@ -111,8 +107,8 @@ impl Fsm for ArtboardToolFsmState {
(ArtboardToolFsmState::Ready | ArtboardToolFsmState::ResizingBounds | ArtboardToolFsmState::Dragging, ArtboardToolMessage::DocumentIsDirty) => { (ArtboardToolFsmState::Ready | ArtboardToolFsmState::ResizingBounds | ArtboardToolFsmState::Dragging, ArtboardToolMessage::DocumentIsDirty) => {
let mut buffer = Vec::new(); let mut buffer = Vec::new();
match ( match (
data.selected_board.map(|path| document.artboard_bounding_box_and_transform(&[path])).unwrap_or(None), tool_data.selected_board.map(|path| document.artboard_bounding_box_and_transform(&[path], font_cache)).unwrap_or(None),
data.bounding_box_overlays.take(), tool_data.bounding_box_overlays.take(),
) { ) {
(None, Some(bounding_box_overlays)) => bounding_box_overlays.delete(&mut buffer), (None, Some(bounding_box_overlays)) => bounding_box_overlays.delete(&mut buffer),
(Some((bounds, transform)), paths) => { (Some((bounds, transform)), paths) => {
@@ -123,12 +119,12 @@ impl Fsm for ArtboardToolFsmState {
bounding_box_overlays.transform(&mut buffer); bounding_box_overlays.transform(&mut buffer);
data.bounding_box_overlays = Some(bounding_box_overlays); tool_data.bounding_box_overlays = Some(bounding_box_overlays);
responses.push_back(OverlaysMessage::Rerender.into()); responses.push_back(OverlaysMessage::Rerender.into());
responses.push_back( responses.push_back(
PropertiesPanelMessage::SetActiveLayers { PropertiesPanelMessage::SetActiveLayers {
paths: vec![vec![data.selected_board.unwrap()]], paths: vec![vec![tool_data.selected_board.unwrap()]],
document: TargetDocument::Artboard, document: TargetDocument::Artboard,
} }
.into(), .into(),
@@ -140,10 +136,10 @@ impl Fsm for ArtboardToolFsmState {
self self
} }
(ArtboardToolFsmState::Ready, ArtboardToolMessage::PointerDown) => { (ArtboardToolFsmState::Ready, ArtboardToolMessage::PointerDown) => {
data.drag_start = input.mouse.position; tool_data.drag_start = input.mouse.position;
data.drag_current = input.mouse.position; tool_data.drag_current = input.mouse.position;
let dragging_bounds = if let Some(bounding_box) = &mut data.bounding_box_overlays { let dragging_bounds = if let Some(bounding_box) = &mut tool_data.bounding_box_overlays {
let edges = bounding_box.check_selected_edges(input.mouse.position); let edges = bounding_box.check_selected_edges(input.mouse.position);
bounding_box.selected_edges = edges.map(|(top, bottom, left, right)| { bounding_box.selected_edges = edges.map(|(top, bottom, left, right)| {
@@ -161,22 +157,25 @@ impl Fsm for ArtboardToolFsmState {
let snap_x = selected_edges.2 || selected_edges.3; let snap_x = selected_edges.2 || selected_edges.3;
let snap_y = selected_edges.0 || selected_edges.1; let snap_y = selected_edges.0 || selected_edges.1;
data.snap_handler tool_data
.start_snap(document, document.bounding_boxes(None, Some(data.selected_board.unwrap())), snap_x, snap_y); .snap_handler
data.snap_handler.add_all_document_handles(document, &[], &[]); .start_snap(document, document.bounding_boxes(None, Some(tool_data.selected_board.unwrap()), font_cache), snap_x, snap_y);
tool_data.snap_handler.add_all_document_handles(document, &[], &[]);
ArtboardToolFsmState::ResizingBounds ArtboardToolFsmState::ResizingBounds
} else { } else {
let tolerance = DVec2::splat(SELECTION_TOLERANCE); let tolerance = DVec2::splat(SELECTION_TOLERANCE);
let quad = Quad::from_box([input.mouse.position - tolerance, input.mouse.position + tolerance]); let quad = Quad::from_box([input.mouse.position - tolerance, input.mouse.position + tolerance]);
let intersection = document.artboard_message_handler.artboards_graphene_document.intersects_quad_root(quad); let intersection = document.artboard_message_handler.artboards_graphene_document.intersects_quad_root(quad, font_cache);
responses.push_back(ToolMessage::DocumentIsDirty.into()); responses.push_back(ToolMessage::DocumentIsDirty.into());
if let Some(intersection) = intersection.last() { if let Some(intersection) = intersection.last() {
data.selected_board = Some(intersection[0]); tool_data.selected_board = Some(intersection[0]);
data.snap_handler.start_snap(document, document.bounding_boxes(None, Some(intersection[0])), true, true); tool_data
data.snap_handler.add_all_document_handles(document, &[], &[]); .snap_handler
.start_snap(document, document.bounding_boxes(None, Some(intersection[0]), font_cache), true, true);
tool_data.snap_handler.add_all_document_handles(document, &[], &[]);
responses.push_back( responses.push_back(
PropertiesPanelMessage::SetActiveLayers { PropertiesPanelMessage::SetActiveLayers {
@@ -189,10 +188,10 @@ impl Fsm for ArtboardToolFsmState {
ArtboardToolFsmState::Dragging ArtboardToolFsmState::Dragging
} else { } else {
let id = generate_uuid(); let id = generate_uuid();
data.selected_board = Some(id); tool_data.selected_board = Some(id);
data.snap_handler.start_snap(document, document.bounding_boxes(None, Some(id)), true, true); tool_data.snap_handler.start_snap(document, document.bounding_boxes(None, Some(id), font_cache), true, true);
data.snap_handler.add_all_document_handles(document, &[], &[]); tool_data.snap_handler.add_all_document_handles(document, &[], &[]);
responses.push_back( responses.push_back(
ArtboardMessage::AddArtboard { ArtboardMessage::AddArtboard {
@@ -210,20 +209,20 @@ impl Fsm for ArtboardToolFsmState {
} }
} }
(ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => { (ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => {
if let Some(bounds) = &data.bounding_box_overlays { if let Some(bounds) = &tool_data.bounding_box_overlays {
if let Some(movement) = &bounds.selected_edges { if let Some(movement) = &bounds.selected_edges {
let from_center = input.keyboard.get(center as usize); let from_center = input.keyboard.get(center as usize);
let constrain_square = input.keyboard.get(constrain_axis_or_aspect as usize); let constrain_square = input.keyboard.get(constrain_axis_or_aspect as usize);
let mouse_position = input.mouse.position; let mouse_position = input.mouse.position;
let snapped_mouse_position = data.snap_handler.snap_position(responses, document, mouse_position); let snapped_mouse_position = tool_data.snap_handler.snap_position(responses, document, mouse_position);
let [position, size] = movement.new_size(snapped_mouse_position, bounds.transform, from_center, constrain_square); let [position, size] = movement.new_size(snapped_mouse_position, bounds.transform, from_center, constrain_square);
let position = movement.center_position(position, size, from_center); let position = movement.center_position(position, size, from_center);
responses.push_back( responses.push_back(
ArtboardMessage::ResizeArtboard { ArtboardMessage::ResizeArtboard {
artboard: data.selected_board.unwrap(), artboard: tool_data.selected_board.unwrap(),
position: position.round().into(), position: position.round().into(),
size: size.round().into(), size: size.round().into(),
} }
@@ -236,22 +235,22 @@ impl Fsm for ArtboardToolFsmState {
ArtboardToolFsmState::ResizingBounds ArtboardToolFsmState::ResizingBounds
} }
(ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, .. }) => { (ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, .. }) => {
if let Some(bounds) = &data.bounding_box_overlays { if let Some(bounds) = &tool_data.bounding_box_overlays {
let axis_align = input.keyboard.get(constrain_axis_or_aspect as usize); let axis_align = input.keyboard.get(constrain_axis_or_aspect as usize);
let mouse_position = axis_align_drag(axis_align, input.mouse.position, data.drag_start); let mouse_position = axis_align_drag(axis_align, input.mouse.position, tool_data.drag_start);
let mouse_delta = mouse_position - data.drag_current; let mouse_delta = mouse_position - tool_data.drag_current;
let snap = bounds.evaluate_transform_handle_positions().into_iter().collect(); let snap = bounds.evaluate_transform_handle_positions().into_iter().collect();
let closest_move = data.snap_handler.snap_layers(responses, document, snap, mouse_delta); let closest_move = tool_data.snap_handler.snap_layers(responses, document, snap, mouse_delta);
let size = bounds.bounds[1] - bounds.bounds[0]; let size = bounds.bounds[1] - bounds.bounds[0];
let position = bounds.bounds[0] + bounds.transform.inverse().transform_vector2(mouse_position - data.drag_current + closest_move); let position = bounds.bounds[0] + bounds.transform.inverse().transform_vector2(mouse_position - tool_data.drag_current + closest_move);
responses.push_back( responses.push_back(
ArtboardMessage::ResizeArtboard { ArtboardMessage::ResizeArtboard {
artboard: data.selected_board.unwrap(), artboard: tool_data.selected_board.unwrap(),
position: position.round().into(), position: position.round().into(),
size: size.round().into(), size: size.round().into(),
} }
@@ -260,17 +259,17 @@ impl Fsm for ArtboardToolFsmState {
responses.push_back(ToolMessage::DocumentIsDirty.into()); responses.push_back(ToolMessage::DocumentIsDirty.into());
data.drag_current = mouse_position + closest_move; tool_data.drag_current = mouse_position + closest_move;
} }
ArtboardToolFsmState::Dragging ArtboardToolFsmState::Dragging
} }
(ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => { (ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => {
let mouse_position = input.mouse.position; let mouse_position = input.mouse.position;
let snapped_mouse_position = data.snap_handler.snap_position(responses, document, mouse_position); let snapped_mouse_position = tool_data.snap_handler.snap_position(responses, document, mouse_position);
let root_transform = document.graphene_document.root.transform.inverse(); let root_transform = document.graphene_document.root.transform.inverse();
let mut start = data.drag_start; let mut start = tool_data.drag_start;
let mut size = snapped_mouse_position - start; let mut size = snapped_mouse_position - start;
// Constrain axis // Constrain axis
if input.keyboard.get(constrain_axis_or_aspect as usize) { if input.keyboard.get(constrain_axis_or_aspect as usize) {
@@ -287,7 +286,7 @@ impl Fsm for ArtboardToolFsmState {
responses.push_back( responses.push_back(
ArtboardMessage::ResizeArtboard { ArtboardMessage::ResizeArtboard {
artboard: data.selected_board.unwrap(), artboard: tool_data.selected_board.unwrap(),
position: start.round().into(), position: start.round().into(),
size: size.round().into(), size: size.round().into(),
} }
@@ -298,7 +297,7 @@ impl Fsm for ArtboardToolFsmState {
// This might result in a few more calls but it is not reliant on the order of messages // This might result in a few more calls but it is not reliant on the order of messages
responses.push_back( responses.push_back(
PropertiesPanelMessage::SetActiveLayers { PropertiesPanelMessage::SetActiveLayers {
paths: vec![vec![data.selected_board.unwrap()]], paths: vec![vec![tool_data.selected_board.unwrap()]],
document: TargetDocument::Artboard, document: TargetDocument::Artboard,
} }
.into(), .into(),
@@ -309,28 +308,28 @@ impl Fsm for ArtboardToolFsmState {
ArtboardToolFsmState::Drawing ArtboardToolFsmState::Drawing
} }
(ArtboardToolFsmState::Ready, ArtboardToolMessage::PointerMove { .. }) => { (ArtboardToolFsmState::Ready, ArtboardToolMessage::PointerMove { .. }) => {
let cursor = data.bounding_box_overlays.as_ref().map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, false)); let cursor = tool_data.bounding_box_overlays.as_ref().map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, false));
if data.cursor != cursor { if tool_data.cursor != cursor {
data.cursor = cursor; tool_data.cursor = cursor;
responses.push_back(FrontendMessage::UpdateMouseCursor { cursor }.into()); responses.push_back(FrontendMessage::UpdateMouseCursor { cursor }.into());
} }
ArtboardToolFsmState::Ready ArtboardToolFsmState::Ready
} }
(ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerUp) => { (ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerUp) => {
data.snap_handler.cleanup(responses); tool_data.snap_handler.cleanup(responses);
if let Some(bounds) = &mut data.bounding_box_overlays { if let Some(bounds) = &mut tool_data.bounding_box_overlays {
bounds.original_transforms.clear(); bounds.original_transforms.clear();
} }
ArtboardToolFsmState::Ready ArtboardToolFsmState::Ready
} }
(ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerUp) => { (ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerUp) => {
data.snap_handler.cleanup(responses); tool_data.snap_handler.cleanup(responses);
if let Some(bounds) = &mut data.bounding_box_overlays { if let Some(bounds) = &mut tool_data.bounding_box_overlays {
bounds.original_transforms.clear(); bounds.original_transforms.clear();
} }
@@ -339,23 +338,23 @@ impl Fsm for ArtboardToolFsmState {
ArtboardToolFsmState::Ready ArtboardToolFsmState::Ready
} }
(ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerUp) => { (ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerUp) => {
data.snap_handler.cleanup(responses); tool_data.snap_handler.cleanup(responses);
if let Some(bounds) = &mut data.bounding_box_overlays { if let Some(bounds) = &mut tool_data.bounding_box_overlays {
bounds.original_transforms.clear(); bounds.original_transforms.clear();
} }
ArtboardToolFsmState::Ready ArtboardToolFsmState::Ready
} }
(_, ArtboardToolMessage::DeleteSelected) => { (_, ArtboardToolMessage::DeleteSelected) => {
if let Some(artboard) = data.selected_board.take() { if let Some(artboard) = tool_data.selected_board.take() {
responses.push_back(ArtboardMessage::DeleteArtboard { artboard }.into()); responses.push_back(ArtboardMessage::DeleteArtboard { artboard }.into());
responses.push_back(ToolMessage::DocumentIsDirty.into()); responses.push_back(ToolMessage::DocumentIsDirty.into());
} }
ArtboardToolFsmState::Ready ArtboardToolFsmState::Ready
} }
(_, ArtboardToolMessage::Abort) => { (_, ArtboardToolMessage::Abort) => {
if let Some(bounding_box_overlays) = data.bounding_box_overlays.take() { if let Some(bounding_box_overlays) = tool_data.bounding_box_overlays.take() {
bounding_box_overlays.delete(responses); bounding_box_overlays.delete(responses);
} }
@@ -368,7 +367,7 @@ impl Fsm for ArtboardToolFsmState {
.into(), .into(),
); );
data.snap_handler.cleanup(responses); tool_data.snap_handler.cleanup(responses);
ArtboardToolFsmState::Ready ArtboardToolFsmState::Ready
} }
_ => self, _ => self,
@@ -1,13 +1,11 @@
use super::shared::resize::Resize; use super::shared::resize::Resize;
use crate::consts::DRAG_THRESHOLD; use crate::consts::DRAG_THRESHOLD;
use crate::document::DocumentMessageHandler;
use crate::frontend::utility_types::MouseCursorIcon; use crate::frontend::utility_types::MouseCursorIcon;
use crate::input::keyboard::{Key, MouseMotion}; use crate::input::keyboard::{Key, MouseMotion};
use crate::input::InputPreprocessorMessageHandler;
use crate::layout::widgets::PropertyHolder; use crate::layout::widgets::PropertyHolder;
use crate::message_prelude::*; use crate::message_prelude::*;
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; use crate::viewport_tools::tool::{Fsm, ToolActionHandlerData};
use graphene::layers::style; use graphene::layers::style;
use graphene::Operation; use graphene::Operation;
@@ -23,7 +21,7 @@ pub struct EllipseTool {
#[remain::sorted] #[remain::sorted]
#[impl_message(Message, ToolMessage, Ellipse)] #[impl_message(Message, ToolMessage, Ellipse)]
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)] #[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
pub enum EllipseToolMessage { pub enum EllipseToolMessage {
// Standard messages // Standard messages
#[remain::unsorted] #[remain::unsorted]
@@ -52,7 +50,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for EllipseTool
return; return;
} }
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, &(), data.2, responses); let new_state = self.fsm_state.transition(action, &mut self.data, data, &(), responses);
if self.fsm_state != new_state { if self.fsm_state != new_state {
self.fsm_state = new_state; self.fsm_state = new_state;
@@ -95,22 +93,20 @@ impl Fsm for EllipseToolFsmState {
fn transition( fn transition(
self, self,
event: ToolMessage, event: ToolMessage,
document: &DocumentMessageHandler, tool_data: &mut Self::ToolData,
tool_data: &DocumentToolData, (document, global_tool_data, input, font_cache): ToolActionHandlerData,
data: &mut Self::ToolData,
_tool_options: &Self::ToolOptions, _tool_options: &Self::ToolOptions,
input: &InputPreprocessorMessageHandler,
responses: &mut VecDeque<Message>, responses: &mut VecDeque<Message>,
) -> Self { ) -> Self {
use EllipseToolFsmState::*; use EllipseToolFsmState::*;
use EllipseToolMessage::*; use EllipseToolMessage::*;
let mut shape_data = &mut data.data; let mut shape_data = &mut tool_data.data;
if let ToolMessage::Ellipse(event) = event { if let ToolMessage::Ellipse(event) = event {
match (self, event) { match (self, event) {
(Ready, DragStart) => { (Ready, DragStart) => {
shape_data.start(responses, document, input.mouse.position); shape_data.start(responses, document, input.mouse.position, font_cache);
responses.push_back(DocumentMessage::StartTransaction.into()); responses.push_back(DocumentMessage::StartTransaction.into());
shape_data.path = Some(document.get_path_for_new_layer()); shape_data.path = Some(document.get_path_for_new_layer());
responses.push_back(DocumentMessage::DeselectAllLayers.into()); responses.push_back(DocumentMessage::DeselectAllLayers.into());
@@ -120,7 +116,7 @@ impl Fsm for EllipseToolFsmState {
path: shape_data.path.clone().unwrap(), path: shape_data.path.clone().unwrap(),
insert_index: -1, insert_index: -1,
transform: DAffine2::ZERO.to_cols_array(), transform: DAffine2::ZERO.to_cols_array(),
style: style::PathStyle::new(None, style::Fill::solid(tool_data.primary_color)), style: style::PathStyle::new(None, style::Fill::solid(global_tool_data.primary_color)),
} }
.into(), .into(),
); );
@@ -1,12 +1,10 @@
use crate::consts::SELECTION_TOLERANCE; use crate::consts::SELECTION_TOLERANCE;
use crate::document::DocumentMessageHandler;
use crate::frontend::utility_types::MouseCursorIcon; use crate::frontend::utility_types::MouseCursorIcon;
use crate::input::keyboard::MouseMotion; use crate::input::keyboard::MouseMotion;
use crate::input::InputPreprocessorMessageHandler;
use crate::layout::widgets::PropertyHolder; use crate::layout::widgets::PropertyHolder;
use crate::message_prelude::*; use crate::message_prelude::*;
use crate::misc::{HintData, HintGroup, HintInfo}; use crate::misc::{HintData, HintGroup, HintInfo};
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; use crate::viewport_tools::tool::{Fsm, ToolActionHandlerData};
use graphene::intersection::Quad; use graphene::intersection::Quad;
use graphene::layers::layer_info::LayerDataType; use graphene::layers::layer_info::LayerDataType;
@@ -22,7 +20,7 @@ pub struct EyedropperTool {
#[remain::sorted] #[remain::sorted]
#[impl_message(Message, ToolMessage, Eyedropper)] #[impl_message(Message, ToolMessage, Eyedropper)]
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)] #[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
pub enum EyedropperToolMessage { pub enum EyedropperToolMessage {
// Standard messages // Standard messages
#[remain::unsorted] #[remain::unsorted]
@@ -47,7 +45,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for EyedropperTo
return; return;
} }
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, &(), data.2, responses); let new_state = self.fsm_state.transition(action, &mut self.data, data, &(), responses);
if self.fsm_state != new_state { if self.fsm_state != new_state {
self.fsm_state = new_state; self.fsm_state = new_state;
@@ -80,11 +78,9 @@ impl Fsm for EyedropperToolFsmState {
fn transition( fn transition(
self, self,
event: ToolMessage, event: ToolMessage,
document: &DocumentMessageHandler, _tool_data: &mut Self::ToolData,
_tool_data: &DocumentToolData, (document, _global_tool_data, input, font_cache): ToolActionHandlerData,
_data: &mut Self::ToolData,
_tool_options: &Self::ToolOptions, _tool_options: &Self::ToolOptions,
input: &InputPreprocessorMessageHandler,
responses: &mut VecDeque<Message>, responses: &mut VecDeque<Message>,
) -> Self { ) -> Self {
use EyedropperToolFsmState::*; use EyedropperToolFsmState::*;
@@ -98,7 +94,7 @@ impl Fsm for EyedropperToolFsmState {
let quad = Quad::from_box([mouse_pos - tolerance, mouse_pos + tolerance]); let quad = Quad::from_box([mouse_pos - tolerance, mouse_pos + tolerance]);
// TODO: Destroy this pyramid // TODO: Destroy this pyramid
if let Some(path) = document.graphene_document.intersects_quad_root(quad).last() { if let Some(path) = document.graphene_document.intersects_quad_root(quad, font_cache).last() {
if let Ok(layer) = document.graphene_document.layer(path) { if let Ok(layer) = document.graphene_document.layer(path) {
if let LayerDataType::Shape(shape) = &layer.data { if let LayerDataType::Shape(shape) = &layer.data {
if shape.style.fill().is_some() { if shape.style.fill().is_some() {
+8 -12
View File
@@ -1,12 +1,10 @@
use crate::consts::SELECTION_TOLERANCE; use crate::consts::SELECTION_TOLERANCE;
use crate::document::DocumentMessageHandler;
use crate::frontend::utility_types::MouseCursorIcon; use crate::frontend::utility_types::MouseCursorIcon;
use crate::input::keyboard::MouseMotion; use crate::input::keyboard::MouseMotion;
use crate::input::InputPreprocessorMessageHandler;
use crate::layout::widgets::PropertyHolder; use crate::layout::widgets::PropertyHolder;
use crate::message_prelude::*; use crate::message_prelude::*;
use crate::misc::{HintData, HintGroup, HintInfo}; use crate::misc::{HintData, HintGroup, HintInfo};
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; use crate::viewport_tools::tool::{Fsm, ToolActionHandlerData};
use graphene::intersection::Quad; use graphene::intersection::Quad;
use graphene::Operation; use graphene::Operation;
@@ -23,7 +21,7 @@ pub struct FillTool {
#[remain::sorted] #[remain::sorted]
#[impl_message(Message, ToolMessage, Fill)] #[impl_message(Message, ToolMessage, Fill)]
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)] #[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
pub enum FillToolMessage { pub enum FillToolMessage {
// Standard messages // Standard messages
#[remain::unsorted] #[remain::unsorted]
@@ -48,7 +46,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for FillTool {
return; return;
} }
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, &(), data.2, responses); let new_state = self.fsm_state.transition(action, &mut self.data, data, &(), responses);
if self.fsm_state != new_state { if self.fsm_state != new_state {
self.fsm_state = new_state; self.fsm_state = new_state;
@@ -81,11 +79,9 @@ impl Fsm for FillToolFsmState {
fn transition( fn transition(
self, self,
event: ToolMessage, event: ToolMessage,
document: &DocumentMessageHandler, _tool_data: &mut Self::ToolData,
tool_data: &DocumentToolData, (document, global_tool_data, input, font_cache): ToolActionHandlerData,
_data: &mut Self::ToolData,
_tool_options: &Self::ToolOptions, _tool_options: &Self::ToolOptions,
input: &InputPreprocessorMessageHandler,
responses: &mut VecDeque<Message>, responses: &mut VecDeque<Message>,
) -> Self { ) -> Self {
use FillToolFsmState::*; use FillToolFsmState::*;
@@ -98,10 +94,10 @@ impl Fsm for FillToolFsmState {
let tolerance = DVec2::splat(SELECTION_TOLERANCE); let tolerance = DVec2::splat(SELECTION_TOLERANCE);
let quad = Quad::from_box([mouse_pos - tolerance, mouse_pos + tolerance]); let quad = Quad::from_box([mouse_pos - tolerance, mouse_pos + tolerance]);
if let Some(path) = document.graphene_document.intersects_quad_root(quad).last() { if let Some(path) = document.graphene_document.intersects_quad_root(quad, font_cache).last() {
let color = match lmb_or_rmb { let color = match lmb_or_rmb {
LeftMouseDown => tool_data.primary_color, LeftMouseDown => global_tool_data.primary_color,
RightMouseDown => tool_data.secondary_color, RightMouseDown => global_tool_data.secondary_color,
Abort => unreachable!(), Abort => unreachable!(),
}; };
let fill = Fill::Solid(color); let fill = Fill::Solid(color);
@@ -1,7 +1,5 @@
use crate::document::DocumentMessageHandler;
use crate::frontend::utility_types::MouseCursorIcon; use crate::frontend::utility_types::MouseCursorIcon;
use crate::input::keyboard::MouseMotion; use crate::input::keyboard::MouseMotion;
use crate::input::InputPreprocessorMessageHandler;
use crate::layout::widgets::{LayoutRow, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; use crate::layout::widgets::{LayoutRow, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::message_prelude::*; use crate::message_prelude::*;
use crate::misc::{HintData, HintGroup, HintInfo}; use crate::misc::{HintData, HintGroup, HintInfo};
@@ -92,7 +90,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for FreehandTool
return; return;
} }
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, &self.options, data.2, responses); let new_state = self.fsm_state.transition(action, &mut self.data, data, &self.options, responses);
if self.fsm_state != new_state { if self.fsm_state != new_state {
self.fsm_state = new_state; self.fsm_state = new_state;
@@ -130,11 +128,9 @@ impl Fsm for FreehandToolFsmState {
fn transition( fn transition(
self, self,
event: ToolMessage, event: ToolMessage,
document: &DocumentMessageHandler, tool_data: &mut Self::ToolData,
tool_data: &DocumentToolData, (document, global_tool_data, input, _font_cache): ToolActionHandlerData,
data: &mut Self::ToolData,
tool_options: &Self::ToolOptions, tool_options: &Self::ToolOptions,
input: &InputPreprocessorMessageHandler,
responses: &mut VecDeque<Message>, responses: &mut VecDeque<Message>,
) -> Self { ) -> Self {
use FreehandToolFsmState::*; use FreehandToolFsmState::*;
@@ -147,42 +143,42 @@ impl Fsm for FreehandToolFsmState {
(Ready, DragStart) => { (Ready, DragStart) => {
responses.push_back(DocumentMessage::StartTransaction.into()); responses.push_back(DocumentMessage::StartTransaction.into());
responses.push_back(DocumentMessage::DeselectAllLayers.into()); responses.push_back(DocumentMessage::DeselectAllLayers.into());
data.path = Some(document.get_path_for_new_layer()); tool_data.path = Some(document.get_path_for_new_layer());
let pos = transform.inverse().transform_point2(input.mouse.position); let pos = transform.inverse().transform_point2(input.mouse.position);
data.points.push(pos); tool_data.points.push(pos);
data.weight = tool_options.line_weight; tool_data.weight = tool_options.line_weight;
responses.push_back(add_polyline(data, tool_data)); responses.push_back(add_polyline(tool_data, global_tool_data));
Drawing Drawing
} }
(Drawing, PointerMove) => { (Drawing, PointerMove) => {
let pos = transform.inverse().transform_point2(input.mouse.position); let pos = transform.inverse().transform_point2(input.mouse.position);
if data.points.last() != Some(&pos) { if tool_data.points.last() != Some(&pos) {
data.points.push(pos); tool_data.points.push(pos);
} }
responses.push_back(remove_preview(data)); responses.push_back(remove_preview(tool_data));
responses.push_back(add_polyline(data, tool_data)); responses.push_back(add_polyline(tool_data, global_tool_data));
Drawing Drawing
} }
(Drawing, DragStop) | (Drawing, Abort) => { (Drawing, DragStop) | (Drawing, Abort) => {
if data.points.len() >= 2 { if tool_data.points.len() >= 2 {
responses.push_back(DocumentMessage::DeselectAllLayers.into()); responses.push_back(DocumentMessage::DeselectAllLayers.into());
responses.push_back(remove_preview(data)); responses.push_back(remove_preview(tool_data));
responses.push_back(add_polyline(data, tool_data)); responses.push_back(add_polyline(tool_data, global_tool_data));
responses.push_back(DocumentMessage::CommitTransaction.into()); responses.push_back(DocumentMessage::CommitTransaction.into());
} else { } else {
responses.push_back(DocumentMessage::AbortTransaction.into()); responses.push_back(DocumentMessage::AbortTransaction.into());
} }
data.path = None; tool_data.path = None;
data.points.clear(); tool_data.points.clear();
Ready Ready
} }
@@ -2,12 +2,11 @@ use crate::consts::{COLOR_ACCENT, LINE_ROTATE_SNAP_ANGLE, SELECTION_TOLERANCE, V
use crate::document::DocumentMessageHandler; use crate::document::DocumentMessageHandler;
use crate::frontend::utility_types::MouseCursorIcon; use crate::frontend::utility_types::MouseCursorIcon;
use crate::input::keyboard::{Key, MouseMotion}; use crate::input::keyboard::{Key, MouseMotion};
use crate::input::InputPreprocessorMessageHandler;
use crate::layout::widgets::{LayoutRow, PropertyHolder, RadioEntryData, RadioInput, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; use crate::layout::widgets::{LayoutRow, PropertyHolder, RadioEntryData, RadioInput, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::message_prelude::*; use crate::message_prelude::*;
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
use crate::viewport_tools::snapping::SnapHandler; use crate::viewport_tools::snapping::SnapHandler;
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; use crate::viewport_tools::tool::{Fsm, ToolActionHandlerData};
use graphene::color::Color; use graphene::color::Color;
use graphene::intersection::Quad; use graphene::intersection::Quad;
@@ -16,6 +15,7 @@ use graphene::layers::style::{Fill, Gradient, GradientType, PathStyle, Stroke};
use graphene::Operation; use graphene::Operation;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use graphene::layers::text_layer::FontCache;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Default)] #[derive(Default)]
@@ -37,7 +37,7 @@ impl Default for GradientOptions {
#[remain::sorted] #[remain::sorted]
#[impl_message(Message, ToolMessage, Gradient)] #[impl_message(Message, ToolMessage, Gradient)]
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)] #[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
pub enum GradientToolMessage { pub enum GradientToolMessage {
// Standard messages // Standard messages
#[remain::unsorted] #[remain::unsorted]
@@ -55,7 +55,7 @@ pub enum GradientToolMessage {
} }
#[remain::sorted] #[remain::sorted]
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)] #[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
pub enum GradientOptionsUpdate { pub enum GradientOptionsUpdate {
Type(GradientType), Type(GradientType),
} }
@@ -78,7 +78,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for GradientTool
return; return;
} }
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, &self.options, data.2, responses); let new_state = self.fsm_state.transition(action, &mut self.data, data, &self.options, responses);
if self.fsm_state != new_state { if self.fsm_state != new_state {
self.fsm_state = new_state; self.fsm_state = new_state;
@@ -128,8 +128,8 @@ impl Default for GradientToolFsmState {
} }
/// Computes the transform from gradient space to layer space (where gradient space is 0..1 in layer space) /// Computes the transform from gradient space to layer space (where gradient space is 0..1 in layer space)
fn gradient_space_transform(path: &[LayerId], layer: &Layer, document: &DocumentMessageHandler) -> DAffine2 { fn gradient_space_transform(path: &[LayerId], layer: &Layer, document: &DocumentMessageHandler, font_cache: &FontCache) -> DAffine2 {
let bounds = layer.aabounding_box_for_transform(DAffine2::IDENTITY, &document.graphene_document.font_cache).unwrap(); let bounds = layer.aabounding_box_for_transform(DAffine2::IDENTITY, font_cache).unwrap();
let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]); let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
let multiplied = document.graphene_document.multiply_transforms(path).unwrap(); let multiplied = document.graphene_document.multiply_transforms(path).unwrap();
@@ -183,8 +183,8 @@ impl GradientOverlay {
path path
} }
pub fn new(fill: &Gradient, dragging_start: Option<bool>, path: &[LayerId], layer: &Layer, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) -> Self { pub fn new(fill: &Gradient, dragging_start: Option<bool>, path: &[LayerId], layer: &Layer, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>, font_cache: &FontCache) -> Self {
let transform = gradient_space_transform(path, layer, document); let transform = gradient_space_transform(path, layer, document, font_cache);
let Gradient { start, end, .. } = fill; let Gradient { start, end, .. } = fill;
let [start, end] = [transform.transform_point2(*start), transform.transform_point2(*end)]; let [start, end] = [transform.transform_point2(*start), transform.transform_point2(*end)];
@@ -232,8 +232,8 @@ struct SelectedGradient {
} }
impl SelectedGradient { impl SelectedGradient {
pub fn new(gradient: Gradient, path: &[LayerId], layer: &Layer, document: &DocumentMessageHandler) -> Self { pub fn new(gradient: Gradient, path: &[LayerId], layer: &Layer, document: &DocumentMessageHandler, font_cache: &FontCache) -> Self {
let transform = gradient_space_transform(path, layer, document); let transform = gradient_space_transform(path, layer, document, font_cache);
Self { Self {
path: path.to_vec(), path: path.to_vec(),
transform, transform,
@@ -291,8 +291,8 @@ struct GradientToolData {
snap_handler: SnapHandler, snap_handler: SnapHandler,
} }
pub fn start_snap(snap_handler: &mut SnapHandler, document: &DocumentMessageHandler) { pub fn start_snap(snap_handler: &mut SnapHandler, document: &DocumentMessageHandler, font_cache: &FontCache) {
snap_handler.start_snap(document, document.bounding_boxes(None, None), true, true); snap_handler.start_snap(document, document.bounding_boxes(None, None, font_cache), true, true);
snap_handler.add_all_document_handles(document, &[], &[]); snap_handler.add_all_document_handles(document, &[], &[]);
} }
@@ -303,17 +303,15 @@ impl Fsm for GradientToolFsmState {
fn transition( fn transition(
self, self,
event: ToolMessage, event: ToolMessage,
document: &DocumentMessageHandler, tool_data: &mut Self::ToolData,
tool_data: &DocumentToolData, (document, global_tool_data, input, font_cache): ToolActionHandlerData,
data: &mut Self::ToolData,
tool_options: &Self::ToolOptions, tool_options: &Self::ToolOptions,
input: &InputPreprocessorMessageHandler,
responses: &mut VecDeque<Message>, responses: &mut VecDeque<Message>,
) -> Self { ) -> Self {
if let ToolMessage::Gradient(event) = event { if let ToolMessage::Gradient(event) = event {
match (self, event) { match (self, event) {
(_, GradientToolMessage::DocumentIsDirty) => { (_, GradientToolMessage::DocumentIsDirty) => {
while let Some(overlay) = data.gradient_overlays.pop() { while let Some(overlay) = tool_data.gradient_overlays.pop() {
overlay.delete_overlays(responses); overlay.delete_overlays(responses);
} }
@@ -321,11 +319,13 @@ impl Fsm for GradientToolFsmState {
let layer = document.graphene_document.layer(path).unwrap(); let layer = document.graphene_document.layer(path).unwrap();
if let Ok(Fill::Gradient(gradient)) = layer.style().map(|style| style.fill()) { if let Ok(Fill::Gradient(gradient)) = layer.style().map(|style| style.fill()) {
let dragging_start = data let dragging_start = tool_data
.selected_gradient .selected_gradient
.as_ref() .as_ref()
.and_then(|selected| if selected.path == path { Some(selected.dragging_start) } else { None }); .and_then(|selected| if selected.path == path { Some(selected.dragging_start) } else { None });
data.gradient_overlays.push(GradientOverlay::new(gradient, dragging_start, path, layer, document, responses)) tool_data
.gradient_overlays
.push(GradientOverlay::new(gradient, dragging_start, path, layer, document, responses, font_cache))
} }
} }
@@ -338,11 +338,11 @@ impl Fsm for GradientToolFsmState {
let tolerance = VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE.powi(2); let tolerance = VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE.powi(2);
let mut dragging = false; let mut dragging = false;
for overlay in &data.gradient_overlays { for overlay in &tool_data.gradient_overlays {
if overlay.evaluate_gradient_start().distance_squared(mouse) < tolerance { if overlay.evaluate_gradient_start().distance_squared(mouse) < tolerance {
dragging = true; dragging = true;
start_snap(&mut data.snap_handler, document); start_snap(&mut tool_data.snap_handler, document, font_cache);
data.selected_gradient = Some(SelectedGradient { tool_data.selected_gradient = Some(SelectedGradient {
path: overlay.path.clone(), path: overlay.path.clone(),
transform: overlay.transform, transform: overlay.transform,
gradient: overlay.gradient.clone(), gradient: overlay.gradient.clone(),
@@ -351,8 +351,8 @@ impl Fsm for GradientToolFsmState {
} }
if overlay.evaluate_gradient_end().distance_squared(mouse) < tolerance { if overlay.evaluate_gradient_end().distance_squared(mouse) < tolerance {
dragging = true; dragging = true;
start_snap(&mut data.snap_handler, document); start_snap(&mut tool_data.snap_handler, document, font_cache);
data.selected_gradient = Some(SelectedGradient { tool_data.selected_gradient = Some(SelectedGradient {
path: overlay.path.clone(), path: overlay.path.clone(),
transform: overlay.transform, transform: overlay.transform,
gradient: overlay.gradient.clone(), gradient: overlay.gradient.clone(),
@@ -365,7 +365,7 @@ impl Fsm for GradientToolFsmState {
} else { } else {
let tolerance = DVec2::splat(SELECTION_TOLERANCE); let tolerance = DVec2::splat(SELECTION_TOLERANCE);
let quad = Quad::from_box([input.mouse.position - tolerance, input.mouse.position + tolerance]); let quad = Quad::from_box([input.mouse.position - tolerance, input.mouse.position + tolerance]);
let intersection = document.graphene_document.intersects_quad_root(quad).pop(); let intersection = document.graphene_document.intersects_quad_root(quad, font_cache).pop();
if let Some(intersection) = intersection { if let Some(intersection) = intersection {
if !document.selected_layers_contains(&intersection) { if !document.selected_layers_contains(&intersection) {
@@ -378,19 +378,19 @@ impl Fsm for GradientToolFsmState {
let gradient = Gradient::new( let gradient = Gradient::new(
DVec2::ZERO, DVec2::ZERO,
tool_data.secondary_color, global_tool_data.secondary_color,
DVec2::ONE, DVec2::ONE,
tool_data.primary_color, global_tool_data.primary_color,
DAffine2::IDENTITY, DAffine2::IDENTITY,
generate_uuid(), generate_uuid(),
tool_options.gradient_type, tool_options.gradient_type,
); );
let mut selected_gradient = SelectedGradient::new(gradient, &intersection, layer, document).with_gradient_start(input.mouse.position); let mut selected_gradient = SelectedGradient::new(gradient, &intersection, layer, document, font_cache).with_gradient_start(input.mouse.position);
selected_gradient.update_gradient(input.mouse.position, responses, false, tool_options.gradient_type); selected_gradient.update_gradient(input.mouse.position, responses, false, tool_options.gradient_type);
data.selected_gradient = Some(selected_gradient); tool_data.selected_gradient = Some(selected_gradient);
start_snap(&mut data.snap_handler, document); start_snap(&mut tool_data.snap_handler, document, font_cache);
GradientToolFsmState::Drawing GradientToolFsmState::Drawing
} else { } else {
@@ -399,23 +399,23 @@ impl Fsm for GradientToolFsmState {
} }
} }
(GradientToolFsmState::Drawing, GradientToolMessage::PointerMove { constrain_axis }) => { (GradientToolFsmState::Drawing, GradientToolMessage::PointerMove { constrain_axis }) => {
if let Some(selected_gradient) = &mut data.selected_gradient { if let Some(selected_gradient) = &mut tool_data.selected_gradient {
let mouse = data.snap_handler.snap_position(responses, document, input.mouse.position); let mouse = tool_data.snap_handler.snap_position(responses, document, input.mouse.position);
selected_gradient.update_gradient(mouse, responses, input.keyboard.get(constrain_axis as usize), selected_gradient.gradient.gradient_type); selected_gradient.update_gradient(mouse, responses, input.keyboard.get(constrain_axis as usize), selected_gradient.gradient.gradient_type);
} }
GradientToolFsmState::Drawing GradientToolFsmState::Drawing
} }
(GradientToolFsmState::Drawing, GradientToolMessage::PointerUp) => { (GradientToolFsmState::Drawing, GradientToolMessage::PointerUp) => {
data.snap_handler.cleanup(responses); tool_data.snap_handler.cleanup(responses);
GradientToolFsmState::Ready GradientToolFsmState::Ready
} }
(_, GradientToolMessage::Abort) => { (_, GradientToolMessage::Abort) => {
data.snap_handler.cleanup(responses); tool_data.snap_handler.cleanup(responses);
while let Some(overlay) = data.gradient_overlays.pop() { while let Some(overlay) = tool_data.gradient_overlays.pop() {
overlay.delete_overlays(responses); overlay.delete_overlays(responses);
} }
GradientToolFsmState::Ready GradientToolFsmState::Ready
+27 -31
View File
@@ -1,14 +1,12 @@
use crate::consts::{DRAG_THRESHOLD, LINE_ROTATE_SNAP_ANGLE}; use crate::consts::{DRAG_THRESHOLD, LINE_ROTATE_SNAP_ANGLE};
use crate::document::DocumentMessageHandler;
use crate::frontend::utility_types::MouseCursorIcon; use crate::frontend::utility_types::MouseCursorIcon;
use crate::input::keyboard::{Key, MouseMotion}; use crate::input::keyboard::{Key, MouseMotion};
use crate::input::mouse::ViewportPosition; use crate::input::mouse::ViewportPosition;
use crate::input::InputPreprocessorMessageHandler;
use crate::layout::widgets::{LayoutRow, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; use crate::layout::widgets::{LayoutRow, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::message_prelude::*; use crate::message_prelude::*;
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
use crate::viewport_tools::snapping::SnapHandler; use crate::viewport_tools::snapping::SnapHandler;
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; use crate::viewport_tools::tool::{Fsm, ToolActionHandlerData};
use graphene::layers::style; use graphene::layers::style;
use graphene::Operation; use graphene::Operation;
@@ -19,7 +17,7 @@ use serde::{Deserialize, Serialize};
#[derive(Default)] #[derive(Default)]
pub struct LineTool { pub struct LineTool {
fsm_state: LineToolFsmState, fsm_state: LineToolFsmState,
data: LineToolData, tool_data: LineToolData,
options: LineOptions, options: LineOptions,
} }
@@ -75,7 +73,7 @@ impl PropertyHolder for LineTool {
} }
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for LineTool { impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for LineTool {
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) { fn process_action(&mut self, action: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
if action == ToolMessage::UpdateHints { if action == ToolMessage::UpdateHints {
self.fsm_state.update_hints(responses); self.fsm_state.update_hints(responses);
return; return;
@@ -93,7 +91,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for LineTool {
return; return;
} }
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, &self.options, data.2, responses); let new_state = self.fsm_state.transition(action, &mut self.tool_data, tool_data, &self.options, responses);
if self.fsm_state != new_state { if self.fsm_state != new_state {
self.fsm_state = new_state; self.fsm_state = new_state;
@@ -141,11 +139,9 @@ impl Fsm for LineToolFsmState {
fn transition( fn transition(
self, self,
event: ToolMessage, event: ToolMessage,
document: &DocumentMessageHandler, tool_data: &mut Self::ToolData,
tool_data: &DocumentToolData, (document, global_tool_data, input, font_cache): ToolActionHandlerData,
data: &mut Self::ToolData,
tool_options: &Self::ToolOptions, tool_options: &Self::ToolOptions,
input: &InputPreprocessorMessageHandler,
responses: &mut VecDeque<Message>, responses: &mut VecDeque<Message>,
) -> Self { ) -> Self {
use LineToolFsmState::*; use LineToolFsmState::*;
@@ -154,22 +150,22 @@ impl Fsm for LineToolFsmState {
if let ToolMessage::Line(event) = event { if let ToolMessage::Line(event) = event {
match (self, event) { match (self, event) {
(Ready, DragStart) => { (Ready, DragStart) => {
data.snap_handler.start_snap(document, document.bounding_boxes(None, None), true, true); tool_data.snap_handler.start_snap(document, document.bounding_boxes(None, None, font_cache), true, true);
data.snap_handler.add_all_document_handles(document, &[], &[]); tool_data.snap_handler.add_all_document_handles(document, &[], &[]);
data.drag_start = data.snap_handler.snap_position(responses, document, input.mouse.position); tool_data.drag_start = tool_data.snap_handler.snap_position(responses, document, input.mouse.position);
responses.push_back(DocumentMessage::StartTransaction.into()); responses.push_back(DocumentMessage::StartTransaction.into());
data.path = Some(document.get_path_for_new_layer()); tool_data.path = Some(document.get_path_for_new_layer());
responses.push_back(DocumentMessage::DeselectAllLayers.into()); responses.push_back(DocumentMessage::DeselectAllLayers.into());
data.weight = tool_options.line_weight; tool_data.weight = tool_options.line_weight;
responses.push_back( responses.push_back(
Operation::AddLine { Operation::AddLine {
path: data.path.clone().unwrap(), path: tool_data.path.clone().unwrap(),
insert_index: -1, insert_index: -1,
transform: DAffine2::ZERO.to_cols_array(), transform: DAffine2::ZERO.to_cols_array(),
style: style::PathStyle::new(Some(style::Stroke::new(tool_data.primary_color, data.weight)), style::Fill::None), style: style::PathStyle::new(Some(style::Stroke::new(global_tool_data.primary_color, tool_data.weight)), style::Fill::None),
} }
.into(), .into(),
); );
@@ -177,30 +173,30 @@ impl Fsm for LineToolFsmState {
Drawing Drawing
} }
(Drawing, Redraw { center, snap_angle, lock_angle }) => { (Drawing, Redraw { center, snap_angle, lock_angle }) => {
data.drag_current = data.snap_handler.snap_position(responses, document, input.mouse.position); tool_data.drag_current = tool_data.snap_handler.snap_position(responses, document, input.mouse.position);
let values: Vec<_> = [lock_angle, snap_angle, center].iter().map(|k| input.keyboard.get(*k as usize)).collect(); let values: Vec<_> = [lock_angle, snap_angle, center].iter().map(|k| input.keyboard.get(*k as usize)).collect();
responses.push_back(generate_transform(data, values[0], values[1], values[2])); responses.push_back(generate_transform(tool_data, values[0], values[1], values[2]));
Drawing Drawing
} }
(Drawing, DragStop) => { (Drawing, DragStop) => {
data.drag_current = data.snap_handler.snap_position(responses, document, input.mouse.position); tool_data.drag_current = tool_data.snap_handler.snap_position(responses, document, input.mouse.position);
data.snap_handler.cleanup(responses); tool_data.snap_handler.cleanup(responses);
match data.drag_start.distance(input.mouse.position) <= DRAG_THRESHOLD { match tool_data.drag_start.distance(input.mouse.position) <= DRAG_THRESHOLD {
true => responses.push_back(DocumentMessage::AbortTransaction.into()), true => responses.push_back(DocumentMessage::AbortTransaction.into()),
false => responses.push_back(DocumentMessage::CommitTransaction.into()), false => responses.push_back(DocumentMessage::CommitTransaction.into()),
} }
data.path = None; tool_data.path = None;
Ready Ready
} }
(Drawing, Abort) => { (Drawing, Abort) => {
data.snap_handler.cleanup(responses); tool_data.snap_handler.cleanup(responses);
responses.push_back(DocumentMessage::AbortTransaction.into()); responses.push_back(DocumentMessage::AbortTransaction.into());
data.path = None; tool_data.path = None;
Ready Ready
} }
_ => self, _ => self,
@@ -268,16 +264,16 @@ impl Fsm for LineToolFsmState {
} }
} }
fn generate_transform(data: &mut LineToolData, lock: bool, snap: bool, center: bool) -> Message { fn generate_transform(tool_data: &mut LineToolData, lock: bool, snap: bool, center: bool) -> Message {
let mut start = data.drag_start; let mut start = tool_data.drag_start;
let stop = data.drag_current; let stop = tool_data.drag_current;
let dir = stop - start; let dir = stop - start;
let mut angle = -dir.angle_between(DVec2::X); let mut angle = -dir.angle_between(DVec2::X);
if lock { if lock {
angle = data.angle angle = tool_data.angle
}; };
if snap { if snap {
@@ -285,7 +281,7 @@ fn generate_transform(data: &mut LineToolData, lock: bool, snap: bool, center: b
angle = (angle / snap_resolution).round() * snap_resolution; angle = (angle / snap_resolution).round() * snap_resolution;
} }
data.angle = angle; tool_data.angle = angle;
let mut scale = dir.length(); let mut scale = dir.length();
@@ -300,7 +296,7 @@ fn generate_transform(data: &mut LineToolData, lock: bool, snap: bool, center: b
} }
Operation::SetLayerTransformInViewport { Operation::SetLayerTransformInViewport {
path: data.path.clone().unwrap(), path: tool_data.path.clone().unwrap(),
transform: glam::DAffine2::from_scale_angle_translation(DVec2::new(scale, 1.), angle, start).to_cols_array(), transform: glam::DAffine2::from_scale_angle_translation(DVec2::new(scale, 1.), angle, start).to_cols_array(),
} }
.into() .into()
@@ -1,11 +1,9 @@
use crate::document::DocumentMessageHandler;
use crate::frontend::utility_types::MouseCursorIcon; use crate::frontend::utility_types::MouseCursorIcon;
use crate::input::keyboard::{Key, MouseMotion}; use crate::input::keyboard::{Key, MouseMotion};
use crate::input::InputPreprocessorMessageHandler;
use crate::layout::widgets::PropertyHolder; use crate::layout::widgets::PropertyHolder;
use crate::message_prelude::*; use crate::message_prelude::*;
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; use crate::viewport_tools::tool::{Fsm, ToolActionHandlerData};
use glam::DVec2; use glam::DVec2;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -13,12 +11,12 @@ use serde::{Deserialize, Serialize};
#[derive(Default)] #[derive(Default)]
pub struct NavigateTool { pub struct NavigateTool {
fsm_state: NavigateToolFsmState, fsm_state: NavigateToolFsmState,
data: NavigateToolData, tool_data: NavigateToolData,
} }
#[remain::sorted] #[remain::sorted]
#[impl_message(Message, ToolMessage, Navigate)] #[impl_message(Message, ToolMessage, Navigate)]
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)] #[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
pub enum NavigateToolMessage { pub enum NavigateToolMessage {
// Standard messages // Standard messages
#[remain::unsorted] #[remain::unsorted]
@@ -41,7 +39,7 @@ pub enum NavigateToolMessage {
impl PropertyHolder for NavigateTool {} impl PropertyHolder for NavigateTool {}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for NavigateTool { impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for NavigateTool {
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) { fn process_action(&mut self, action: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
if action == ToolMessage::UpdateHints { if action == ToolMessage::UpdateHints {
self.fsm_state.update_hints(responses); self.fsm_state.update_hints(responses);
return; return;
@@ -52,7 +50,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for NavigateTool
return; return;
} }
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, &(), data.2, responses); let new_state = self.fsm_state.transition(action, &mut self.tool_data, tool_data, &(), responses);
if self.fsm_state != new_state { if self.fsm_state != new_state {
self.fsm_state = new_state; self.fsm_state = new_state;
@@ -97,11 +95,9 @@ impl Fsm for NavigateToolFsmState {
fn transition( fn transition(
self, self,
message: ToolMessage, message: ToolMessage,
_document: &DocumentMessageHandler, tool_data: &mut Self::ToolData,
_tool_data: &DocumentToolData, (_document, _global_tool_data, input, _font_cache): ToolActionHandlerData,
data: &mut Self::ToolData,
_tool_options: &Self::ToolOptions, _tool_options: &Self::ToolOptions,
input: &InputPreprocessorMessageHandler,
messages: &mut VecDeque<Message>, messages: &mut VecDeque<Message>,
) -> Self { ) -> Self {
if let ToolMessage::Navigate(navigate) = message { if let ToolMessage::Navigate(navigate) = message {
@@ -112,7 +108,7 @@ impl Fsm for NavigateToolFsmState {
messages.push_front(MovementMessage::TransformCanvasEnd.into()); messages.push_front(MovementMessage::TransformCanvasEnd.into());
// Mouse has not moved from pointerdown to pointerup // Mouse has not moved from pointerdown to pointerup
if data.drag_start == input.mouse.position { if tool_data.drag_start == input.mouse.position {
messages.push_front(if zoom_in { messages.push_front(if zoom_in {
MovementMessage::IncreaseCanvasZoom { center_on_mouse: true }.into() MovementMessage::IncreaseCanvasZoom { center_on_mouse: true }.into()
} else { } else {
@@ -128,24 +124,24 @@ impl Fsm for NavigateToolFsmState {
snap_angle, snap_angle,
wait_for_snap_angle_release: false, wait_for_snap_angle_release: false,
snap_zoom, snap_zoom,
zoom_from_viewport: Some(data.drag_start), zoom_from_viewport: Some(tool_data.drag_start),
} }
.into(), .into(),
); );
self self
} }
TranslateCanvasBegin => { TranslateCanvasBegin => {
data.drag_start = input.mouse.position; tool_data.drag_start = input.mouse.position;
messages.push_front(MovementMessage::TranslateCanvasBegin.into()); messages.push_front(MovementMessage::TranslateCanvasBegin.into());
NavigateToolFsmState::Panning NavigateToolFsmState::Panning
} }
RotateCanvasBegin => { RotateCanvasBegin => {
data.drag_start = input.mouse.position; tool_data.drag_start = input.mouse.position;
messages.push_front(MovementMessage::RotateCanvasBegin.into()); messages.push_front(MovementMessage::RotateCanvasBegin.into());
NavigateToolFsmState::Tilting NavigateToolFsmState::Tilting
} }
ZoomCanvasBegin => { ZoomCanvasBegin => {
data.drag_start = input.mouse.position; tool_data.drag_start = input.mouse.position;
messages.push_front(MovementMessage::ZoomCanvasBegin.into()); messages.push_front(MovementMessage::ZoomCanvasBegin.into());
NavigateToolFsmState::Zooming NavigateToolFsmState::Zooming
} }
+30 -32
View File
@@ -1,13 +1,11 @@
use crate::consts::SELECTION_THRESHOLD; use crate::consts::SELECTION_THRESHOLD;
use crate::document::DocumentMessageHandler;
use crate::frontend::utility_types::MouseCursorIcon; use crate::frontend::utility_types::MouseCursorIcon;
use crate::input::keyboard::{Key, MouseMotion}; use crate::input::keyboard::{Key, MouseMotion};
use crate::input::InputPreprocessorMessageHandler;
use crate::layout::widgets::PropertyHolder; use crate::layout::widgets::PropertyHolder;
use crate::message_prelude::*; use crate::message_prelude::*;
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
use crate::viewport_tools::snapping::SnapHandler; use crate::viewport_tools::snapping::SnapHandler;
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; use crate::viewport_tools::tool::{Fsm, ToolActionHandlerData};
use crate::viewport_tools::vector_editor::shape_editor::ShapeEditor; use crate::viewport_tools::vector_editor::shape_editor::ShapeEditor;
use graphene::intersection::Quad; use graphene::intersection::Quad;
@@ -18,12 +16,12 @@ use serde::{Deserialize, Serialize};
#[derive(Default)] #[derive(Default)]
pub struct PathTool { pub struct PathTool {
fsm_state: PathToolFsmState, fsm_state: PathToolFsmState,
data: PathToolData, tool_data: PathToolData,
} }
#[remain::sorted] #[remain::sorted]
#[impl_message(Message, ToolMessage, Path)] #[impl_message(Message, ToolMessage, Path)]
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)] #[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
pub enum PathToolMessage { pub enum PathToolMessage {
// Standard messages // Standard messages
#[remain::unsorted] #[remain::unsorted]
@@ -47,7 +45,7 @@ pub enum PathToolMessage {
impl PropertyHolder for PathTool {} impl PropertyHolder for PathTool {}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for PathTool { impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for PathTool {
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) { fn process_action(&mut self, action: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
if action == ToolMessage::UpdateHints { if action == ToolMessage::UpdateHints {
self.fsm_state.update_hints(responses); self.fsm_state.update_hints(responses);
return; return;
@@ -58,7 +56,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for PathTool {
return; return;
} }
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, &(), data.2, responses); let new_state = self.fsm_state.transition(action, &mut self.tool_data, tool_data, &(), responses);
if self.fsm_state != new_state { if self.fsm_state != new_state {
self.fsm_state = new_state; self.fsm_state = new_state;
@@ -107,11 +105,9 @@ impl Fsm for PathToolFsmState {
fn transition( fn transition(
self, self,
event: ToolMessage, event: ToolMessage,
document: &DocumentMessageHandler, tool_data: &mut Self::ToolData,
_tool_data: &DocumentToolData, (document, _global_tool_data, input, font_cache): ToolActionHandlerData,
data: &mut Self::ToolData,
_tool_options: &Self::ToolOptions, _tool_options: &Self::ToolOptions,
input: &InputPreprocessorMessageHandler,
responses: &mut VecDeque<Message>, responses: &mut VecDeque<Message>,
) -> Self { ) -> Self {
if let ToolMessage::Path(event) = event { if let ToolMessage::Path(event) = event {
@@ -122,17 +118,17 @@ impl Fsm for PathToolFsmState {
// TODO: Capture a tool event instead of doing this? // TODO: Capture a tool event instead of doing this?
(_, SelectionChanged) => { (_, SelectionChanged) => {
// Remove any residual overlays that might exist on selection change // Remove any residual overlays that might exist on selection change
data.shape_editor.remove_overlays(responses); tool_data.shape_editor.remove_overlays(responses);
// This currently creates new VectorManipulatorShapes for every shape, which is not ideal // This currently creates new VectorManipulatorShapes for every shape, which is not ideal
// At least it is only on selection change for now // At least it is only on selection change for now
data.shape_editor.set_shapes_to_modify(document.selected_visible_layers_vector_shapes(responses)); tool_data.shape_editor.set_shapes_to_modify(document.selected_visible_layers_vector_shapes(responses, font_cache));
self self
} }
(_, DocumentIsDirty) => { (_, DocumentIsDirty) => {
// Update the VectorManipulatorShapes by reference so they match the kurbo data // Update the VectorManipulatorShapes by reference so they match the kurbo tool_data
for shape in &mut data.shape_editor.shapes_to_modify { for shape in &mut tool_data.shape_editor.shapes_to_modify {
shape.update_shape(document, responses); shape.update_shape(document, responses);
} }
self self
@@ -142,16 +138,18 @@ impl Fsm for PathToolFsmState {
let add_to_selection = input.keyboard.get(add_to_selection as usize); let add_to_selection = input.keyboard.get(add_to_selection as usize);
// Select the first point within the threshold (in pixels) // Select the first point within the threshold (in pixels)
if data.shape_editor.select_point(input.mouse.position, SELECTION_THRESHOLD, add_to_selection, responses) { if tool_data.shape_editor.select_point(input.mouse.position, SELECTION_THRESHOLD, add_to_selection, responses) {
responses.push_back(DocumentMessage::StartTransaction.into()); responses.push_back(DocumentMessage::StartTransaction.into());
let ignore_document = data.shape_editor.shapes_to_modify.iter().map(|shape| shape.layer_path.clone()).collect::<Vec<_>>(); let ignore_document = tool_data.shape_editor.shapes_to_modify.iter().map(|shape| shape.layer_path.clone()).collect::<Vec<_>>();
data.snap_handler.start_snap(document, document.bounding_boxes(Some(&ignore_document), None), true, true); tool_data
.snap_handler
.start_snap(document, document.bounding_boxes(Some(&ignore_document), None, font_cache), true, true);
let include_handles = data.shape_editor.shapes_to_modify.iter().map(|shape| shape.layer_path.as_slice()).collect::<Vec<_>>(); let include_handles = tool_data.shape_editor.shapes_to_modify.iter().map(|shape| shape.layer_path.as_slice()).collect::<Vec<_>>();
data.snap_handler.add_all_document_handles(document, &include_handles, &[]); tool_data.snap_handler.add_all_document_handles(document, &include_handles, &[]);
data.drag_start_pos = input.mouse.position; tool_data.drag_start_pos = input.mouse.position;
Dragging Dragging
} }
// We didn't find a point nearby, so consider selecting the nearest shape instead // We didn't find a point nearby, so consider selecting the nearest shape instead
@@ -160,7 +158,7 @@ impl Fsm for PathToolFsmState {
// Select shapes directly under our mouse // Select shapes directly under our mouse
let intersection = document let intersection = document
.graphene_document .graphene_document
.intersects_quad_root(Quad::from_box([input.mouse.position - selection_size, input.mouse.position + selection_size])); .intersects_quad_root(Quad::from_box([input.mouse.position - selection_size, input.mouse.position + selection_size]), font_cache);
if !intersection.is_empty() { if !intersection.is_empty() {
if add_to_selection { if add_to_selection {
responses.push_back(DocumentMessage::AddSelectedLayers { additional_layers: intersection }.into()); responses.push_back(DocumentMessage::AddSelectedLayers { additional_layers: intersection }.into());
@@ -191,33 +189,33 @@ impl Fsm for PathToolFsmState {
) => { ) => {
// Determine when alt state changes // Determine when alt state changes
let alt_pressed = input.keyboard.get(alt_mirror_angle as usize); let alt_pressed = input.keyboard.get(alt_mirror_angle as usize);
if alt_pressed != data.alt_debounce { if alt_pressed != tool_data.alt_debounce {
data.alt_debounce = alt_pressed; tool_data.alt_debounce = alt_pressed;
// Only on alt down // Only on alt down
if alt_pressed { if alt_pressed {
data.shape_editor.toggle_selected_mirror_angle(); tool_data.shape_editor.toggle_selected_mirror_angle();
} }
} }
// Determine when shift state changes // Determine when shift state changes
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize); let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
if shift_pressed != data.shift_debounce { if shift_pressed != tool_data.shift_debounce {
data.shift_debounce = shift_pressed; tool_data.shift_debounce = shift_pressed;
data.shape_editor.toggle_selected_mirror_distance(); tool_data.shape_editor.toggle_selected_mirror_distance();
} }
// Move the selected points by the mouse position // Move the selected points by the mouse position
let snapped_position = data.snap_handler.snap_position(responses, document, input.mouse.position); let snapped_position = tool_data.snap_handler.snap_position(responses, document, input.mouse.position);
data.shape_editor.move_selected_points(snapped_position - data.drag_start_pos, true, responses); tool_data.shape_editor.move_selected_points(snapped_position - tool_data.drag_start_pos, true, responses);
Dragging Dragging
} }
// Mouse up // Mouse up
(_, DragStop) => { (_, DragStop) => {
data.snap_handler.cleanup(responses); tool_data.snap_handler.cleanup(responses);
Ready Ready
} }
(_, Abort) => { (_, Abort) => {
data.shape_editor.remove_overlays(responses); tool_data.shape_editor.remove_overlays(responses);
Ready Ready
} }
( (
+66 -68
View File
@@ -7,7 +7,7 @@ use crate::layout::widgets::{LayoutRow, NumberInput, PropertyHolder, Widget, Wid
use crate::message_prelude::*; use crate::message_prelude::*;
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
use crate::viewport_tools::snapping::SnapHandler; use crate::viewport_tools::snapping::SnapHandler;
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; use crate::viewport_tools::tool::{Fsm, ToolActionHandlerData};
use crate::viewport_tools::vector_editor::constants::ControlPointType; use crate::viewport_tools::vector_editor::constants::ControlPointType;
use crate::viewport_tools::vector_editor::shape_editor::ShapeEditor; use crate::viewport_tools::vector_editor::shape_editor::ShapeEditor;
use crate::viewport_tools::vector_editor::vector_shape::VectorShape; use crate::viewport_tools::vector_editor::vector_shape::VectorShape;
@@ -22,7 +22,7 @@ use serde::{Deserialize, Serialize};
#[derive(Default)] #[derive(Default)]
pub struct PenTool { pub struct PenTool {
fsm_state: PenToolFsmState, fsm_state: PenToolFsmState,
data: PenToolData, tool_data: PenToolData,
options: PenOptions, options: PenOptions,
} }
@@ -84,7 +84,7 @@ impl PropertyHolder for PenTool {
} }
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for PenTool { impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for PenTool {
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) { fn process_action(&mut self, action: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
if action == ToolMessage::UpdateHints { if action == ToolMessage::UpdateHints {
self.fsm_state.update_hints(responses); self.fsm_state.update_hints(responses);
return; return;
@@ -102,7 +102,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for PenTool {
return; return;
} }
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, &self.options, data.2, responses); let new_state = self.fsm_state.transition(action, &mut self.tool_data, tool_data, &self.options, responses);
if self.fsm_state != new_state { if self.fsm_state != new_state {
self.fsm_state = new_state; self.fsm_state = new_state;
@@ -144,11 +144,9 @@ impl Fsm for PenToolFsmState {
fn transition( fn transition(
self, self,
event: ToolMessage, event: ToolMessage,
document: &DocumentMessageHandler, tool_data: &mut Self::ToolData,
tool_data: &DocumentToolData, (document, global_tool_data, input, font_cache): ToolActionHandlerData,
data: &mut Self::ToolData,
tool_options: &Self::ToolOptions, tool_options: &Self::ToolOptions,
input: &InputPreprocessorMessageHandler,
responses: &mut VecDeque<Message>, responses: &mut VecDeque<Message>,
) -> Self { ) -> Self {
use PenToolFsmState::*; use PenToolFsmState::*;
@@ -159,7 +157,7 @@ impl Fsm for PenToolFsmState {
if let ToolMessage::Pen(event) = event { if let ToolMessage::Pen(event) = event {
match (self, event) { match (self, event) {
(_, DocumentIsDirty) => { (_, DocumentIsDirty) => {
data.shape_editor.update_shapes(document, responses); tool_data.shape_editor.update_shapes(document, responses);
self self
} }
(Ready, DragStart) => { (Ready, DragStart) => {
@@ -167,76 +165,76 @@ impl Fsm for PenToolFsmState {
responses.push_back(DocumentMessage::DeselectAllLayers.into()); responses.push_back(DocumentMessage::DeselectAllLayers.into());
// Create a new layer and prep snap system // Create a new layer and prep snap system
data.path = Some(document.get_path_for_new_layer()); tool_data.path = Some(document.get_path_for_new_layer());
data.snap_handler.start_snap(document, document.bounding_boxes(None, None), true, true); tool_data.snap_handler.start_snap(document, document.bounding_boxes(None, None, font_cache), true, true);
data.snap_handler.add_all_document_handles(document, &[], &[]); tool_data.snap_handler.add_all_document_handles(document, &[], &[]);
let snapped_position = data.snap_handler.snap_position(responses, document, input.mouse.position); let snapped_position = tool_data.snap_handler.snap_position(responses, document, input.mouse.position);
// Get the position and set properties // Get the position and set properties
let start_position = transform.inverse().transform_point2(snapped_position); let start_position = transform.inverse().transform_point2(snapped_position);
data.weight = tool_options.line_weight; tool_data.weight = tool_options.line_weight;
// Create the initial shape with a `bez_path` (only contains a moveto initially) // Create the initial shape with a `bez_path` (only contains a moveto initially)
if let Some(layer_path) = &data.path { if let Some(layer_path) = &tool_data.path {
data.bez_path = start_bez_path(start_position); tool_data.bez_path = start_bez_path(start_position);
responses.push_back( responses.push_back(
Operation::AddShape { Operation::AddShape {
path: layer_path.clone(), path: layer_path.clone(),
transform: transform.to_cols_array(), transform: transform.to_cols_array(),
insert_index: -1, insert_index: -1,
bez_path: data.bez_path.clone().into_iter().collect(), bez_path: tool_data.bez_path.clone().into_iter().collect(),
style: style::PathStyle::new(Some(style::Stroke::new(tool_data.primary_color, data.weight)), style::Fill::None), style: style::PathStyle::new(Some(style::Stroke::new(global_tool_data.primary_color, tool_data.weight)), style::Fill::None),
closed: false, closed: false,
} }
.into(), .into(),
); );
} }
add_to_curve(data, input, transform, document, responses); add_to_curve(tool_data, input, transform, document, responses);
Drawing Drawing
} }
(Drawing, DragStart) => { (Drawing, DragStart) => {
data.drag_start_position = input.mouse.position; tool_data.drag_start_position = input.mouse.position;
add_to_curve(data, input, transform, document, responses); add_to_curve(tool_data, input, transform, document, responses);
Drawing Drawing
} }
(Drawing, DragStop) => { (Drawing, DragStop) => {
// Deselect everything (this means we are no longer dragging the handle) // Deselect everything (this means we are no longer dragging the handle)
data.shape_editor.deselect_all(responses); tool_data.shape_editor.deselect_all(responses);
// If the drag does not exceed the threshold, then replace the curve with a line // If the drag does not exceed the threshold, then replace the curve with a line
if data.drag_start_position.distance(input.mouse.position) < CREATE_CURVE_THRESHOLD { if tool_data.drag_start_position.distance(input.mouse.position) < CREATE_CURVE_THRESHOLD {
// Modify the second to last element (as we have an unplaced element tracing to the cursor as the last element) // Modify the second to last element (as we have an unplaced element tracing to the cursor as the last element)
let replace_index = data.bez_path.len() - 2; let replace_index = tool_data.bez_path.len() - 2;
let line_from_curve = convert_curve_to_line(data.bez_path[replace_index]); let line_from_curve = convert_curve_to_line(tool_data.bez_path[replace_index]);
replace_path_element(data, transform, replace_index, line_from_curve, responses); replace_path_element(tool_data, transform, replace_index, line_from_curve, responses);
} }
// Reselect the last point // Reselect the last point
if let Some(last_anchor) = data.shape_editor.select_last_anchor() { if let Some(last_anchor) = tool_data.shape_editor.select_last_anchor() {
last_anchor.select_point(ControlPointType::Anchor as usize, true, responses); last_anchor.select_point(ControlPointType::Anchor as usize, true, responses);
} }
// Move the newly selected points to the cursor // Move the newly selected points to the cursor
let snapped_position = data.snap_handler.snap_position(responses, document, input.mouse.position); let snapped_position = tool_data.snap_handler.snap_position(responses, document, input.mouse.position);
data.shape_editor.move_selected_points(snapped_position, false, responses); tool_data.shape_editor.move_selected_points(snapped_position, false, responses);
Drawing Drawing
} }
(Drawing, PointerMove) => { (Drawing, PointerMove) => {
// Move selected points // Move selected points
let snapped_position = data.snap_handler.snap_position(responses, document, input.mouse.position); let snapped_position = tool_data.snap_handler.snap_position(responses, document, input.mouse.position);
data.shape_editor.move_selected_points(snapped_position, false, responses); tool_data.shape_editor.move_selected_points(snapped_position, false, responses);
Drawing Drawing
} }
(Drawing, Confirm) | (Drawing, Abort) => { (Drawing, Confirm) | (Drawing, Abort) => {
// Cleanup, we are either canceling or finished drawing // Cleanup, we are either canceling or finished drawing
if data.bez_path.len() >= 2 { if tool_data.bez_path.len() >= 2 {
// Remove the last segment // Remove the last segment
remove_from_curve(data); remove_from_curve(tool_data);
if let Some(layer_path) = &data.path { if let Some(layer_path) = &tool_data.path {
responses.push_back(apply_bez_path(layer_path.clone(), data.bez_path.clone(), transform)); responses.push_back(apply_bez_path(layer_path.clone(), tool_data.bez_path.clone(), transform));
} }
responses.push_back(DocumentMessage::DeselectAllLayers.into()); responses.push_back(DocumentMessage::DeselectAllLayers.into());
@@ -245,17 +243,17 @@ impl Fsm for PenToolFsmState {
responses.push_back(DocumentMessage::AbortTransaction.into()); responses.push_back(DocumentMessage::AbortTransaction.into());
} }
data.shape_editor.remove_overlays(responses); tool_data.shape_editor.remove_overlays(responses);
data.shape_editor.clear_shapes_to_modify(); tool_data.shape_editor.clear_shapes_to_modify();
data.path = None; tool_data.path = None;
data.snap_handler.cleanup(responses); tool_data.snap_handler.cleanup(responses);
Ready Ready
} }
(_, Abort) => { (_, Abort) => {
data.shape_editor.remove_overlays(responses); tool_data.shape_editor.remove_overlays(responses);
data.shape_editor.clear_shapes_to_modify(); tool_data.shape_editor.clear_shapes_to_modify();
Ready Ready
} }
_ => self, _ => self,
@@ -298,56 +296,56 @@ impl Fsm for PenToolFsmState {
} }
/// Add to the curve and select the second anchor of the last point and the newly added anchor point /// Add to the curve and select the second anchor of the last point and the newly added anchor point
fn add_to_curve(data: &mut PenToolData, input: &InputPreprocessorMessageHandler, transform: DAffine2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) { fn add_to_curve(tool_data: &mut PenToolData, input: &InputPreprocessorMessageHandler, transform: DAffine2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
// Refresh data's representation of the path // Refresh tool_data's representation of the path
update_path_representation(data); update_path_representation(tool_data);
// Setup our position params // Setup our position params
let snapped_position = data.snap_handler.snap_position(responses, document, input.mouse.position); let snapped_position = tool_data.snap_handler.snap_position(responses, document, input.mouse.position);
let position = transform.inverse().transform_point2(snapped_position); let position = transform.inverse().transform_point2(snapped_position);
// Add a curve to the path // Add a curve to the path
if let Some(layer_path) = &data.path { if let Some(layer_path) = &tool_data.path {
// Push curve onto path // Push curve onto path
let point = Point { x: position.x, y: position.y }; let point = Point { x: position.x, y: position.y };
data.bez_path.push(PathEl::CurveTo(point, point, point)); tool_data.bez_path.push(PathEl::CurveTo(point, point, point));
responses.push_back(apply_bez_path(layer_path.clone(), data.bez_path.clone(), transform)); responses.push_back(apply_bez_path(layer_path.clone(), tool_data.bez_path.clone(), transform));
// Clear previous overlays // Clear previous overlays
data.shape_editor.remove_overlays(responses); tool_data.shape_editor.remove_overlays(responses);
// Create a new `shape` from the updated `bez_path` // Create a new `shape` from the updated `bez_path`
let bez_path = data.bez_path.clone().into_iter().collect(); let bez_path = tool_data.bez_path.clone().into_iter().collect();
data.curve_shape = VectorShape::new(layer_path.to_vec(), transform, &bez_path, false, responses); tool_data.curve_shape = VectorShape::new(layer_path.to_vec(), transform, &bez_path, false, responses);
data.shape_editor.set_shapes_to_modify(vec![data.curve_shape.clone()]); tool_data.shape_editor.set_shapes_to_modify(vec![tool_data.curve_shape.clone()]);
// Select the second to last `PathEl`'s handle // Select the second to last `PathEl`'s handle
data.shape_editor.set_shape_selected(0); tool_data.shape_editor.set_shape_selected(0);
let handle_element = data.shape_editor.select_nth_anchor(0, -2); let handle_element = tool_data.shape_editor.select_nth_anchor(0, -2);
handle_element.select_point(ControlPointType::Handle2 as usize, true, responses); handle_element.select_point(ControlPointType::Handle2 as usize, true, responses);
// Select the last `PathEl`'s anchor point // Select the last `PathEl`'s anchor point
if let Some(last_anchor) = data.shape_editor.select_last_anchor() { if let Some(last_anchor) = tool_data.shape_editor.select_last_anchor() {
last_anchor.select_point(ControlPointType::Anchor as usize, true, responses); last_anchor.select_point(ControlPointType::Anchor as usize, true, responses);
} }
data.shape_editor.set_selected_mirror_options(true, true); tool_data.shape_editor.set_selected_mirror_options(true, true);
} }
} }
/// Replace a `PathEl` with another inside of `bez_path` by index /// Replace a `PathEl` with another inside of `bez_path` by index
fn replace_path_element(data: &mut PenToolData, transform: DAffine2, replace_index: usize, replacement: PathEl, responses: &mut VecDeque<Message>) { fn replace_path_element(tool_data: &mut PenToolData, transform: DAffine2, replace_index: usize, replacement: PathEl, responses: &mut VecDeque<Message>) {
data.bez_path[replace_index] = replacement; tool_data.bez_path[replace_index] = replacement;
if let Some(layer_path) = &data.path { if let Some(layer_path) = &tool_data.path {
responses.push_back(apply_bez_path(layer_path.clone(), data.bez_path.clone(), transform)); responses.push_back(apply_bez_path(layer_path.clone(), tool_data.bez_path.clone(), transform));
} }
} }
/// Remove a curve from the end of the `bez_path` /// Remove a curve from the end of the `bez_path`
fn remove_from_curve(data: &mut PenToolData) { fn remove_from_curve(tool_data: &mut PenToolData) {
// Refresh data's representation of the path // Refresh tool_data's representation of the path
update_path_representation(data); update_path_representation(tool_data);
data.bez_path.pop(); tool_data.bez_path.pop();
} }
/// Create the initial moveto for the `bez_path` /// Create the initial moveto for the `bez_path`
@@ -366,13 +364,13 @@ fn convert_curve_to_line(curve: PathEl) -> PathEl {
} }
} }
/// Update data's version of `bez_path` to match `ShapeEditor`'s version /// Update tool_data's version of `bez_path` to match `ShapeEditor`'s version
fn update_path_representation(data: &mut PenToolData) { fn update_path_representation(tool_data: &mut PenToolData) {
// TODO Update ShapeEditor to provide similar functionality // TODO Update ShapeEditor to provide similar functionality
// We need to make sure we have the most up-to-date bez_path // We need to make sure we have the most up-to-date bez_path
if !data.shape_editor.shapes_to_modify.is_empty() { if !tool_data.shape_editor.shapes_to_modify.is_empty() {
// Hacky way of saving the curve changes // Hacky way of saving the curve changes
data.bez_path = data.shape_editor.shapes_to_modify[0].bez_path.elements().to_vec(); tool_data.bez_path = tool_data.shape_editor.shapes_to_modify[0].bez_path.elements().to_vec();
} }
} }
@@ -1,13 +1,11 @@
use super::shared::resize::Resize; use super::shared::resize::Resize;
use crate::consts::DRAG_THRESHOLD; use crate::consts::DRAG_THRESHOLD;
use crate::document::DocumentMessageHandler;
use crate::frontend::utility_types::MouseCursorIcon; use crate::frontend::utility_types::MouseCursorIcon;
use crate::input::keyboard::{Key, MouseMotion}; use crate::input::keyboard::{Key, MouseMotion};
use crate::input::InputPreprocessorMessageHandler;
use crate::layout::widgets::PropertyHolder; use crate::layout::widgets::PropertyHolder;
use crate::message_prelude::*; use crate::message_prelude::*;
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; use crate::viewport_tools::tool::{Fsm, ToolActionHandlerData};
use graphene::layers::style; use graphene::layers::style;
use graphene::Operation; use graphene::Operation;
@@ -18,12 +16,12 @@ use serde::{Deserialize, Serialize};
#[derive(Default)] #[derive(Default)]
pub struct RectangleTool { pub struct RectangleTool {
fsm_state: RectangleToolFsmState, fsm_state: RectangleToolFsmState,
data: RectangleToolData, tool_data: RectangleToolData,
} }
#[remain::sorted] #[remain::sorted]
#[impl_message(Message, ToolMessage, Rectangle)] #[impl_message(Message, ToolMessage, Rectangle)]
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)] #[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
pub enum RectangleToolMessage { pub enum RectangleToolMessage {
// Standard messages // Standard messages
#[remain::unsorted] #[remain::unsorted]
@@ -41,7 +39,7 @@ pub enum RectangleToolMessage {
impl PropertyHolder for RectangleTool {} impl PropertyHolder for RectangleTool {}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for RectangleTool { impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for RectangleTool {
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) { fn process_action(&mut self, action: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
if action == ToolMessage::UpdateHints { if action == ToolMessage::UpdateHints {
self.fsm_state.update_hints(responses); self.fsm_state.update_hints(responses);
return; return;
@@ -52,7 +50,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for RectangleToo
return; return;
} }
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, &(), data.2, responses); let new_state = self.fsm_state.transition(action, &mut self.tool_data, tool_data, &(), responses);
if self.fsm_state != new_state { if self.fsm_state != new_state {
self.fsm_state = new_state; self.fsm_state = new_state;
@@ -94,22 +92,20 @@ impl Fsm for RectangleToolFsmState {
fn transition( fn transition(
self, self,
event: ToolMessage, event: ToolMessage,
document: &DocumentMessageHandler, tool_data: &mut Self::ToolData,
tool_data: &DocumentToolData, (document, global_tool_data, input, font_cache): ToolActionHandlerData,
data: &mut Self::ToolData,
_tool_options: &Self::ToolOptions, _tool_options: &Self::ToolOptions,
input: &InputPreprocessorMessageHandler,
responses: &mut VecDeque<Message>, responses: &mut VecDeque<Message>,
) -> Self { ) -> Self {
use RectangleToolFsmState::*; use RectangleToolFsmState::*;
use RectangleToolMessage::*; use RectangleToolMessage::*;
let mut shape_data = &mut data.data; let mut shape_data = &mut tool_data.data;
if let ToolMessage::Rectangle(event) = event { if let ToolMessage::Rectangle(event) = event {
match (self, event) { match (self, event) {
(Ready, DragStart) => { (Ready, DragStart) => {
shape_data.start(responses, document, input.mouse.position); shape_data.start(responses, document, input.mouse.position, font_cache);
responses.push_back(DocumentMessage::StartTransaction.into()); responses.push_back(DocumentMessage::StartTransaction.into());
shape_data.path = Some(document.get_path_for_new_layer()); shape_data.path = Some(document.get_path_for_new_layer());
responses.push_back(DocumentMessage::DeselectAllLayers.into()); responses.push_back(DocumentMessage::DeselectAllLayers.into());
@@ -119,7 +115,7 @@ impl Fsm for RectangleToolFsmState {
path: shape_data.path.clone().unwrap(), path: shape_data.path.clone().unwrap(),
insert_index: -1, insert_index: -1,
transform: DAffine2::ZERO.to_cols_array(), transform: DAffine2::ZERO.to_cols_array(),
style: style::PathStyle::new(None, style::Fill::solid(tool_data.primary_color)), style: style::PathStyle::new(None, style::Fill::solid(global_tool_data.primary_color)),
} }
.into(), .into(),
); );
+81 -74
View File
@@ -1,16 +1,14 @@
use crate::consts::{ROTATE_SNAP_ANGLE, SELECTION_TOLERANCE}; use crate::consts::{ROTATE_SNAP_ANGLE, SELECTION_TOLERANCE};
use crate::document::transformation::Selected; use crate::document::transformation::Selected;
use crate::document::utility_types::{AlignAggregate, AlignAxis, FlipAxis}; use crate::document::utility_types::{AlignAggregate, AlignAxis, FlipAxis};
use crate::document::DocumentMessageHandler;
use crate::frontend::utility_types::MouseCursorIcon; use crate::frontend::utility_types::MouseCursorIcon;
use crate::input::keyboard::{Key, MouseMotion}; use crate::input::keyboard::{Key, MouseMotion};
use crate::input::mouse::ViewportPosition; use crate::input::mouse::ViewportPosition;
use crate::input::InputPreprocessorMessageHandler;
use crate::layout::widgets::{IconButton, LayoutRow, PopoverButton, PropertyHolder, Separator, SeparatorDirection, SeparatorType, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; use crate::layout::widgets::{IconButton, LayoutRow, PopoverButton, PropertyHolder, Separator, SeparatorDirection, SeparatorType, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::message_prelude::*; use crate::message_prelude::*;
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
use crate::viewport_tools::snapping::{self, SnapHandler}; use crate::viewport_tools::snapping::{self, SnapHandler};
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData, ToolType}; use crate::viewport_tools::tool::{Fsm, ToolActionHandlerData, ToolType};
use graphene::boolean_ops::BooleanOperation; use graphene::boolean_ops::BooleanOperation;
use graphene::document::Document; use graphene::document::Document;
use graphene::intersection::Quad; use graphene::intersection::Quad;
@@ -26,12 +24,12 @@ use serde::{Deserialize, Serialize};
#[derive(Default)] #[derive(Default)]
pub struct SelectTool { pub struct SelectTool {
fsm_state: SelectToolFsmState, fsm_state: SelectToolFsmState,
data: SelectToolData, tool_data: SelectToolData,
} }
#[remain::sorted] #[remain::sorted]
#[impl_message(Message, ToolMessage, Select)] #[impl_message(Message, ToolMessage, Select)]
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)] #[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
pub enum SelectToolMessage { pub enum SelectToolMessage {
// Standard messages // Standard messages
#[remain::unsorted] #[remain::unsorted]
@@ -231,7 +229,7 @@ impl PropertyHolder for SelectTool {
} }
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for SelectTool { impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for SelectTool {
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) { fn process_action(&mut self, action: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
if action == ToolMessage::UpdateHints { if action == ToolMessage::UpdateHints {
self.fsm_state.update_hints(responses); self.fsm_state.update_hints(responses);
return; return;
@@ -242,7 +240,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for SelectTool {
return; return;
} }
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, &(), data.2, responses); let new_state = self.fsm_state.transition(action, &mut self.tool_data, tool_data, &(), responses);
if self.fsm_state != new_state { if self.fsm_state != new_state {
self.fsm_state = new_state; self.fsm_state = new_state;
@@ -310,11 +308,9 @@ impl Fsm for SelectToolFsmState {
fn transition( fn transition(
self, self,
event: ToolMessage, event: ToolMessage,
document: &DocumentMessageHandler, tool_data: &mut Self::ToolData,
_tool_data: &DocumentToolData, (document, _global_tool_data, input, font_cache): ToolActionHandlerData,
data: &mut Self::ToolData,
_tool_options: &Self::ToolOptions, _tool_options: &Self::ToolOptions,
input: &InputPreprocessorMessageHandler,
responses: &mut VecDeque<Message>, responses: &mut VecDeque<Message>,
) -> Self { ) -> Self {
use SelectToolFsmState::*; use SelectToolFsmState::*;
@@ -324,7 +320,7 @@ impl Fsm for SelectToolFsmState {
match (self, event) { match (self, event) {
(_, DocumentIsDirty) => { (_, DocumentIsDirty) => {
let mut buffer = Vec::new(); let mut buffer = Vec::new();
match (document.selected_visible_layers_bounding_box(), data.bounding_box_overlays.take()) { match (document.selected_visible_layers_bounding_box(font_cache), tool_data.bounding_box_overlays.take()) {
(None, Some(bounding_box_overlays)) => bounding_box_overlays.delete(&mut buffer), (None, Some(bounding_box_overlays)) => bounding_box_overlays.delete(&mut buffer),
(Some(bounds), paths) => { (Some(bounds), paths) => {
let mut bounding_box_overlays = paths.unwrap_or_else(|| BoundingBoxOverlays::new(&mut buffer)); let mut bounding_box_overlays = paths.unwrap_or_else(|| BoundingBoxOverlays::new(&mut buffer));
@@ -334,13 +330,13 @@ impl Fsm for SelectToolFsmState {
bounding_box_overlays.transform(&mut buffer); bounding_box_overlays.transform(&mut buffer);
data.bounding_box_overlays = Some(bounding_box_overlays); tool_data.bounding_box_overlays = Some(bounding_box_overlays);
} }
(_, _) => {} (_, _) => {}
}; };
buffer.into_iter().rev().for_each(|message| responses.push_front(message)); buffer.into_iter().rev().for_each(|message| responses.push_front(message));
data.path_outlines.update_selected(document.selected_visible_layers(), document, responses); tool_data.path_outlines.update_selected(document.selected_visible_layers(), document, responses, font_cache);
self self
} }
@@ -349,7 +345,12 @@ impl Fsm for SelectToolFsmState {
let tolerance = DVec2::splat(SELECTION_TOLERANCE); let tolerance = DVec2::splat(SELECTION_TOLERANCE);
let quad = Quad::from_box([mouse_pos - tolerance, mouse_pos + tolerance]); let quad = Quad::from_box([mouse_pos - tolerance, mouse_pos + tolerance]);
if let Some(Ok(intersect)) = document.graphene_document.intersects_quad_root(quad).last().map(|path| document.graphene_document.layer(path)) { if let Some(Ok(intersect)) = document
.graphene_document
.intersects_quad_root(quad, font_cache)
.last()
.map(|path| document.graphene_document.layer(path))
{
match intersect.data { match intersect.data {
LayerDataType::Text(_) => { LayerDataType::Text(_) => {
responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Text }.into()); responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Text }.into());
@@ -365,13 +366,13 @@ impl Fsm for SelectToolFsmState {
self self
} }
(Ready, DragStart { add_to_selection }) => { (Ready, DragStart { add_to_selection }) => {
data.path_outlines.clear_hovered(responses); tool_data.path_outlines.clear_hovered(responses);
data.drag_start = input.mouse.position; tool_data.drag_start = input.mouse.position;
data.drag_current = input.mouse.position; tool_data.drag_current = input.mouse.position;
let mut buffer = Vec::new(); let mut buffer = Vec::new();
let dragging_bounds = if let Some(bounding_box) = &mut data.bounding_box_overlays { let dragging_bounds = if let Some(bounding_box) = &mut tool_data.bounding_box_overlays {
let edges = bounding_box.check_selected_edges(input.mouse.position); let edges = bounding_box.check_selected_edges(input.mouse.position);
bounding_box.selected_edges = edges.map(|(top, bottom, left, right)| { bounding_box.selected_edges = edges.map(|(top, bottom, left, right)| {
@@ -385,15 +386,15 @@ impl Fsm for SelectToolFsmState {
None None
}; };
let rotating_bounds = if let Some(bounding_box) = &mut data.bounding_box_overlays { let rotating_bounds = if let Some(bounding_box) = &mut tool_data.bounding_box_overlays {
bounding_box.check_rotate(input.mouse.position) bounding_box.check_rotate(input.mouse.position)
} else { } else {
false false
}; };
let mut selected: Vec<_> = document.selected_visible_layers().map(|path| path.to_vec()).collect(); let mut selected: Vec<_> = document.selected_visible_layers().map(|path| path.to_vec()).collect();
let quad = data.selection_quad(); let quad = tool_data.selection_quad();
let mut intersection = document.graphene_document.intersects_quad_root(quad); let mut intersection = document.graphene_document.intersects_quad_root(quad, font_cache);
// If the user is dragging the bounding box bounds, go into ResizingBounds mode. // If the user is dragging the bounding box bounds, go into ResizingBounds mode.
// If the user is dragging the rotate trigger, go into RotatingBounds mode. // If the user is dragging the rotate trigger, go into RotatingBounds mode.
// If the user clicks on a layer that is in their current selection, go into the dragging mode. // If the user clicks on a layer that is in their current selection, go into the dragging mode.
@@ -403,46 +404,52 @@ impl Fsm for SelectToolFsmState {
let snap_x = selected_edges.2 || selected_edges.3; let snap_x = selected_edges.2 || selected_edges.3;
let snap_y = selected_edges.0 || selected_edges.1; let snap_y = selected_edges.0 || selected_edges.1;
data.snap_handler.start_snap(document, document.bounding_boxes(Some(&selected), None), snap_x, snap_y); tool_data.snap_handler.start_snap(document, document.bounding_boxes(Some(&selected), None, font_cache), snap_x, snap_y);
data.snap_handler.add_all_document_handles(document, &[], &selected.iter().map(|x| x.as_slice()).collect::<Vec<_>>()); tool_data
.snap_handler
.add_all_document_handles(document, &[], &selected.iter().map(|x| x.as_slice()).collect::<Vec<_>>());
data.layers_dragging = selected; tool_data.layers_dragging = selected;
ResizingBounds ResizingBounds
} else if rotating_bounds { } else if rotating_bounds {
if let Some(bounds) = &mut data.bounding_box_overlays { if let Some(bounds) = &mut tool_data.bounding_box_overlays {
let selected = selected.iter().collect::<Vec<_>>(); let selected = selected.iter().collect::<Vec<_>>();
let mut selected = Selected::new(&mut bounds.original_transforms, &mut bounds.pivot, &selected, responses, &document.graphene_document); let mut selected = Selected::new(&mut bounds.original_transforms, &mut bounds.pivot, &selected, responses, &document.graphene_document);
*selected.pivot = selected.calculate_pivot(&document.graphene_document.font_cache); *selected.pivot = selected.calculate_pivot(font_cache);
} }
data.layers_dragging = selected; tool_data.layers_dragging = selected;
RotatingBounds RotatingBounds
} else if selected.iter().any(|path| intersection.contains(path)) { } else if selected.iter().any(|path| intersection.contains(path)) {
buffer.push(DocumentMessage::StartTransaction.into()); buffer.push(DocumentMessage::StartTransaction.into());
data.layers_dragging = selected; tool_data.layers_dragging = selected;
data.snap_handler.start_snap(document, document.bounding_boxes(Some(&data.layers_dragging), None), true, true); tool_data
.snap_handler
.start_snap(document, document.bounding_boxes(Some(&tool_data.layers_dragging), None, font_cache), true, true);
Dragging Dragging
} else { } else {
if !input.keyboard.get(add_to_selection as usize) { if !input.keyboard.get(add_to_selection as usize) {
buffer.push(DocumentMessage::DeselectAllLayers.into()); buffer.push(DocumentMessage::DeselectAllLayers.into());
data.layers_dragging.clear(); tool_data.layers_dragging.clear();
} }
if let Some(intersection) = intersection.pop() { if let Some(intersection) = intersection.pop() {
selected = vec![intersection]; selected = vec![intersection];
buffer.push(DocumentMessage::AddSelectedLayers { additional_layers: selected.clone() }.into()); buffer.push(DocumentMessage::AddSelectedLayers { additional_layers: selected.clone() }.into());
buffer.push(DocumentMessage::StartTransaction.into()); buffer.push(DocumentMessage::StartTransaction.into());
data.layers_dragging.append(&mut selected); tool_data.layers_dragging.append(&mut selected);
data.snap_handler.start_snap(document, document.bounding_boxes(Some(&data.layers_dragging), None), true, true); tool_data
.snap_handler
.start_snap(document, document.bounding_boxes(Some(&tool_data.layers_dragging), None, font_cache), true, true);
Dragging Dragging
} else { } else {
data.drag_box_overlay_layer = Some(add_bounding_box(&mut buffer)); tool_data.drag_box_overlay_layer = Some(add_bounding_box(&mut buffer));
DrawingBox DrawingBox
} }
}; };
@@ -454,20 +461,20 @@ impl Fsm for SelectToolFsmState {
// TODO: This is a cheat. Break out the relevant functionality from the handler above and call it from there and here. // TODO: This is a cheat. Break out the relevant functionality from the handler above and call it from there and here.
responses.push_front(SelectToolMessage::DocumentIsDirty.into()); responses.push_front(SelectToolMessage::DocumentIsDirty.into());
let mouse_position = axis_align_drag(input.keyboard.get(axis_align as usize), input.mouse.position, data.drag_start); let mouse_position = axis_align_drag(input.keyboard.get(axis_align as usize), input.mouse.position, tool_data.drag_start);
let mouse_delta = mouse_position - data.drag_current; let mouse_delta = mouse_position - tool_data.drag_current;
let snap = data let snap = tool_data
.layers_dragging .layers_dragging
.iter() .iter()
.filter_map(|path| document.graphene_document.viewport_bounding_box(path).ok()?) .filter_map(|path| document.graphene_document.viewport_bounding_box(path, font_cache).ok()?)
.flat_map(snapping::expand_bounds) .flat_map(snapping::expand_bounds)
.collect(); .collect();
let closest_move = data.snap_handler.snap_layers(responses, document, snap, mouse_delta); let closest_move = tool_data.snap_handler.snap_layers(responses, document, snap, mouse_delta);
// TODO: Cache the result of `shallowest_unique_layers` to avoid this heavy computation every frame of movement, see https://github.com/GraphiteEditor/Graphite/pull/481 // TODO: Cache the result of `shallowest_unique_layers` to avoid this heavy computation every frame of movement, see https://github.com/GraphiteEditor/Graphite/pull/481
for path in Document::shallowest_unique_layers(data.layers_dragging.iter()) { for path in Document::shallowest_unique_layers(tool_data.layers_dragging.iter()) {
responses.push_front( responses.push_front(
Operation::TransformLayerInViewport { Operation::TransformLayerInViewport {
path: path.clone(), path: path.clone(),
@@ -476,22 +483,22 @@ impl Fsm for SelectToolFsmState {
.into(), .into(),
); );
} }
data.drag_current = mouse_position + closest_move; tool_data.drag_current = mouse_position + closest_move;
Dragging Dragging
} }
(ResizingBounds, PointerMove { axis_align, center, .. }) => { (ResizingBounds, PointerMove { axis_align, center, .. }) => {
if let Some(bounds) = &mut data.bounding_box_overlays { if let Some(bounds) = &mut tool_data.bounding_box_overlays {
if let Some(movement) = &mut bounds.selected_edges { if let Some(movement) = &mut bounds.selected_edges {
let (center, axis_align) = (input.keyboard.get(center as usize), input.keyboard.get(axis_align as usize)); let (center, axis_align) = (input.keyboard.get(center as usize), input.keyboard.get(axis_align as usize));
let mouse_position = input.mouse.position; let mouse_position = input.mouse.position;
let snapped_mouse_position = data.snap_handler.snap_position(responses, document, mouse_position); let snapped_mouse_position = tool_data.snap_handler.snap_position(responses, document, mouse_position);
let [_position, size] = movement.new_size(snapped_mouse_position, bounds.transform, center, axis_align); let [_position, size] = movement.new_size(snapped_mouse_position, bounds.transform, center, axis_align);
let delta = movement.bounds_to_scale_transform(center, size); let delta = movement.bounds_to_scale_transform(center, size);
let selected = data.layers_dragging.iter().collect::<Vec<_>>(); let selected = tool_data.layers_dragging.iter().collect::<Vec<_>>();
let mut selected = Selected::new(&mut bounds.original_transforms, &mut bounds.pivot, &selected, responses, &document.graphene_document); let mut selected = Selected::new(&mut bounds.original_transforms, &mut bounds.pivot, &selected, responses, &document.graphene_document);
selected.update_transforms(delta); selected.update_transforms(delta);
@@ -500,9 +507,9 @@ impl Fsm for SelectToolFsmState {
ResizingBounds ResizingBounds
} }
(RotatingBounds, PointerMove { snap_angle, .. }) => { (RotatingBounds, PointerMove { snap_angle, .. }) => {
if let Some(bounds) = &mut data.bounding_box_overlays { if let Some(bounds) = &mut tool_data.bounding_box_overlays {
let angle = { let angle = {
let start_offset = data.drag_start - bounds.pivot; let start_offset = tool_data.drag_start - bounds.pivot;
let end_offset = input.mouse.position - bounds.pivot; let end_offset = input.mouse.position - bounds.pivot;
start_offset.angle_between(end_offset) start_offset.angle_between(end_offset)
@@ -517,7 +524,7 @@ impl Fsm for SelectToolFsmState {
let delta = DAffine2::from_angle(snapped_angle); let delta = DAffine2::from_angle(snapped_angle);
let selected = data.layers_dragging.iter().collect::<Vec<_>>(); let selected = tool_data.layers_dragging.iter().collect::<Vec<_>>();
let mut selected = Selected::new(&mut bounds.original_transforms, &mut bounds.pivot, &selected, responses, &document.graphene_document); let mut selected = Selected::new(&mut bounds.original_transforms, &mut bounds.pivot, &selected, responses, &document.graphene_document);
selected.update_transforms(delta); selected.update_transforms(delta);
@@ -526,13 +533,13 @@ impl Fsm for SelectToolFsmState {
RotatingBounds RotatingBounds
} }
(DrawingBox, PointerMove { .. }) => { (DrawingBox, PointerMove { .. }) => {
data.drag_current = input.mouse.position; tool_data.drag_current = input.mouse.position;
responses.push_front( responses.push_front(
DocumentMessage::Overlays( DocumentMessage::Overlays(
Operation::SetLayerTransformInViewport { Operation::SetLayerTransformInViewport {
path: data.drag_box_overlay_layer.clone().unwrap(), path: tool_data.drag_box_overlay_layer.clone().unwrap(),
transform: transform_from_box(data.drag_start, data.drag_current, DAffine2::IDENTITY).to_cols_array(), transform: transform_from_box(tool_data.drag_start, tool_data.drag_current, DAffine2::IDENTITY).to_cols_array(),
} }
.into(), .into(),
) )
@@ -541,73 +548,73 @@ impl Fsm for SelectToolFsmState {
DrawingBox DrawingBox
} }
(Ready, PointerMove { .. }) => { (Ready, PointerMove { .. }) => {
let cursor = data.bounding_box_overlays.as_ref().map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true)); let cursor = tool_data.bounding_box_overlays.as_ref().map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true));
// Generate the select outline (but not if the user is going to use the bound overlays) // Generate the select outline (but not if the user is going to use the bound overlays)
if cursor == MouseCursorIcon::Default { if cursor == MouseCursorIcon::Default {
// Get the layer the user is hovering over // Get the layer the user is hovering over
let tolerance = DVec2::splat(SELECTION_TOLERANCE); let tolerance = DVec2::splat(SELECTION_TOLERANCE);
let quad = Quad::from_box([input.mouse.position - tolerance, input.mouse.position + tolerance]); let quad = Quad::from_box([input.mouse.position - tolerance, input.mouse.position + tolerance]);
let mut intersection = document.graphene_document.intersects_quad_root(quad); let mut intersection = document.graphene_document.intersects_quad_root(quad, font_cache);
// If the user is hovering over a layer they have not already selected, then update outline // If the user is hovering over a layer they have not already selected, then update outline
if let Some(path) = intersection.pop() { if let Some(path) = intersection.pop() {
if !document.selected_visible_layers().any(|visible| visible == path.as_slice()) { if !document.selected_visible_layers().any(|visible| visible == path.as_slice()) {
data.path_outlines.update_hovered(path, document, responses) tool_data.path_outlines.update_hovered(path, document, responses, font_cache)
} else { } else {
data.path_outlines.clear_hovered(responses); tool_data.path_outlines.clear_hovered(responses);
} }
} else { } else {
data.path_outlines.clear_hovered(responses); tool_data.path_outlines.clear_hovered(responses);
} }
} else { } else {
data.path_outlines.clear_hovered(responses); tool_data.path_outlines.clear_hovered(responses);
} }
if data.cursor != cursor { if tool_data.cursor != cursor {
data.cursor = cursor; tool_data.cursor = cursor;
responses.push_back(FrontendMessage::UpdateMouseCursor { cursor }.into()); responses.push_back(FrontendMessage::UpdateMouseCursor { cursor }.into());
} }
Ready Ready
} }
(Dragging, DragStop) => { (Dragging, DragStop) => {
let response = match input.mouse.position.distance(data.drag_start) < 10. * f64::EPSILON { let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
true => DocumentMessage::Undo, true => DocumentMessage::Undo,
false => DocumentMessage::CommitTransaction, false => DocumentMessage::CommitTransaction,
}; };
data.snap_handler.cleanup(responses); tool_data.snap_handler.cleanup(responses);
responses.push_front(response.into()); responses.push_front(response.into());
Ready Ready
} }
(ResizingBounds, DragStop) => { (ResizingBounds, DragStop) => {
data.snap_handler.cleanup(responses); tool_data.snap_handler.cleanup(responses);
if let Some(bounds) = &mut data.bounding_box_overlays { if let Some(bounds) = &mut tool_data.bounding_box_overlays {
bounds.original_transforms.clear(); bounds.original_transforms.clear();
} }
Ready Ready
} }
(RotatingBounds, DragStop) => { (RotatingBounds, DragStop) => {
if let Some(bounds) = &mut data.bounding_box_overlays { if let Some(bounds) = &mut tool_data.bounding_box_overlays {
bounds.original_transforms.clear(); bounds.original_transforms.clear();
} }
Ready Ready
} }
(DrawingBox, DragStop) => { (DrawingBox, DragStop) => {
let quad = data.selection_quad(); let quad = tool_data.selection_quad();
responses.push_front( responses.push_front(
DocumentMessage::AddSelectedLayers { DocumentMessage::AddSelectedLayers {
additional_layers: document.graphene_document.intersects_quad_root(quad), additional_layers: document.graphene_document.intersects_quad_root(quad, font_cache),
} }
.into(), .into(),
); );
responses.push_front( responses.push_front(
DocumentMessage::Overlays( DocumentMessage::Overlays(
Operation::DeleteLayer { Operation::DeleteLayer {
path: data.drag_box_overlay_layer.take().unwrap(), path: tool_data.drag_box_overlay_layer.take().unwrap(),
} }
.into(), .into(),
) )
@@ -616,19 +623,19 @@ impl Fsm for SelectToolFsmState {
Ready Ready
} }
(Dragging, Abort) => { (Dragging, Abort) => {
data.snap_handler.cleanup(responses); tool_data.snap_handler.cleanup(responses);
responses.push_back(DocumentMessage::Undo.into()); responses.push_back(DocumentMessage::Undo.into());
data.path_outlines.clear_selected(responses); tool_data.path_outlines.clear_selected(responses);
Ready Ready
} }
(_, Abort) => { (_, Abort) => {
if let Some(path) = data.drag_box_overlay_layer.take() { if let Some(path) = tool_data.drag_box_overlay_layer.take() {
responses.push_front(DocumentMessage::Overlays(Operation::DeleteLayer { path }.into()).into()) responses.push_front(DocumentMessage::Overlays(Operation::DeleteLayer { path }.into()).into())
}; };
if let Some(mut bounding_box_overlays) = data.bounding_box_overlays.take() { if let Some(mut bounding_box_overlays) = tool_data.bounding_box_overlays.take() {
let selected = data.layers_dragging.iter().collect::<Vec<_>>(); let selected = tool_data.layers_dragging.iter().collect::<Vec<_>>();
let mut selected = Selected::new( let mut selected = Selected::new(
&mut bounding_box_overlays.original_transforms, &mut bounding_box_overlays.original_transforms,
&mut bounding_box_overlays.pivot, &mut bounding_box_overlays.pivot,
@@ -642,10 +649,10 @@ impl Fsm for SelectToolFsmState {
bounding_box_overlays.delete(responses); bounding_box_overlays.delete(responses);
} }
data.path_outlines.clear_hovered(responses); tool_data.path_outlines.clear_hovered(responses);
data.path_outlines.clear_selected(responses); tool_data.path_outlines.clear_selected(responses);
data.snap_handler.cleanup(responses); tool_data.snap_handler.cleanup(responses);
Ready Ready
} }
(_, Align { axis, aggregate }) => { (_, Align { axis, aggregate }) => {
+13 -17
View File
@@ -1,13 +1,11 @@
use super::shared::resize::Resize; use super::shared::resize::Resize;
use crate::consts::DRAG_THRESHOLD; use crate::consts::DRAG_THRESHOLD;
use crate::document::DocumentMessageHandler;
use crate::frontend::utility_types::MouseCursorIcon; use crate::frontend::utility_types::MouseCursorIcon;
use crate::input::keyboard::{Key, MouseMotion}; use crate::input::keyboard::{Key, MouseMotion};
use crate::input::InputPreprocessorMessageHandler;
use crate::layout::widgets::{LayoutRow, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; use crate::layout::widgets::{LayoutRow, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::message_prelude::*; use crate::message_prelude::*;
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; use crate::viewport_tools::tool::{Fsm, ToolActionHandlerData};
use graphene::layers::style; use graphene::layers::style;
use graphene::Operation; use graphene::Operation;
@@ -18,7 +16,7 @@ use serde::{Deserialize, Serialize};
#[derive(Default)] #[derive(Default)]
pub struct ShapeTool { pub struct ShapeTool {
fsm_state: ShapeToolFsmState, fsm_state: ShapeToolFsmState,
data: ShapeToolData, tool_data: ShapeToolData,
options: ShapeOptions, options: ShapeOptions,
} }
@@ -34,7 +32,7 @@ impl Default for ShapeOptions {
#[remain::sorted] #[remain::sorted]
#[impl_message(Message, ToolMessage, Shape)] #[impl_message(Message, ToolMessage, Shape)]
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)] #[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
pub enum ShapeToolMessage { pub enum ShapeToolMessage {
// Standard messages // Standard messages
#[remain::unsorted] #[remain::unsorted]
@@ -51,7 +49,7 @@ pub enum ShapeToolMessage {
} }
#[remain::sorted] #[remain::sorted]
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)] #[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
pub enum ShapeOptionsUpdate { pub enum ShapeOptionsUpdate {
Vertices(u32), Vertices(u32),
} }
@@ -73,7 +71,7 @@ impl PropertyHolder for ShapeTool {
} }
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for ShapeTool { impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for ShapeTool {
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) { fn process_action(&mut self, action: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
if action == ToolMessage::UpdateHints { if action == ToolMessage::UpdateHints {
self.fsm_state.update_hints(responses); self.fsm_state.update_hints(responses);
return; return;
@@ -91,7 +89,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for ShapeTool {
return; return;
} }
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, &self.options, data.2, responses); let new_state = self.fsm_state.transition(action, &mut self.tool_data, tool_data, &self.options, responses);
if self.fsm_state != new_state { if self.fsm_state != new_state {
self.fsm_state = new_state; self.fsm_state = new_state;
@@ -134,34 +132,32 @@ impl Fsm for ShapeToolFsmState {
fn transition( fn transition(
self, self,
event: ToolMessage, event: ToolMessage,
document: &DocumentMessageHandler, tool_data: &mut Self::ToolData,
tool_data: &DocumentToolData, (document, global_tool_data, input, font_cache): ToolActionHandlerData,
data: &mut Self::ToolData,
tool_options: &Self::ToolOptions, tool_options: &Self::ToolOptions,
input: &InputPreprocessorMessageHandler,
responses: &mut VecDeque<Message>, responses: &mut VecDeque<Message>,
) -> Self { ) -> Self {
use ShapeToolFsmState::*; use ShapeToolFsmState::*;
use ShapeToolMessage::*; use ShapeToolMessage::*;
let mut shape_data = &mut data.data; let mut shape_data = &mut tool_data.data;
if let ToolMessage::Shape(event) = event { if let ToolMessage::Shape(event) = event {
match (self, event) { match (self, event) {
(Ready, DragStart) => { (Ready, DragStart) => {
shape_data.start(responses, document, input.mouse.position); shape_data.start(responses, document, input.mouse.position, font_cache);
responses.push_back(DocumentMessage::StartTransaction.into()); responses.push_back(DocumentMessage::StartTransaction.into());
shape_data.path = Some(document.get_path_for_new_layer()); shape_data.path = Some(document.get_path_for_new_layer());
responses.push_back(DocumentMessage::DeselectAllLayers.into()); responses.push_back(DocumentMessage::DeselectAllLayers.into());
data.sides = tool_options.vertices; tool_data.sides = tool_options.vertices;
responses.push_back( responses.push_back(
Operation::AddNgon { Operation::AddNgon {
path: shape_data.path.clone().unwrap(), path: shape_data.path.clone().unwrap(),
insert_index: -1, insert_index: -1,
transform: DAffine2::ZERO.to_cols_array(), transform: DAffine2::ZERO.to_cols_array(),
sides: data.sides, sides: tool_data.sides,
style: style::PathStyle::new(None, style::Fill::solid(tool_data.primary_color)), style: style::PathStyle::new(None, style::Fill::solid(global_tool_data.primary_color)),
} }
.into(), .into(),
); );
@@ -4,6 +4,7 @@ use crate::message_prelude::*;
use graphene::layers::layer_info::LayerDataType; use graphene::layers::layer_info::LayerDataType;
use graphene::layers::style::{self, Fill, Stroke}; use graphene::layers::style::{self, Fill, Stroke};
use graphene::layers::text_layer::FontCache;
use graphene::{LayerId, Operation}; use graphene::{LayerId, Operation};
use glam::DAffine2; use glam::DAffine2;
@@ -20,16 +21,22 @@ pub struct PathOutline {
impl PathOutline { impl PathOutline {
/// Creates an outline of a layer either with a pre-existing overlay or by generating a new one /// Creates an outline of a layer either with a pre-existing overlay or by generating a new one
fn create_outline(document_layer_path: Vec<LayerId>, overlay_path: Option<Vec<LayerId>>, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) -> Option<Vec<LayerId>> { fn create_outline(
document_layer_path: Vec<LayerId>,
overlay_path: Option<Vec<LayerId>>,
document: &DocumentMessageHandler,
responses: &mut VecDeque<Message>,
font_cache: &FontCache,
) -> Option<Vec<LayerId>> {
// Get layer data // Get layer data
let document_layer = document.graphene_document.layer(&document_layer_path).ok()?; let document_layer = document.graphene_document.layer(&document_layer_path).ok()?;
// Get the bezpath from the shape or text // Get the bezpath from the shape or text
let path = match &document_layer.data { let path = match &document_layer.data {
LayerDataType::Shape(shape) => Some(shape.path.clone()), LayerDataType::Shape(shape) => Some(shape.path.clone()),
LayerDataType::Text(text) => Some(text.to_bez_path_nonmut(&document.graphene_document.font_cache)), LayerDataType::Text(text) => Some(text.to_bez_path_nonmut(font_cache)),
_ => document_layer _ => document_layer
.aabounding_box_for_transform(DAffine2::IDENTITY, &document.graphene_document.font_cache) .aabounding_box_for_transform(DAffine2::IDENTITY, font_cache)
.map(|bounds| kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y).to_path(0.)), .map(|bounds| kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y).to_path(0.)),
}?; }?;
@@ -78,10 +85,10 @@ impl PathOutline {
} }
/// Updates the overlay, generating a new one if necessary /// Updates the overlay, generating a new one if necessary
pub fn update_hovered(&mut self, new_layer_path: Vec<LayerId>, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) { pub fn update_hovered(&mut self, new_layer_path: Vec<LayerId>, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>, font_cache: &FontCache) {
// Check if we are hovering over a different layer than before // Check if we are hovering over a different layer than before
if self.hovered_layer_path.as_ref().map_or(true, |old| &new_layer_path != old) { if self.hovered_layer_path.as_ref().map_or(true, |old| &new_layer_path != old) {
self.hovered_overlay_path = Self::create_outline(new_layer_path.clone(), self.hovered_overlay_path.take(), document, responses); self.hovered_overlay_path = Self::create_outline(new_layer_path.clone(), self.hovered_overlay_path.take(), document, responses, font_cache);
if self.hovered_overlay_path.is_none() { if self.hovered_overlay_path.is_none() {
self.clear_hovered(responses); self.clear_hovered(responses);
} }
@@ -98,11 +105,11 @@ impl PathOutline {
} }
/// Updates the selected overlays, generating or removing overlays if necessary /// Updates the selected overlays, generating or removing overlays if necessary
pub fn update_selected<'a>(&mut self, selected: impl Iterator<Item = &'a [LayerId]>, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) { pub fn update_selected<'a>(&mut self, selected: impl Iterator<Item = &'a [LayerId]>, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>, font_cache: &FontCache) {
let mut old_overlay_paths = std::mem::take(&mut self.selected_overlay_paths); let mut old_overlay_paths = std::mem::take(&mut self.selected_overlay_paths);
for document_layer_path in selected { for document_layer_path in selected {
if let Some(overlay_path) = Self::create_outline(document_layer_path.to_vec(), old_overlay_paths.pop(), document, responses) { if let Some(overlay_path) = Self::create_outline(document_layer_path.to_vec(), old_overlay_paths.pop(), document, responses, font_cache) {
self.selected_overlay_paths.push(overlay_path); self.selected_overlay_paths.push(overlay_path);
} }
} }
@@ -5,6 +5,7 @@ use crate::input::InputPreprocessorMessageHandler;
use crate::message_prelude::*; use crate::message_prelude::*;
use crate::viewport_tools::snapping::SnapHandler; use crate::viewport_tools::snapping::SnapHandler;
use graphene::layers::text_layer::FontCache;
use graphene::Operation; use graphene::Operation;
use glam::{DAffine2, DVec2, Vec2Swizzles}; use glam::{DAffine2, DVec2, Vec2Swizzles};
@@ -18,8 +19,8 @@ pub struct Resize {
impl Resize { impl Resize {
/// Starts a resize, assigning the snap targets and snapping the starting position. /// Starts a resize, assigning the snap targets and snapping the starting position.
pub fn start(&mut self, responses: &mut VecDeque<Message>, document: &DocumentMessageHandler, mouse_position: DVec2) { pub fn start(&mut self, responses: &mut VecDeque<Message>, document: &DocumentMessageHandler, mouse_position: DVec2, font_cache: &FontCache) {
self.snap_handler.start_snap(document, document.bounding_boxes(None, None), true, true); self.snap_handler.start_snap(document, document.bounding_boxes(None, None, font_cache), true, true);
self.snap_handler.add_all_document_handles(document, &[], &[]); self.snap_handler.add_all_document_handles(document, &[], &[]);
self.drag_start = self.snap_handler.snap_position(responses, document, mouse_position); self.drag_start = self.snap_handler.snap_position(responses, document, mouse_position);
} }
+39 -40
View File
@@ -1,8 +1,6 @@
use crate::consts::DRAG_THRESHOLD; use crate::consts::DRAG_THRESHOLD;
use crate::document::DocumentMessageHandler;
use crate::frontend::utility_types::MouseCursorIcon; use crate::frontend::utility_types::MouseCursorIcon;
use crate::input::keyboard::{Key, MouseMotion}; use crate::input::keyboard::{Key, MouseMotion};
use crate::input::InputPreprocessorMessageHandler;
use crate::layout::widgets::{LayoutRow, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; use crate::layout::widgets::{LayoutRow, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::message_prelude::*; use crate::message_prelude::*;
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
@@ -18,7 +16,7 @@ use serde::{Deserialize, Serialize};
#[derive(Default)] #[derive(Default)]
pub struct SplineTool { pub struct SplineTool {
fsm_state: SplineToolFsmState, fsm_state: SplineToolFsmState,
data: SplineToolData, tool_data: SplineToolData,
options: SplineOptions, options: SplineOptions,
} }
@@ -78,7 +76,7 @@ impl PropertyHolder for SplineTool {
} }
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for SplineTool { impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for SplineTool {
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) { fn process_action(&mut self, action: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
if action == ToolMessage::UpdateHints { if action == ToolMessage::UpdateHints {
self.fsm_state.update_hints(responses); self.fsm_state.update_hints(responses);
return; return;
@@ -96,7 +94,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for SplineTool {
return; return;
} }
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, &self.options, data.2, responses); let new_state = self.fsm_state.transition(action, &mut self.tool_data, tool_data, &self.options, responses);
if self.fsm_state != new_state { if self.fsm_state != new_state {
self.fsm_state = new_state; self.fsm_state = new_state;
@@ -136,11 +134,9 @@ impl Fsm for SplineToolFsmState {
fn transition( fn transition(
self, self,
event: ToolMessage, event: ToolMessage,
document: &DocumentMessageHandler, tool_data: &mut Self::ToolData,
tool_data: &DocumentToolData, (document, global_tool_data, input, font_cache): ToolActionHandlerData,
data: &mut Self::ToolData,
tool_options: &Self::ToolOptions, tool_options: &Self::ToolOptions,
input: &InputPreprocessorMessageHandler,
responses: &mut VecDeque<Message>, responses: &mut VecDeque<Message>,
) -> Self { ) -> Self {
use SplineToolFsmState::*; use SplineToolFsmState::*;
@@ -153,62 +149,62 @@ impl Fsm for SplineToolFsmState {
(Ready, DragStart) => { (Ready, DragStart) => {
responses.push_back(DocumentMessage::StartTransaction.into()); responses.push_back(DocumentMessage::StartTransaction.into());
responses.push_back(DocumentMessage::DeselectAllLayers.into()); responses.push_back(DocumentMessage::DeselectAllLayers.into());
data.path = Some(document.get_path_for_new_layer()); tool_data.path = Some(document.get_path_for_new_layer());
data.snap_handler.start_snap(document, document.bounding_boxes(None, None), true, true); tool_data.snap_handler.start_snap(document, document.bounding_boxes(None, None, font_cache), true, true);
data.snap_handler.add_all_document_handles(document, &[], &[]); tool_data.snap_handler.add_all_document_handles(document, &[], &[]);
let snapped_position = data.snap_handler.snap_position(responses, document, input.mouse.position); let snapped_position = tool_data.snap_handler.snap_position(responses, document, input.mouse.position);
let pos = transform.inverse().transform_point2(snapped_position); let pos = transform.inverse().transform_point2(snapped_position);
data.points.push(pos); tool_data.points.push(pos);
data.next_point = pos; tool_data.next_point = pos;
data.weight = tool_options.line_weight; tool_data.weight = tool_options.line_weight;
responses.push_back(add_spline(data, tool_data, true)); responses.push_back(add_spline(tool_data, global_tool_data, true));
Drawing Drawing
} }
(Drawing, DragStop) => { (Drawing, DragStop) => {
let snapped_position = data.snap_handler.snap_position(responses, document, input.mouse.position); let snapped_position = tool_data.snap_handler.snap_position(responses, document, input.mouse.position);
let pos = transform.inverse().transform_point2(snapped_position); let pos = transform.inverse().transform_point2(snapped_position);
if let Some(last_pos) = data.points.last() { if let Some(last_pos) = tool_data.points.last() {
if last_pos.distance(pos) > DRAG_THRESHOLD { if last_pos.distance(pos) > DRAG_THRESHOLD {
data.points.push(pos); tool_data.points.push(pos);
data.next_point = pos; tool_data.next_point = pos;
} }
} }
responses.push_back(remove_preview(data)); responses.push_back(remove_preview(tool_data));
responses.push_back(add_spline(data, tool_data, true)); responses.push_back(add_spline(tool_data, global_tool_data, true));
Drawing Drawing
} }
(Drawing, PointerMove) => { (Drawing, PointerMove) => {
let snapped_position = data.snap_handler.snap_position(responses, document, input.mouse.position); let snapped_position = tool_data.snap_handler.snap_position(responses, document, input.mouse.position);
let pos = transform.inverse().transform_point2(snapped_position); let pos = transform.inverse().transform_point2(snapped_position);
data.next_point = pos; tool_data.next_point = pos;
responses.push_back(remove_preview(data)); responses.push_back(remove_preview(tool_data));
responses.push_back(add_spline(data, tool_data, true)); responses.push_back(add_spline(tool_data, global_tool_data, true));
Drawing Drawing
} }
(Drawing, Confirm) | (Drawing, Abort) => { (Drawing, Confirm) | (Drawing, Abort) => {
if data.points.len() >= 2 { if tool_data.points.len() >= 2 {
responses.push_back(DocumentMessage::DeselectAllLayers.into()); responses.push_back(DocumentMessage::DeselectAllLayers.into());
responses.push_back(remove_preview(data)); responses.push_back(remove_preview(tool_data));
responses.push_back(add_spline(data, tool_data, false)); responses.push_back(add_spline(tool_data, global_tool_data, false));
responses.push_back(DocumentMessage::CommitTransaction.into()); responses.push_back(DocumentMessage::CommitTransaction.into());
} else { } else {
responses.push_back(DocumentMessage::AbortTransaction.into()); responses.push_back(DocumentMessage::AbortTransaction.into());
} }
data.path = None; tool_data.path = None;
data.points.clear(); tool_data.points.clear();
data.snap_handler.cleanup(responses); tool_data.snap_handler.cleanup(responses);
Ready Ready
} }
@@ -251,22 +247,25 @@ impl Fsm for SplineToolFsmState {
} }
} }
fn remove_preview(data: &SplineToolData) -> Message { fn remove_preview(tool_data: &SplineToolData) -> Message {
Operation::DeleteLayer { path: data.path.clone().unwrap() }.into() Operation::DeleteLayer {
path: tool_data.path.clone().unwrap(),
}
.into()
} }
fn add_spline(data: &SplineToolData, tool_data: &DocumentToolData, show_preview: bool) -> Message { fn add_spline(tool_data: &SplineToolData, global_tool_data: &DocumentToolData, show_preview: bool) -> Message {
let mut points: Vec<(f64, f64)> = data.points.iter().map(|p| (p.x, p.y)).collect(); let mut points: Vec<(f64, f64)> = tool_data.points.iter().map(|p| (p.x, p.y)).collect();
if show_preview { if show_preview {
points.push((data.next_point.x, data.next_point.y)) points.push((tool_data.next_point.x, tool_data.next_point.y))
} }
Operation::AddSpline { Operation::AddSpline {
path: data.path.clone().unwrap(), path: tool_data.path.clone().unwrap(),
insert_index: -1, insert_index: -1,
transform: DAffine2::IDENTITY.to_cols_array(), transform: DAffine2::IDENTITY.to_cols_array(),
points, points,
style: style::PathStyle::new(Some(style::Stroke::new(tool_data.primary_color, data.weight)), style::Fill::None), style: style::PathStyle::new(Some(style::Stroke::new(global_tool_data.primary_color, tool_data.weight)), style::Fill::None),
} }
.into() .into()
} }
+53 -52
View File
@@ -2,25 +2,25 @@ use crate::consts::{COLOR_ACCENT, SELECTION_TOLERANCE};
use crate::document::DocumentMessageHandler; use crate::document::DocumentMessageHandler;
use crate::frontend::utility_types::MouseCursorIcon; use crate::frontend::utility_types::MouseCursorIcon;
use crate::input::keyboard::{Key, MouseMotion}; use crate::input::keyboard::{Key, MouseMotion};
use crate::input::InputPreprocessorMessageHandler;
use crate::layout::layout_message::LayoutTarget; use crate::layout::layout_message::LayoutTarget;
use crate::layout::widgets::{FontInput, LayoutRow, NumberInput, PropertyHolder, Separator, SeparatorDirection, SeparatorType, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; use crate::layout::widgets::{FontInput, LayoutRow, NumberInput, PropertyHolder, Separator, SeparatorDirection, SeparatorType, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::message_prelude::*; use crate::message_prelude::*;
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; use crate::viewport_tools::tool::{Fsm, ToolActionHandlerData};
use glam::{DAffine2, DVec2};
use graphene::document::FontCache;
use graphene::intersection::Quad; use graphene::intersection::Quad;
use graphene::layers::style::{self, Fill, Stroke}; use graphene::layers::style::{self, Fill, Stroke};
use graphene::layers::text_layer::FontCache;
use graphene::Operation; use graphene::Operation;
use glam::{DAffine2, DVec2};
use kurbo::Shape; use kurbo::Shape;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Default)] #[derive(Default)]
pub struct TextTool { pub struct TextTool {
fsm_state: TextToolFsmState, fsm_state: TextToolFsmState,
data: TextToolData, tool_data: TextToolData,
options: TextOptions, options: TextOptions,
} }
@@ -28,7 +28,6 @@ pub struct TextOptions {
font_size: u32, font_size: u32,
font_name: String, font_name: String,
font_style: String, font_style: String,
font_file: Option<String>,
} }
impl Default for TextOptions { impl Default for TextOptions {
@@ -37,14 +36,13 @@ impl Default for TextOptions {
font_size: 24, font_size: 24,
font_name: "Merriweather".into(), font_name: "Merriweather".into(),
font_style: "Normal (400)".into(), font_style: "Normal (400)".into(),
font_file: None,
} }
} }
} }
#[remain::sorted] #[remain::sorted]
#[impl_message(Message, ToolMessage, Text)] #[impl_message(Message, ToolMessage, Text)]
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)] #[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
pub enum TextMessage { pub enum TextMessage {
// Standard messages // Standard messages
#[remain::unsorted] #[remain::unsorted]
@@ -66,9 +64,9 @@ pub enum TextMessage {
} }
#[remain::sorted] #[remain::sorted]
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)] #[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
pub enum TextOptionsUpdate { pub enum TextOptionsUpdate {
Font { family: String, style: String, file: String }, Font { family: String, style: String },
FontSize(u32), FontSize(u32),
} }
@@ -84,11 +82,9 @@ impl PropertyHolder for TextTool {
TextMessage::UpdateOptions(TextOptionsUpdate::Font { TextMessage::UpdateOptions(TextOptionsUpdate::Font {
family: font_input.font_family.clone(), family: font_input.font_family.clone(),
style: font_input.font_style.clone(), style: font_input.font_style.clone(),
file: font_input.font_file_url.clone(),
}) })
.into() .into()
}), }),
..Default::default()
})), })),
WidgetHolder::new(Widget::Separator(Separator { WidgetHolder::new(Widget::Separator(Separator {
direction: SeparatorDirection::Horizontal, direction: SeparatorDirection::Horizontal,
@@ -102,11 +98,9 @@ impl PropertyHolder for TextTool {
TextMessage::UpdateOptions(TextOptionsUpdate::Font { TextMessage::UpdateOptions(TextOptionsUpdate::Font {
family: font_input.font_family.clone(), family: font_input.font_family.clone(),
style: font_input.font_style.clone(), style: font_input.font_style.clone(),
file: font_input.font_file_url.clone(),
}) })
.into() .into()
}), }),
..Default::default()
})), })),
WidgetHolder::new(Widget::Separator(Separator { WidgetHolder::new(Widget::Separator(Separator {
direction: SeparatorDirection::Horizontal, direction: SeparatorDirection::Horizontal,
@@ -127,7 +121,7 @@ impl PropertyHolder for TextTool {
} }
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for TextTool { impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for TextTool {
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) { fn process_action(&mut self, action: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
if action == ToolMessage::UpdateHints { if action == ToolMessage::UpdateHints {
self.fsm_state.update_hints(responses); self.fsm_state.update_hints(responses);
return; return;
@@ -140,10 +134,9 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for TextTool {
if let ToolMessage::Text(TextMessage::UpdateOptions(action)) = action { if let ToolMessage::Text(TextMessage::UpdateOptions(action)) = action {
match action { match action {
TextOptionsUpdate::Font { family, style, file } => { TextOptionsUpdate::Font { family, style } => {
self.options.font_name = family; self.options.font_name = family;
self.options.font_style = style; self.options.font_style = style;
self.options.font_file = Some(file);
self.register_properties(responses, LayoutTarget::ToolOptions); self.register_properties(responses, LayoutTarget::ToolOptions);
} }
@@ -152,7 +145,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for TextTool {
return; return;
} }
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, &self.options, data.2, responses); let new_state = self.fsm_state.transition(action, &mut self.tool_data, tool_data, &self.options, responses);
if self.fsm_state != new_state { if self.fsm_state != new_state {
self.fsm_state = new_state; self.fsm_state = new_state;
@@ -211,13 +204,13 @@ fn resize_overlays(overlays: &mut Vec<Vec<LayerId>>, responses: &mut VecDeque<Me
} }
} }
fn update_overlays(document: &DocumentMessageHandler, data: &mut TextToolData, responses: &mut VecDeque<Message>, font_cache: &FontCache) { fn update_overlays(document: &DocumentMessageHandler, tool_data: &mut TextToolData, responses: &mut VecDeque<Message>, font_cache: &FontCache) {
let visible_text_layers = document.selected_visible_text_layers().collect::<Vec<_>>(); let visible_text_layers = document.selected_visible_text_layers().collect::<Vec<_>>();
resize_overlays(&mut data.overlays, responses, visible_text_layers.len()); resize_overlays(&mut tool_data.overlays, responses, visible_text_layers.len());
let bounds = visible_text_layers let bounds = visible_text_layers
.into_iter() .into_iter()
.zip(&data.overlays) .zip(&tool_data.overlays)
.filter_map(|(layer_path, overlay_path)| { .filter_map(|(layer_path, overlay_path)| {
document document
.graphene_document .graphene_document
@@ -237,7 +230,7 @@ fn update_overlays(document: &DocumentMessageHandler, data: &mut TextToolData, r
}; };
responses.push_back(DocumentMessage::Overlays(operation.into()).into()); responses.push_back(DocumentMessage::Overlays(operation.into()).into());
} }
resize_overlays(&mut data.overlays, responses, new_len); resize_overlays(&mut tool_data.overlays, responses, new_len);
} }
impl Fsm for TextToolFsmState { impl Fsm for TextToolFsmState {
@@ -247,11 +240,9 @@ impl Fsm for TextToolFsmState {
fn transition( fn transition(
self, self,
event: ToolMessage, event: ToolMessage,
document: &DocumentMessageHandler, tool_data: &mut Self::ToolData,
tool_data: &DocumentToolData, (document, global_tool_data, input, font_cache): ToolActionHandlerData,
data: &mut Self::ToolData,
tool_options: &Self::ToolOptions, tool_options: &Self::ToolOptions,
input: &InputPreprocessorMessageHandler,
responses: &mut VecDeque<Message>, responses: &mut VecDeque<Message>,
) -> Self { ) -> Self {
use TextMessage::*; use TextMessage::*;
@@ -260,7 +251,7 @@ impl Fsm for TextToolFsmState {
if let ToolMessage::Text(event) = event { if let ToolMessage::Text(event) = event {
match (self, event) { match (self, event) {
(state, DocumentIsDirty) => { (state, DocumentIsDirty) => {
update_overlays(document, data, responses, &document.graphene_document.font_cache); update_overlays(document, tool_data, responses, font_cache);
state state
} }
@@ -271,7 +262,7 @@ impl Fsm for TextToolFsmState {
let new_state = if let Some(l) = document let new_state = if let Some(l) = document
.graphene_document .graphene_document
.intersects_quad_root(quad) .intersects_quad_root(quad, font_cache)
.last() .last()
.filter(|l| document.graphene_document.layer(l).map(|l| l.as_text().is_ok()).unwrap_or(false)) .filter(|l| document.graphene_document.layer(l).map(|l| l.as_text().is_ok()).unwrap_or(false))
// Editing existing text // Editing existing text
@@ -279,25 +270,25 @@ impl Fsm for TextToolFsmState {
if state == TextToolFsmState::Editing { if state == TextToolFsmState::Editing {
responses.push_back( responses.push_back(
DocumentMessage::SetTexboxEditability { DocumentMessage::SetTexboxEditability {
path: data.path.clone(), path: tool_data.path.clone(),
editable: false, editable: false,
} }
.into(), .into(),
); );
} }
data.path = l.clone(); tool_data.path = l.clone();
responses.push_back( responses.push_back(
DocumentMessage::SetTexboxEditability { DocumentMessage::SetTexboxEditability {
path: data.path.clone(), path: tool_data.path.clone(),
editable: true, editable: true,
} }
.into(), .into(),
); );
responses.push_back( responses.push_back(
DocumentMessage::SetSelectedLayers { DocumentMessage::SetSelectedLayers {
replacement_selected_layers: vec![data.path.clone()], replacement_selected_layers: vec![tool_data.path.clone()],
} }
.into(), .into(),
); );
@@ -310,28 +301,32 @@ impl Fsm for TextToolFsmState {
let font_size = tool_options.font_size; let font_size = tool_options.font_size;
let font_name = tool_options.font_name.clone(); let font_name = tool_options.font_name.clone();
let font_style = tool_options.font_style.clone(); let font_style = tool_options.font_style.clone();
let font_file = tool_options.font_file.clone(); tool_data.path = document.get_path_for_new_layer();
data.path = document.get_path_for_new_layer();
responses.push_back( responses.push_back(
Operation::AddText { Operation::AddText {
path: data.path.clone(), path: tool_data.path.clone(),
transform: DAffine2::ZERO.to_cols_array(), transform: DAffine2::ZERO.to_cols_array(),
insert_index: -1, insert_index: -1,
text: r#""#.to_string(), text: r#""#.to_string(),
style: style::PathStyle::new(None, Fill::solid(tool_data.primary_color)), style: style::PathStyle::new(None, Fill::solid(global_tool_data.primary_color)),
size: font_size as f64, size: font_size as f64,
font_name, font_name,
font_style, font_style,
font_file,
} }
.into(), .into(),
); );
responses.push_back(Operation::SetLayerTransformInViewport { path: data.path.clone(), transform }.into()); responses.push_back(
Operation::SetLayerTransformInViewport {
path: tool_data.path.clone(),
transform,
}
.into(),
);
responses.push_back( responses.push_back(
DocumentMessage::SetTexboxEditability { DocumentMessage::SetTexboxEditability {
path: data.path.clone(), path: tool_data.path.clone(),
editable: true, editable: true,
} }
.into(), .into(),
@@ -339,7 +334,7 @@ impl Fsm for TextToolFsmState {
responses.push_back( responses.push_back(
DocumentMessage::SetSelectedLayers { DocumentMessage::SetSelectedLayers {
replacement_selected_layers: vec![data.path.clone()], replacement_selected_layers: vec![tool_data.path.clone()],
} }
.into(), .into(),
); );
@@ -349,13 +344,13 @@ impl Fsm for TextToolFsmState {
// Removing old text as editable // Removing old text as editable
responses.push_back( responses.push_back(
DocumentMessage::SetTexboxEditability { DocumentMessage::SetTexboxEditability {
path: data.path.clone(), path: tool_data.path.clone(),
editable: false, editable: false,
} }
.into(), .into(),
); );
resize_overlays(&mut data.overlays, responses, 0); resize_overlays(&mut tool_data.overlays, responses, 0);
Ready Ready
}; };
@@ -366,14 +361,14 @@ impl Fsm for TextToolFsmState {
if state == TextToolFsmState::Editing { if state == TextToolFsmState::Editing {
responses.push_back( responses.push_back(
DocumentMessage::SetTexboxEditability { DocumentMessage::SetTexboxEditability {
path: data.path.clone(), path: tool_data.path.clone(),
editable: false, editable: false,
} }
.into(), .into(),
); );
} }
resize_overlays(&mut data.overlays, responses, 0); resize_overlays(&mut tool_data.overlays, responses, 0);
Ready Ready
} }
@@ -383,35 +378,41 @@ impl Fsm for TextToolFsmState {
Editing Editing
} }
(Editing, TextChange { new_text }) => { (Editing, TextChange { new_text }) => {
responses.push_back(Operation::SetTextContent { path: data.path.clone(), new_text }.into()); responses.push_back(
Operation::SetTextContent {
path: tool_data.path.clone(),
new_text,
}
.into(),
);
responses.push_back( responses.push_back(
DocumentMessage::SetTexboxEditability { DocumentMessage::SetTexboxEditability {
path: data.path.clone(), path: tool_data.path.clone(),
editable: false, editable: false,
} }
.into(), .into(),
); );
resize_overlays(&mut data.overlays, responses, 0); resize_overlays(&mut tool_data.overlays, responses, 0);
Ready Ready
} }
(Editing, UpdateBounds { new_text }) => { (Editing, UpdateBounds { new_text }) => {
resize_overlays(&mut data.overlays, responses, 1); resize_overlays(&mut tool_data.overlays, responses, 1);
let text = document.graphene_document.layer(&data.path).unwrap().as_text().unwrap(); let text = document.graphene_document.layer(&tool_data.path).unwrap().as_text().unwrap();
let mut path = text.bounding_box(&new_text, text.load_face(&document.graphene_document.font_cache)).to_path(0.1); let mut path = text.bounding_box(&new_text, text.load_face(font_cache)).to_path(0.1);
fn glam_to_kurbo(transform: DAffine2) -> kurbo::Affine { fn glam_to_kurbo(transform: DAffine2) -> kurbo::Affine {
kurbo::Affine::new(transform.to_cols_array()) kurbo::Affine::new(transform.to_cols_array())
} }
path.apply_affine(glam_to_kurbo(document.graphene_document.multiply_transforms(&data.path).unwrap())); path.apply_affine(glam_to_kurbo(document.graphene_document.multiply_transforms(&tool_data.path).unwrap()));
let kurbo::Rect { x0, y0, x1, y1 } = path.bounding_box(); let kurbo::Rect { x0, y0, x1, y1 } = path.bounding_box();
let operation = Operation::SetLayerTransformInViewport { let operation = Operation::SetLayerTransformInViewport {
path: data.overlays[0].clone(), path: tool_data.overlays[0].clone(),
transform: transform_from_box(DVec2::new(x0, y0), DVec2::new(x1, y1)), transform: transform_from_box(DVec2::new(x0, y0), DVec2::new(x1, y1)),
}; };
responses.push_back(DocumentMessage::Overlays(operation.into()).into()); responses.push_back(DocumentMessage::Overlays(operation.into()).into());
@@ -6,7 +6,7 @@ pub const ROUNDING_BIAS: f64 = 0.0001;
pub const MINIMUM_MIRROR_THRESHOLD: f64 = 0.1; pub const MINIMUM_MIRROR_THRESHOLD: f64 = 0.1;
#[repr(usize)] #[repr(usize)]
#[derive(PartialEq, Clone, Debug)] #[derive(PartialEq, Eq, Clone, Debug)]
pub enum ControlPointType { pub enum ControlPointType {
Anchor = 0, Anchor = 0,
Handle1 = 1, Handle1 = 1,
+1 -1
View File
@@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
#[remain::sorted] #[remain::sorted]
#[impl_message(Message, Workspace)] #[impl_message(Message, Workspace)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
pub enum WorkspaceMessage { pub enum WorkspaceMessage {
// Messages // Messages
NodeGraphToggleVisibility, NodeGraphToggleVisibility,
@@ -1,12 +1,12 @@
<template> <template>
<LayoutRow class="dropdown-input"> <LayoutRow class="dropdown-input" data-dropdown-input>
<LayoutRow <LayoutRow
class="dropdown-box" class="dropdown-box"
:class="{ disabled }" :class="{ disabled, open }"
:style="{ minWidth: `${minWidth}px` }" :style="{ minWidth: `${minWidth}px` }"
tabindex="0" tabindex="0"
@click="() => !disabled && (open = true)" @click="() => !disabled && (open = true)"
@blur="() => (open = false)" @blur="(e: FocusEvent) => blur(e)"
@keydown="(e) => keydown(e)" @keydown="(e) => keydown(e)"
ref="dropdownBox" ref="dropdownBox"
data-hover-menu-spawner data-hover-menu-spawner
@@ -145,6 +145,9 @@ export default defineComponent({
keydown(e: KeyboardEvent) { keydown(e: KeyboardEvent) {
(this.$refs.menuList as typeof MenuList).keydown(e, false); (this.$refs.menuList as typeof MenuList).keydown(e, false);
}, },
blur(e: FocusEvent) {
if ((e.target as HTMLElement).closest("[data-dropdown-input]") !== this.$el) this.open = false;
},
}, },
components: { components: {
IconLabel, IconLabel,
@@ -1,5 +1,5 @@
<template> <template>
<div class="menu-bar-input"> <div class="menu-bar-input" data-menu-bar-input>
<div class="entry-container"> <div class="entry-container">
<button @click="() => visitWebsite('https://graphite.rs')" class="entry"> <button @click="() => visitWebsite('https://graphite.rs')" class="entry">
<IconLabel :icon="'GraphiteLogo'" /> <IconLabel :icon="'GraphiteLogo'" />
@@ -8,11 +8,11 @@
<div class="entry-container" v-for="(entry, index) in entries" :key="index"> <div class="entry-container" v-for="(entry, index) in entries" :key="index">
<div <div
@click="(e) => onClick(entry, e.target)" @click="(e) => onClick(entry, e.target)"
@blur="() => close(entry)"
tabindex="0" tabindex="0"
@blur="(e: FocusEvent) => blur(e,entry)"
@keydown="entry.ref?.keydown" @keydown="entry.ref?.keydown"
class="entry" class="entry"
:class="{ open: entry.ref?.open }" :class="{ open: entry.ref?.isOpen }"
data-hover-menu-spawner data-hover-menu-spawner
> >
<IconLabel v-if="entry.icon" :icon="entry.icon" /> <IconLabel v-if="entry.icon" :icon="entry.icon" />
@@ -225,8 +225,8 @@ export default defineComponent({
if (menuEntry.ref) menuEntry.ref.isOpen = true; if (menuEntry.ref) menuEntry.ref.isOpen = true;
else throw new Error("The menu bar floating menu has no associated ref"); else throw new Error("The menu bar floating menu has no associated ref");
}, },
close(menuEntry: MenuListEntry) { blur(e: FocusEvent, menuEntry: MenuListEntry) {
if (menuEntry.ref) menuEntry.ref.isOpen = false; if ((e.target as HTMLElement).closest("[data-menu-bar-input]") !== this.$el && menuEntry.ref) menuEntry.ref.isOpen = false;
}, },
// TODO: Move to backend // TODO: Move to backend
visitWebsite(url: string) { visitWebsite(url: string) {
+8 -14
View File
@@ -1,10 +1,7 @@
import { reactive, readonly } from "vue"; import { reactive, readonly } from "vue";
import { Editor } from "@/wasm-communication/editor"; import { Editor } from "@/wasm-communication/editor";
import { TriggerFontLoad, TriggerFontLoadDefault } from "@/wasm-communication/messages"; import { TriggerFontLoad } from "@/wasm-communication/messages";
const DEFAULT_FONT = "Merriweather";
const DEFAULT_FONT_STYLE = "Normal (400)";
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function createFontsState(editor: Editor) { export function createFontsState(editor: Editor) {
@@ -40,17 +37,14 @@ export function createFontsState(editor: Editor) {
} }
// Subscribe to process backend events // Subscribe to process backend events
editor.subscriptions.subscribeJsMessage(TriggerFontLoadDefault, async (): Promise<void> => {
const fontFileUrl = await getFontFileUrl(DEFAULT_FONT, DEFAULT_FONT_STYLE);
if (!fontFileUrl) return;
const response = await fetch(fontFileUrl);
const responseBuffer = await response.arrayBuffer();
editor.instance.on_font_load(fontFileUrl, new Uint8Array(responseBuffer), true);
});
editor.subscriptions.subscribeJsMessage(TriggerFontLoad, async (triggerFontLoad) => { editor.subscriptions.subscribeJsMessage(TriggerFontLoad, async (triggerFontLoad) => {
const response = await (await fetch(triggerFontLoad.font_file_url)).arrayBuffer(); const url = await getFontFileUrl(triggerFontLoad.font.font_family, triggerFontLoad.font.font_style);
editor.instance.on_font_load(triggerFontLoad.font_file_url, new Uint8Array(response), false); if (url) {
const response = await (await fetch(url)).arrayBuffer();
editor.instance.on_font_load(triggerFontLoad.font.font_family, triggerFontLoad.font.font_style, url, new Uint8Array(response), triggerFontLoad.is_default);
} else {
editor.instance.error_dialog("Failed to load font", `The font ${triggerFontLoad.font.font_family} with style ${triggerFontLoad.font.font_style} does not exist`);
}
}); });
const fontList: Promise<{ family: string; variants: string[]; files: Map<string, string> }[]> = new Promise((resolve) => { const fontList: Promise<{ family: string; variants: string[]; files: Map<string, string> }[]> = new Promise((resolve) => {
+10 -4
View File
@@ -352,11 +352,18 @@ export class TriggerIndexedDbRemoveDocument extends JsMessage {
document_id!: string; document_id!: string;
} }
export class TriggerFontLoad extends JsMessage { export class Font {
font_file_url!: string; font_family!: string;
font_style!: string;
} }
export class TriggerFontLoadDefault extends JsMessage {} export class TriggerFontLoad extends JsMessage {
@Type(() => Font)
font!: Font;
is_default!: boolean;
}
export class TriggerVisitLink extends JsMessage { export class TriggerVisitLink extends JsMessage {
url!: string; url!: string;
@@ -545,7 +552,6 @@ export const messageMakers: Record<string, MessageMaker> = {
TriggerFileUpload, TriggerFileUpload,
TriggerIndexedDbRemoveDocument, TriggerIndexedDbRemoveDocument,
TriggerFontLoad, TriggerFontLoad,
TriggerFontLoadDefault,
TriggerIndexedDbWriteDocument, TriggerIndexedDbWriteDocument,
TriggerRasterDownload, TriggerRasterDownload,
TriggerTextCommit, TriggerTextCommit,
+18 -4
View File
@@ -5,7 +5,7 @@
use crate::helpers::{translate_key, Error}; use crate::helpers::{translate_key, Error};
use crate::{EDITOR_HAS_CRASHED, EDITOR_INSTANCES, JS_EDITOR_HANDLES}; use crate::{EDITOR_HAS_CRASHED, EDITOR_INSTANCES, JS_EDITOR_HANDLES};
use editor::consts::{FILE_SAVE_SUFFIX, GRAPHITE_DOCUMENT_VERSION}; use editor::consts::{DEFAULT_FONT_FAMILY, DEFAULT_FONT_STYLE, FILE_SAVE_SUFFIX, GRAPHITE_DOCUMENT_VERSION};
use editor::input::input_preprocessor::ModifierKeys; use editor::input::input_preprocessor::ModifierKeys;
use editor::input::mouse::{EditorMouseState, ScrollDelta, ViewportBounds}; use editor::input::mouse::{EditorMouseState, ScrollDelta, ViewportBounds};
use editor::message_prelude::*; use editor::message_prelude::*;
@@ -110,13 +110,21 @@ impl JsEditorHandle {
let message = ToolMessage::InitTools; let message = ToolMessage::InitTools;
self.dispatch(message); self.dispatch(message);
let message = FrontendMessage::TriggerFontLoadDefault; // A default font
let font = graphene::layers::text_layer::Font::new(DEFAULT_FONT_FAMILY.into(), DEFAULT_FONT_STYLE.into());
let message = FrontendMessage::TriggerFontLoad { font, is_default: true };
self.dispatch(message); self.dispatch(message);
let message = MovementMessage::TranslateCanvas { delta: (0., 0.).into() }; let message = MovementMessage::TranslateCanvas { delta: (0., 0.).into() };
self.dispatch(message); self.dispatch(message);
} }
/// Displays a dialog with an error message
pub fn error_dialog(&self, title: String, description: String) {
let message = DialogMessage::DisplayDialogError { title, description };
self.dispatch(message);
}
/// Intentionally panic for debugging purposes /// Intentionally panic for debugging purposes
pub fn intentional_panic(&self) { pub fn intentional_panic(&self) {
panic!(); panic!();
@@ -359,8 +367,14 @@ impl JsEditorHandle {
} }
/// A font has been downloaded /// A font has been downloaded
pub fn on_font_load(&self, font_file_url: String, data: Vec<u8>, is_default: bool) -> Result<(), JsValue> { pub fn on_font_load(&self, font_family: String, font_style: String, preview_url: String, data: Vec<u8>, is_default: bool) -> Result<(), JsValue> {
let message = DocumentMessage::FontLoaded { font_file_url, data, is_default }; let message = PortfolioMessage::FontLoaded {
font_family,
font_style,
preview_url,
data,
is_default,
};
self.dispatch(message); self.dispatch(message);
Ok(()) Ok(())
+3 -3
View File
@@ -9,7 +9,7 @@ use std::cell::RefCell;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use std::mem::swap; use std::mem::swap;
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq)] #[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
pub enum BooleanOperation { pub enum BooleanOperation {
Union, Union,
Difference, Difference,
@@ -507,10 +507,10 @@ pub fn composite_boolean_operation(mut select: BooleanOperation, shapes: &mut Ve
} }
BooleanOperation::SubtractFront => { BooleanOperation::SubtractFront => {
let mut result = vec![shapes[0].borrow().clone()]; let mut result = vec![shapes[0].borrow().clone()];
for shape_idx in 1..shapes.len() { for shape_idx in shapes.iter().skip(1) {
let mut temp = Vec::new(); let mut temp = Vec::new();
for mut partial in result { for mut partial in result {
match boolean_operation(select, &mut partial, &mut shapes[shape_idx].borrow_mut()) { match boolean_operation(select, &mut partial, &mut shape_idx.borrow_mut()) {
Ok(mut partial_result) => temp.append(&mut partial_result), Ok(mut partial_result) => temp.append(&mut partial_result),
Err(BooleanOperationError::NothingDone) => temp.push(partial), Err(BooleanOperationError::NothingDone) => temp.push(partial),
Err(err) => return Err(err), Err(err) => return Err(err),
+25 -71
View File
@@ -6,7 +6,7 @@ use crate::layers::image_layer::ImageLayer;
use crate::layers::layer_info::{Layer, LayerData, LayerDataType}; use crate::layers::layer_info::{Layer, LayerData, LayerDataType};
use crate::layers::shape_layer::ShapeLayer; use crate::layers::shape_layer::ShapeLayer;
use crate::layers::style::ViewMode; use crate::layers::style::ViewMode;
use crate::layers::text_layer::TextLayer; use crate::layers::text_layer::{Font, FontCache, TextLayer};
use crate::{DocumentError, DocumentResponse, Operation}; use crate::{DocumentError, DocumentResponse, Operation};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
@@ -15,50 +15,12 @@ use serde::{Deserialize, Serialize};
use std::cell::RefCell; use std::cell::RefCell;
use std::cmp::max; use std::cmp::max;
use std::collections::hash_map::DefaultHasher; use std::collections::hash_map::DefaultHasher;
use std::collections::HashMap;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
/// A number that identifies a layer. /// A number that identifies a layer.
/// This does not technically need to be unique globally, only within a folder. /// This does not technically need to be unique globally, only within a folder.
pub type LayerId = u64; pub type LayerId = u64;
/// A cache of all loaded fonts along with a string of the name of the default font (sent from js)
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct FontCache {
data: HashMap<String, Vec<u8>>,
default_font: Option<String>,
}
impl FontCache {
/// Returns the font family name if the font is cached, otherwise returns the default font family name if that is cached
pub fn resolve_font<'a>(&'a self, font: Option<&'a String>) -> Option<&'a String> {
font.filter(|font| self.loaded_font(font))
.map_or(self.default_font.as_ref().filter(|font| self.loaded_font(font)), Some)
}
/// Try to get the bytes for a font
pub fn get<'a>(&'a self, font: Option<&String>) -> Option<&'a Vec<u8>> {
self.resolve_font(font).and_then(|font| self.data.get(font))
}
/// Check if the font is already loaded
pub fn loaded_font(&self, font: &str) -> bool {
self.data.contains_key(font)
}
/// Insert a new font into the cache
pub fn insert(&mut self, font: String, data: Vec<u8>, is_default: bool) {
if is_default {
self.default_font = Some(font.clone());
}
self.data.insert(font, data);
}
/// Checks if the font cache has a default font
pub fn has_default(&self) -> bool {
self.default_font.is_some()
}
}
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Document { pub struct Document {
/// The root layer, usually a [FolderLayer](layers::folder_layer::FolderLayer) that contains all other [Layers](layers::layer_info::Layer). /// The root layer, usually a [FolderLayer](layers::folder_layer::FolderLayer) that contains all other [Layers](layers::layer_info::Layer).
@@ -67,7 +29,6 @@ pub struct Document {
/// This identifier is not a hash and is not guaranteed to be equal for equivalent documents. /// This identifier is not a hash and is not guaranteed to be equal for equivalent documents.
#[serde(skip)] #[serde(skip)]
pub state_identifier: DefaultHasher, pub state_identifier: DefaultHasher,
pub font_cache: FontCache,
} }
impl Default for Document { impl Default for Document {
@@ -75,17 +36,16 @@ impl Default for Document {
Self { Self {
root: Layer::new(LayerDataType::Folder(FolderLayer::default()), DAffine2::IDENTITY.to_cols_array()), root: Layer::new(LayerDataType::Folder(FolderLayer::default()), DAffine2::IDENTITY.to_cols_array()),
state_identifier: DefaultHasher::new(), state_identifier: DefaultHasher::new(),
font_cache: FontCache::default(),
} }
} }
} }
impl Document { impl Document {
/// Wrapper around render, that returns the whole document as a Response. /// Wrapper around render, that returns the whole document as a Response.
pub fn render_root(&mut self, mode: ViewMode) -> String { pub fn render_root(&mut self, mode: ViewMode, font_cache: &FontCache) -> String {
let mut svg_defs = String::from("<defs>"); let mut svg_defs = String::from("<defs>");
self.root.render(&mut vec![], mode, &mut svg_defs, &self.font_cache); self.root.render(&mut vec![], mode, &mut svg_defs, font_cache);
svg_defs.push_str("</defs>"); svg_defs.push_str("</defs>");
@@ -98,14 +58,14 @@ impl Document {
} }
/// Checks whether each layer under `path` intersects with the provided `quad` and adds all intersection layers as paths to `intersections`. /// Checks whether each layer under `path` intersects with the provided `quad` and adds all intersection layers as paths to `intersections`.
pub fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>) { pub fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, font_cache: &FontCache) {
self.layer(path).unwrap().intersects_quad(quad, path, intersections, &self.font_cache); self.layer(path).unwrap().intersects_quad(quad, path, intersections, font_cache);
} }
/// Checks whether each layer under the root path intersects with the provided `quad` and returns the paths to all intersecting layers. /// Checks whether each layer under the root path intersects with the provided `quad` and returns the paths to all intersecting layers.
pub fn intersects_quad_root(&self, quad: Quad) -> Vec<Vec<LayerId>> { pub fn intersects_quad_root(&self, quad: Quad, font_cache: &FontCache) -> Vec<Vec<LayerId>> {
let mut intersections = Vec::new(); let mut intersections = Vec::new();
self.intersects_quad(quad, &mut vec![], &mut intersections); self.intersects_quad(quad, &mut vec![], &mut intersections, font_cache);
intersections intersections
} }
@@ -359,26 +319,26 @@ impl Document {
Ok(()) Ok(())
} }
pub fn viewport_bounding_box(&self, path: &[LayerId]) -> Result<Option<[DVec2; 2]>, DocumentError> { pub fn viewport_bounding_box(&self, path: &[LayerId], font_cache: &FontCache) -> Result<Option<[DVec2; 2]>, DocumentError> {
let layer = self.layer(path)?; let layer = self.layer(path)?;
let transform = self.multiply_transforms(path)?; let transform = self.multiply_transforms(path)?;
Ok(layer.data.bounding_box(transform, &self.font_cache)) Ok(layer.data.bounding_box(transform, font_cache))
} }
pub fn bounding_box_and_transform(&self, path: &[LayerId]) -> Result<Option<([DVec2; 2], DAffine2)>, DocumentError> { pub fn bounding_box_and_transform(&self, path: &[LayerId], font_cache: &FontCache) -> Result<Option<([DVec2; 2], DAffine2)>, DocumentError> {
let layer = self.layer(path)?; let layer = self.layer(path)?;
let transform = self.multiply_transforms(&path[..path.len() - 1])?; let transform = self.multiply_transforms(&path[..path.len() - 1])?;
Ok(layer.data.bounding_box(layer.transform, &self.font_cache).map(|bounds| (bounds, transform))) Ok(layer.data.bounding_box(layer.transform, font_cache).map(|bounds| (bounds, transform)))
} }
pub fn visible_layers_bounding_box(&self) -> Option<[DVec2; 2]> { pub fn visible_layers_bounding_box(&self, font_cache: &FontCache) -> Option<[DVec2; 2]> {
let mut paths = vec![]; let mut paths = vec![];
self.visible_layers(&mut vec![], &mut paths).ok()?; self.visible_layers(&mut vec![], &mut paths).ok()?;
self.combined_viewport_bounding_box(paths.iter().map(|x| x.as_slice())) self.combined_viewport_bounding_box(paths.iter().map(|x| x.as_slice()), font_cache)
} }
pub fn combined_viewport_bounding_box<'a>(&self, paths: impl Iterator<Item = &'a [LayerId]>) -> Option<[DVec2; 2]> { pub fn combined_viewport_bounding_box<'a>(&self, paths: impl Iterator<Item = &'a [LayerId]>, font_cache: &FontCache) -> Option<[DVec2; 2]> {
let boxes = paths.filter_map(|path| self.viewport_bounding_box(path).ok()?); let boxes = paths.filter_map(|path| self.viewport_bounding_box(path, font_cache).ok()?);
boxes.reduce(|a, b| [a[0].min(b[0]), a[1].max(b[1])]) boxes.reduce(|a, b| [a[0].min(b[0]), a[1].max(b[1])])
} }
@@ -473,7 +433,7 @@ impl Document {
/// Mutate the document by applying the `operation` to it. If the operation necessitates a /// Mutate the document by applying the `operation` to it. If the operation necessitates a
/// reaction from the frontend, responses may be returned. /// reaction from the frontend, responses may be returned.
pub fn handle_operation(&mut self, operation: Operation) -> Result<Option<Vec<DocumentResponse>>, DocumentError> { pub fn handle_operation(&mut self, operation: Operation, font_cache: &FontCache) -> Result<Option<Vec<DocumentResponse>>, DocumentError> {
use DocumentResponse::*; use DocumentResponse::*;
operation.pseudo_hash().hash(&mut self.state_identifier); operation.pseudo_hash().hash(&mut self.state_identifier);
@@ -536,9 +496,11 @@ impl Document {
size, size,
font_name, font_name,
font_style, font_style,
font_file,
} => { } => {
let layer = Layer::new(LayerDataType::Text(TextLayer::new(text, style, size, font_name, font_style, font_file, &self.font_cache)), transform); let font = Font::new(font_name, font_style);
let layer_text = TextLayer::new(text, style, size, font, font_cache);
let layer_data = LayerDataType::Text(layer_text);
let layer = Layer::new(layer_data, transform);
self.set_layer(&path, layer, insert_index)?; self.set_layer(&path, layer, insert_index)?;
@@ -575,7 +537,7 @@ impl Document {
.layer_mut(id) .layer_mut(id)
.ok_or_else(|| DocumentError::LayerNotFound(path.clone()))? .ok_or_else(|| DocumentError::LayerNotFound(path.clone()))?
.as_text_mut()? .as_text_mut()?
.update_text(new_text, &self.font_cache); .update_text(new_text, font_cache);
self.mark_as_dirty(&path)?; self.mark_as_dirty(&path)?;
@@ -723,13 +685,7 @@ impl Document {
return Err(DocumentError::IndexOutOfBounds); return Err(DocumentError::IndexOutOfBounds);
} }
} }
Operation::ModifyFont { Operation::ModifyFont { path, font_family, font_style, size } => {
path,
font_family,
font_style,
font_file,
size,
} => {
// Not using Document::layer_mut is necessary because we also need to borrow the font cache // Not using Document::layer_mut is necessary because we also need to borrow the font cache
let mut current_folder = &mut self.root; let mut current_folder = &mut self.root;
let (folder_path, id) = split_path(&path)?; let (folder_path, id) = split_path(&path)?;
@@ -739,11 +695,9 @@ impl Document {
let layer_mut = current_folder.as_folder_mut()?.layer_mut(id).ok_or_else(|| DocumentError::LayerNotFound(folder_path.into()))?; let layer_mut = current_folder.as_folder_mut()?.layer_mut(id).ok_or_else(|| DocumentError::LayerNotFound(folder_path.into()))?;
let text = layer_mut.as_text_mut()?; let text = layer_mut.as_text_mut()?;
text.font_family = font_family; text.font = Font::new(font_family, font_style);
text.font_style = font_style;
text.font_file = font_file;
text.size = size; text.size = size;
text.regenerate_path(text.load_face(&self.font_cache)); text.regenerate_path(text.load_face(font_cache));
self.mark_as_dirty(&path)?; self.mark_as_dirty(&path)?;
Some([vec![DocumentChanged, LayerChanged { path: path.clone() }], update_thumbnails_upstream(&path)].concat()) Some([vec![DocumentChanged, LayerChanged { path: path.clone() }], update_thumbnails_upstream(&path)].concat())
} }
@@ -806,7 +760,7 @@ impl Document {
let layer_mut = current_folder.as_folder_mut()?.layer_mut(id).ok_or_else(|| DocumentError::LayerNotFound(folder_path.into()))?; let layer_mut = current_folder.as_folder_mut()?.layer_mut(id).ok_or_else(|| DocumentError::LayerNotFound(folder_path.into()))?;
if let LayerDataType::Text(t) = &mut layer_mut.data { if let LayerDataType::Text(t) = &mut layer_mut.data {
let bezpath = t.to_bez_path(t.load_face(&self.font_cache)); let bezpath = t.to_bez_path(t.load_face(font_cache));
layer_mut.data = layers::layer_info::LayerDataType::Shape(ShapeLayer::from_bez_path(bezpath, t.path_style.clone(), true)); layer_mut.data = layers::layer_info::LayerDataType::Shape(ShapeLayer::from_bez_path(bezpath, t.path_style.clone(), true));
} }
+1 -1
View File
@@ -2,7 +2,7 @@ use super::LayerId;
use crate::boolean_ops::BooleanOperationError; use crate::boolean_ops::BooleanOperationError;
/// A set of different errors that can occur when using Graphene. /// A set of different errors that can occur when using Graphene.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum DocumentError { pub enum DocumentError {
LayerNotFound(Vec<LayerId>), LayerNotFound(Vec<LayerId>),
InvalidPath, InvalidPath,
+1 -1
View File
@@ -3,7 +3,7 @@ use std::fmt;
/// Describes how overlapping SVG elements should be blended together. /// Describes how overlapping SVG elements should be blended together.
/// See the [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/CSS/blend-mode#examples) for examples. /// See the [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/CSS/blend-mode#examples) for examples.
#[derive(PartialEq, Copy, Clone, Debug, Serialize, Deserialize)] #[derive(PartialEq, Eq, Copy, Clone, Debug, Serialize, Deserialize)]
pub enum BlendMode { pub enum BlendMode {
// Basic group // Basic group
Normal, Normal,
+1 -1
View File
@@ -1,7 +1,7 @@
use super::layer_info::{Layer, LayerData, LayerDataType}; use super::layer_info::{Layer, LayerData, LayerDataType};
use super::style::ViewMode; use super::style::ViewMode;
use crate::document::FontCache;
use crate::intersection::Quad; use crate::intersection::Quad;
use crate::layers::text_layer::FontCache;
use crate::{DocumentError, LayerId}; use crate::{DocumentError, LayerId};
use glam::DVec2; use glam::DVec2;
+1 -1
View File
@@ -1,7 +1,7 @@
use super::layer_info::LayerData; use super::layer_info::LayerData;
use super::style::ViewMode; use super::style::ViewMode;
use crate::document::FontCache;
use crate::intersection::{intersect_quad_bez_path, Quad}; use crate::intersection::{intersect_quad_bez_path, Quad};
use crate::layers::text_layer::FontCache;
use crate::LayerId; use crate::LayerId;
use glam::{DAffine2, DMat2, DVec2}; use glam::{DAffine2, DMat2, DVec2};
+1 -1
View File
@@ -4,8 +4,8 @@ use super::image_layer::ImageLayer;
use super::shape_layer::ShapeLayer; use super::shape_layer::ShapeLayer;
use super::style::{PathStyle, ViewMode}; use super::style::{PathStyle, ViewMode};
use super::text_layer::TextLayer; use super::text_layer::TextLayer;
use crate::document::FontCache;
use crate::intersection::Quad; use crate::intersection::Quad;
use crate::layers::text_layer::FontCache;
use crate::DocumentError; use crate::DocumentError;
use crate::LayerId; use crate::LayerId;
+1 -1
View File
@@ -1,7 +1,7 @@
use super::layer_info::LayerData; use super::layer_info::LayerData;
use super::style::{self, PathStyle, ViewMode}; use super::style::{self, PathStyle, ViewMode};
use crate::document::FontCache;
use crate::intersection::{intersect_quad_bez_path, Quad}; use crate::intersection::{intersect_quad_bez_path, Quad};
use crate::layers::text_layer::FontCache;
use crate::LayerId; use crate::LayerId;
use glam::{DAffine2, DMat2, DVec2}; use glam::{DAffine2, DMat2, DVec2};
+4 -4
View File
@@ -20,7 +20,7 @@ fn format_opacity(name: &str, opacity: f32) -> String {
} }
/// Represents different ways of rendering an object /// Represents different ways of rendering an object
#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
pub enum ViewMode { pub enum ViewMode {
/// Render with normal coloration at the current viewport resolution /// Render with normal coloration at the current viewport resolution
Normal, Normal,
@@ -36,7 +36,7 @@ impl Default for ViewMode {
} }
} }
#[derive(PartialEq, Clone, Copy, Debug, Hash, Serialize, Deserialize)] #[derive(PartialEq, Eq, Clone, Copy, Debug, Hash, Serialize, Deserialize)]
pub enum GradientType { pub enum GradientType {
Linear, Linear,
Radial, Radial,
@@ -174,7 +174,7 @@ impl Fill {
/// The stroke (outline) style of an SVG element. /// The stroke (outline) style of an SVG element.
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum LineCap { pub enum LineCap {
Butt, Butt,
Round, Round,
@@ -192,7 +192,7 @@ impl Display for LineCap {
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum LineJoin { pub enum LineJoin {
Miter, Miter,
Bevel, Bevel,
@@ -1,8 +1,8 @@
use super::layer_info::LayerData; use super::layer_info::LayerData;
use super::style::{PathStyle, ViewMode}; use super::style::{PathStyle, ViewMode};
use crate::document::FontCache;
use crate::intersection::{intersect_quad_bez_path, Quad}; use crate::intersection::{intersect_quad_bez_path, Quad};
use crate::LayerId; use crate::LayerId;
pub use font_cache::{Font, FontCache};
use glam::{DAffine2, DMat2, DVec2}; use glam::{DAffine2, DMat2, DVec2};
use kurbo::{Affine, BezPath, Rect, Shape}; use kurbo::{Affine, BezPath, Rect, Shape};
@@ -10,6 +10,7 @@ use rustybuzz::Face;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Write; use std::fmt::Write;
mod font_cache;
mod to_kurbo; mod to_kurbo;
fn glam_to_kurbo(transform: DAffine2) -> Affine { fn glam_to_kurbo(transform: DAffine2) -> Affine {
@@ -28,9 +29,7 @@ pub struct TextLayer {
/// Font size in pixels. /// Font size in pixels.
pub size: f64, pub size: f64,
pub line_width: Option<f64>, pub line_width: Option<f64>,
pub font_family: String, pub font: Font,
pub font_style: String,
pub font_file: Option<String>,
#[serde(skip)] #[serde(skip)]
pub editable: bool, pub editable: bool,
#[serde(skip)] #[serde(skip)]
@@ -54,8 +53,8 @@ impl LayerData for TextLayer {
let _ = svg.write_str(r#")">"#); let _ = svg.write_str(r#")">"#);
if self.editable { if self.editable {
let font = font_cache.resolve_font(self.font_file.as_ref()); let font = font_cache.resolve_font(&self.font);
if let Some(url) = font { if let Some(url) = font.and_then(|font| font_cache.get_preview_url(font)) {
let _ = write!(svg, r#"<style>@font-face {{font-family: local-font;src: url({});}}")</style>"#, url); let _ = write!(svg, r#"<style>@font-face {{font-family: local-font;src: url({});}}")</style>"#, url);
} }
@@ -118,7 +117,7 @@ impl LayerData for TextLayer {
impl TextLayer { impl TextLayer {
pub fn load_face<'a>(&self, font_cache: &'a FontCache) -> Option<Face<'a>> { pub fn load_face<'a>(&self, font_cache: &'a FontCache) -> Option<Face<'a>> {
font_cache.get(self.font_file.as_ref()).map(|data| rustybuzz::Face::from_slice(data, 0).expect("Loading font failed")) font_cache.get(&self.font).map(|data| rustybuzz::Face::from_slice(data, 0).expect("Loading font failed"))
} }
pub fn transform(&self, transforms: &[DAffine2], mode: ViewMode) -> DAffine2 { pub fn transform(&self, transforms: &[DAffine2], mode: ViewMode) -> DAffine2 {
@@ -129,15 +128,13 @@ impl TextLayer {
transforms.iter().skip(start).cloned().reduce(|a, b| a * b).unwrap_or(DAffine2::IDENTITY) transforms.iter().skip(start).cloned().reduce(|a, b| a * b).unwrap_or(DAffine2::IDENTITY)
} }
pub fn new(text: String, style: PathStyle, size: f64, font_family: String, font_style: String, font_file: Option<String>, font_cache: &FontCache) -> Self { pub fn new(text: String, style: PathStyle, size: f64, font: Font, font_cache: &FontCache) -> Self {
let mut new = Self { let mut new = Self {
text, text,
path_style: style, path_style: style,
size, size,
line_width: None, line_width: None,
font_family, font,
font_style,
font_file,
editable: false, editable: false,
cached_path: None, cached_path: None,
}; };
@@ -0,0 +1,64 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// A font type (storing font family and font style and an optional preview URL)
#[derive(Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq)]
pub struct Font {
pub font_family: String,
pub font_style: String,
}
impl Font {
pub fn new(font_family: String, font_style: String) -> Self {
Self { font_family, font_style }
}
}
/// A cache of all loaded font data and preview urls along with the default font (send from `init_app` in `editor_api.rs`)
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct FontCache {
/// Actual font file data used for rendering a font with ttf_parser and rustybuzz
font_file_data: HashMap<Font, Vec<u8>>,
/// Web font preview URLs used for showing fonts when live editing
preview_urls: HashMap<Font, String>,
/// The default font (used as a fallback)
default_font: Option<Font>,
}
impl FontCache {
/// Returns the font family name if the font is cached, otherwise returns the default font family name if that is cached
pub fn resolve_font<'a>(&'a self, font: &'a Font) -> Option<&'a Font> {
if self.loaded_font(font) {
Some(font)
} else {
self.default_font.as_ref().filter(|font| self.loaded_font(font))
}
}
/// Try to get the bytes for a font
pub fn get<'a>(&'a self, font: &Font) -> Option<&'a Vec<u8>> {
self.resolve_font(font).and_then(|font| self.font_file_data.get(font))
}
/// Check if the font is already loaded
pub fn loaded_font(&self, font: &Font) -> bool {
self.font_file_data.contains_key(font)
}
/// Insert a new font into the cache
pub fn insert(&mut self, font: Font, perview_url: String, data: Vec<u8>, is_default: bool) {
if is_default {
self.default_font = Some(font.clone());
}
self.font_file_data.insert(font.clone(), data);
self.preview_urls.insert(font, perview_url);
}
/// Checks if the font cache has a default font
pub fn has_default(&self) -> bool {
self.default_font.is_some()
}
/// Gets the preview URL for showing in text field when live editing
pub fn get_preview_url(&self, font: &Font) -> Option<&String> {
self.preview_urls.get(font)
}
}
-2
View File
@@ -55,7 +55,6 @@ pub enum Operation {
size: f64, size: f64,
font_name: String, font_name: String,
font_style: String, font_style: String,
font_file: Option<String>,
}, },
AddImage { AddImage {
path: Vec<LayerId>, path: Vec<LayerId>,
@@ -126,7 +125,6 @@ pub enum Operation {
path: Vec<LayerId>, path: Vec<LayerId>,
font_family: String, font_family: String,
font_style: String, font_style: String,
font_file: Option<String>,
size: f64, size: f64,
}, },
RenameLayer { RenameLayer {