Additional clean up and bug fixes after migrating document-legacy

This commit is contained in:
Keavon Chambers 2023-12-20 18:21:25 -08:00
parent 4733134b22
commit 5c7e04a725
14 changed files with 244 additions and 211 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -18,6 +18,8 @@ pub const VIEWPORT_SCROLL_RATE: f64 = 0.6;
pub const VIEWPORT_ROTATE_SNAP_INTERVAL: f64 = 15.;
pub const VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR: f64 = 0.95;
// Snapping axis
pub const SNAP_AXIS_TOLERANCE: f64 = 3.;
pub const SNAP_AXIS_OVERLAY_FADE_DISTANCE: f64 = 15.;
@ -79,9 +81,7 @@ pub const DEFAULT_FONT_FAMILY: &str = "Merriweather";
pub const DEFAULT_FONT_STYLE: &str = "Normal (400)";
// Document
pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.1.1"; // Remember to update the demo artwork in /demos with both this version number and the contents so it remains editable
pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.1.2"; // Remember to update the demo artwork in /demos with both this version number and the contents so it remains editable
pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document";
pub const FILE_SAVE_SUFFIX: &str = ".graphite";
pub const MAX_UNDO_HISTORY_LEN: usize = 100; // TODO: Add this to user preferences
pub const VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR: f32 = 1.05;

View file

@ -1,4 +1,3 @@
use crate::consts::VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
@ -34,7 +33,6 @@ impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHa
});
responses.add(NavigationMessage::FitViewportToBounds {
bounds: [DVec2::ZERO, self.dimensions.as_dvec2()],
padding_scale_factor: Some(VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR),
prevent_zoom_past_100: true,
});
}

View file

@ -30,7 +30,9 @@ pub fn default_mapping() -> Mapping {
// it as an available action in the respective message handler file (such as the bottom of `document_message_handler.rs`).
let mappings = mapping![
// HIGHER PRIORITY:
// ===============
// HIGHER PRIORITY
// ===============
//
// NavigationMessage
entry!(
@ -41,7 +43,9 @@ pub fn default_mapping() -> Mapping {
entry!(KeyDown(Lmb); action_dispatch=NavigationMessage::TransformFromMenuEnd { commit_key: Key::Lmb }),
entry!(KeyDown(Mmb); action_dispatch=NavigationMessage::TransformFromMenuEnd { commit_key: Key::Mmb }),
entry!(KeyDown(Rmb); action_dispatch=NavigationMessage::TransformFromMenuEnd { commit_key: Key::Rmb }),
// NORMAL PRIORITY:
// ===============
// NORMAL PRIORITY
// ===============
//
// NodeGraphMessage
entry!(KeyDown(Delete); modifiers=[Accel], action_dispatch=NodeGraphMessage::DeleteSelectedNodes { reconnect: false }),
@ -312,7 +316,6 @@ pub fn default_mapping() -> Mapping {
entry!(KeyDown(KeyS); action_dispatch=TransformLayerMessage::BeginScale),
//
// NavigationMessage
entry!(KeyDown(Lmb); modifiers=[Alt], action_dispatch=NavigationMessage::RotateCanvasBegin { was_dispatched_from_menu: false }),
entry!(KeyDown(Mmb); modifiers=[Alt], action_dispatch=NavigationMessage::RotateCanvasBegin { was_dispatched_from_menu: false }),
entry!(KeyDown(Mmb); modifiers=[Shift], action_dispatch=NavigationMessage::ZoomCanvasBegin),
entry!(KeyDown(Lmb); modifiers=[Shift, Space], action_dispatch=NavigationMessage::ZoomCanvasBegin),

View file

@ -4,7 +4,7 @@ use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate,
use crate::messages::portfolio::document::utility_types::LayerId;
use crate::messages::prelude::*;
use graph_craft::document::NodeId;
use graph_craft::document::{NodeId, NodeNetwork};
use graphene_core::raster::BlendMode;
use graphene_core::raster::Image;
use graphene_core::vector::style::ViewMode;
@ -40,7 +40,7 @@ pub enum DocumentMessage {
aggregate: AlignAggregate,
},
BackupDocument {
document: DocumentMessageHandler,
network: NodeNetwork,
},
ClearLayerTree,
CommitTransaction,

View file

@ -1,7 +1,7 @@
use super::utility_types::error::EditorError;
use super::utility_types::misc::{SnappingOptions, SnappingState};
use crate::application::generate_uuid;
use crate::consts::{ASYMPTOTIC_EFFECT, DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX, GRAPHITE_DOCUMENT_VERSION, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR};
use crate::application::{generate_uuid, GRAPHITE_GIT_COMMIT_HASH};
use crate::consts::{ASYMPTOTIC_EFFECT, DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX, GRAPHITE_DOCUMENT_VERSION, SCALE_EFFECT, SCROLLBAR_SPACING};
use crate::messages::input_mapper::utility_types::macros::action_keys;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::NodeGraphHandlerData;
@ -9,7 +9,7 @@ use crate::messages::portfolio::document::properties_panel::utility_types::Prope
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
use crate::messages::portfolio::document::utility_types::document_metadata::{is_artboard, DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::layer_panel::RawBuffer;
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, DocumentMode, FlipAxis};
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, DocumentMode, FlipAxis, PTZ};
use crate::messages::portfolio::document::utility_types::LayerId;
use crate::messages::portfolio::utility_types::PersistentData;
use crate::messages::prelude::*;
@ -29,56 +29,55 @@ use graphene_std::wasm_application_io::WasmEditorApi;
use glam::{DAffine2, DVec2};
use serde::{Deserialize, Serialize};
use std::collections::hash_map::DefaultHasher;
use std::hash::Hasher;
use std::vec;
/// Utility function for providing a default boolean value to serde.
#[inline(always)]
fn return_true() -> bool {
true
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DocumentMessageHandler {
// ======================
// Child message handlers
navigation_handler: NavigationMessageHandler,
// ======================
#[serde(skip)]
node_graph_handler: NodeGraphMessageHandler,
#[serde(skip)]
navigation_handler: NavigationMessageHandler,
#[serde(skip)]
overlays_message_handler: OverlaysMessageHandler,
#[serde(skip)]
properties_panel_message_handler: PropertiesPanelMessageHandler,
// ============================================
// Fields that are saved in the document format
//
pub name: String,
pub version: String,
// ============================================
#[serde(default = "default_network")]
pub network: NodeNetwork,
pub saved_document_identifier: u64,
pub auto_saved_document_identifier: u64,
// Fields that can be non-fatally missing from the saved document format
//
#[serde(default)]
pub document_mode: DocumentMode,
#[serde(default)]
#[serde(default = "default_name")]
pub name: String,
#[serde(default = "default_version")]
version: String,
#[serde(default = "default_commit_hash")]
commit_hash: String,
#[serde(default = "default_pan_tilt_zoom")]
navigation: PTZ,
#[serde(default = "default_document_mode")]
document_mode: DocumentMode,
#[serde(default = "default_view_mode")]
pub view_mode: ViewMode,
#[serde(default = "return_true")]
pub overlays_visible: bool,
#[serde(default = "return_true")]
#[serde(default = "default_overlays_visible")]
overlays_visible: bool,
#[serde(default = "default_rulers_visible")]
pub rulers_visible: bool,
#[serde(default)]
pub commit_hash: String,
#[serde(default)]
pub collapsed: Vec<LayerNodeIdentifier>,
#[serde(default = "default_collapsed")]
pub collapsed: Vec<LayerNodeIdentifier>, // TODO: Is this actually used? Maybe or maybe not. Investigate and potentially remove.
// =============================================
// Fields omitted from the saved document format
//
// =============================================
#[serde(skip)]
pub document_undo_history: VecDeque<DocumentMessageHandler>,
document_undo_history: VecDeque<NodeNetwork>,
#[serde(skip)]
pub document_redo_history: VecDeque<DocumentMessageHandler>,
document_redo_history: VecDeque<NodeNetwork>,
#[serde(skip)]
saved_hash: Option<u64>,
#[serde(skip)]
auto_saved_hash: Option<u64>,
/// Don't allow aborting transactions whilst undoing to avoid #559
#[serde(skip)]
undo_in_progress: bool,
@ -88,41 +87,87 @@ pub struct DocumentMessageHandler {
layer_range_selection_reference: Option<LayerNodeIdentifier>,
#[serde(skip)]
pub metadata: DocumentMetadata,
/// The state_identifier serves to provide a way to uniquely identify a particular state that the document is in.
/// This identifier is not a hash and is not guaranteed to be equal for equivalent documents.
#[serde(skip)]
pub state_identifier: DefaultHasher,
}
impl Default for DocumentMessageHandler {
fn default() -> Self {
Self {
network: root_network(),
saved_document_identifier: 0,
auto_saved_document_identifier: 0,
name: DEFAULT_DOCUMENT_NAME.to_string(),
version: GRAPHITE_DOCUMENT_VERSION.to_string(),
commit_hash: crate::application::GRAPHITE_GIT_COMMIT_HASH.to_string(),
collapsed: Vec::new(),
document_mode: DocumentMode::DesignMode,
view_mode: ViewMode::default(),
snapping_state: SnappingState::default(),
overlays_visible: true,
rulers_visible: true,
document_undo_history: VecDeque::new(),
document_redo_history: VecDeque::new(),
undo_in_progress: false,
layer_range_selection_reference: None,
// ======================
// Child message handlers
// ======================
node_graph_handler: Default::default(),
navigation_handler: NavigationMessageHandler::default(),
overlays_message_handler: OverlaysMessageHandler::default(),
properties_panel_message_handler: PropertiesPanelMessageHandler::default(),
node_graph_handler: Default::default(),
state_identifier: DefaultHasher::new(),
// ============================================
// Fields that are saved in the document format
// ============================================
network: root_network(),
name: DEFAULT_DOCUMENT_NAME.to_string(),
version: GRAPHITE_DOCUMENT_VERSION.to_string(),
commit_hash: GRAPHITE_GIT_COMMIT_HASH.to_string(),
navigation: PTZ::default(),
document_mode: DocumentMode::DesignMode,
view_mode: ViewMode::default(),
overlays_visible: true,
rulers_visible: true,
collapsed: Vec::new(),
// =============================================
// Fields omitted from the saved document format
// =============================================
document_undo_history: VecDeque::new(),
document_redo_history: VecDeque::new(),
saved_hash: None,
auto_saved_hash: None,
undo_in_progress: false,
snapping_state: SnappingState::default(),
layer_range_selection_reference: None,
metadata: Default::default(),
}
}
}
#[inline(always)]
fn default_network() -> NodeNetwork {
DocumentMessageHandler::default().network
}
#[inline(always)]
fn default_name() -> String {
DocumentMessageHandler::default().name
}
#[inline(always)]
fn default_version() -> String {
DocumentMessageHandler::default().version
}
#[inline(always)]
fn default_commit_hash() -> String {
DocumentMessageHandler::default().commit_hash
}
#[inline(always)]
fn default_pan_tilt_zoom() -> PTZ {
DocumentMessageHandler::default().navigation
}
#[inline(always)]
fn default_document_mode() -> DocumentMode {
DocumentMessageHandler::default().document_mode
}
#[inline(always)]
fn default_view_mode() -> ViewMode {
DocumentMessageHandler::default().view_mode
}
#[inline(always)]
fn default_overlays_visible() -> bool {
DocumentMessageHandler::default().overlays_visible
}
#[inline(always)]
fn default_rulers_visible() -> bool {
DocumentMessageHandler::default().rulers_visible
}
#[inline(always)]
fn default_collapsed() -> Vec<LayerNodeIdentifier> {
DocumentMessageHandler::default().collapsed
}
fn root_network() -> NodeNetwork {
{
let mut network = NodeNetwork::default();
@ -178,12 +223,6 @@ fn root_network() -> NodeNetwork {
}
}
impl PartialEq for DocumentMessageHandler {
fn eq(&self, other: &Self) -> bool {
self.state_identifier.finish() == other.state_identifier.finish()
}
}
pub struct DocumentInputs<'a> {
pub document_id: u64,
pub ipp: &'a InputPreprocessorMessageHandler,
@ -210,8 +249,11 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
#[remain::unsorted]
Navigation(message) => {
let document_bounds = self.metadata().document_bounds_viewport_space();
self.navigation_handler
.process_message(message, responses, (&self.metadata, document_bounds, ipp, self.selected_visible_layers_bounding_box_viewport()));
self.navigation_handler.process_message(
message,
responses,
(&self.metadata, document_bounds, ipp, self.selected_visible_layers_bounding_box_viewport(), &mut self.navigation),
);
}
#[remain::unsorted]
Overlays(message) => {
@ -290,7 +332,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
}
responses.add(BroadcastEvent::DocumentIsDirty);
}
BackupDocument { document } => self.backup_with_document(document, responses),
BackupDocument { network } => self.backup_with_document(network, responses),
ClearLayerTree => {
// Send an empty layer tree
let data_buffer: RawBuffer = Self::default().serialize_root().as_slice().into();
@ -544,7 +586,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
responses.add(OverlaysMessage::Draw);
}
RenderRulers => {
let document_transform_scale = self.navigation_handler.snapped_scale();
let document_transform_scale = self.navigation_handler.snapped_scale(self.navigation.zoom);
let ruler_origin = self.metadata().document_to_viewport.transform_point2(DVec2::ZERO);
let log = document_transform_scale.log2();
@ -559,7 +601,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
});
}
RenderScrollbars => {
let document_transform_scale = self.navigation_handler.snapped_scale();
let document_transform_scale = self.navigation_handler.snapped_scale(self.navigation.zoom);
let scale = 0.5 + ASYMPTOTIC_EFFECT + document_transform_scale * SCALE_EFFECT;
@ -758,11 +800,8 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
}
ZoomCanvasToFitAll => {
if let Some(bounds) = self.metadata().document_bounds_document_space(true) {
responses.add(NavigationMessage::FitViewportToBounds {
bounds,
padding_scale_factor: Some(VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR),
prevent_zoom_past_100: true,
})
responses.add(NavigationMessage::SetCanvasRotation { angle_radians: 0. });
responses.add(NavigationMessage::FitViewportToBounds { bounds, prevent_zoom_past_100: true });
}
}
}
@ -819,10 +858,6 @@ impl DocumentMessageHandler {
.reduce(graphene_core::renderer::Quad::combine_bounds)
}
pub fn current_state_identifier(&self) -> u64 {
self.state_identifier.finish()
}
pub fn network(&self) -> &NodeNetwork {
&self.network
}
@ -853,7 +888,7 @@ impl DocumentMessageHandler {
pub fn with_name(name: String, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) -> Self {
let mut document = Self { name, ..Self::default() };
let transform = document.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.size() / 2.);
let transform = document.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.size() / 2., DVec2::ZERO, 0., 1.);
document.metadata.document_to_viewport = transform;
responses.add(DocumentMessage::UpdateDocumentTransform { transform });
@ -934,9 +969,9 @@ impl DocumentMessageHandler {
}
/// Places a document into the history system
fn backup_with_document(&mut self, document: DocumentMessageHandler, responses: &mut VecDeque<Message>) {
fn backup_with_document(&mut self, network: NodeNetwork, responses: &mut VecDeque<Message>) {
self.document_redo_history.clear();
self.document_undo_history.push_back(document);
self.document_undo_history.push_back(network);
if self.document_undo_history.len() > crate::consts::MAX_UNDO_HISTORY_LEN {
self.document_undo_history.pop_front();
}
@ -947,38 +982,30 @@ impl DocumentMessageHandler {
/// Copies the entire document into the history system
pub fn backup(&mut self, responses: &mut VecDeque<Message>) {
self.backup_with_document(self.clone(), responses);
self.backup_with_document(self.network.clone(), responses);
}
// TODO: Is this now redundant?
/// Push a message backing up the document in its current state
pub fn backup_nonmut(&self, responses: &mut VecDeque<Message>) {
responses.add(DocumentMessage::BackupDocument { document: self.clone() });
responses.add(DocumentMessage::BackupDocument { network: self.network.clone() });
}
/// Replace the document with a new document save, returning the document save.
pub fn replace_document(&mut self, document: DocumentMessageHandler) -> DocumentMessageHandler {
// Replace the network. (Keeping the root is required if the bounds of the viewport have changed during the operation.)
let old_root = self.metadata().document_to_viewport;
let document = std::mem::replace(self, document);
self.metadata.document_to_viewport = old_root;
document
pub fn replace_document(&mut self, network: NodeNetwork) -> NodeNetwork {
std::mem::replace(&mut self.network, network)
}
pub fn undo(&mut self, responses: &mut VecDeque<Message>) {
// Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
let Some(document) = self.document_undo_history.pop_back() else {
return;
};
let Some(network) = self.document_undo_history.pop_back() else { return };
responses.add(BroadcastEvent::SelectionChanged);
let document_save = self.replace_document(document);
self.document_redo_history.push_back(document_save);
let previous_network = std::mem::replace(&mut self.network, network);
self.document_redo_history.push_back(previous_network);
if self.document_redo_history.len() > crate::consts::MAX_UNDO_HISTORY_LEN {
self.document_redo_history.pop_front();
}
@ -991,12 +1018,12 @@ impl DocumentMessageHandler {
// Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
let Some(document) = self.document_redo_history.pop_back() else { return };
let Some(network) = self.document_redo_history.pop_back() else { return };
responses.add(BroadcastEvent::SelectionChanged);
let document_save = self.replace_document(document);
self.document_undo_history.push_back(document_save);
let previous_network = std::mem::replace(&mut self.network, network);
self.document_undo_history.push_back(previous_network);
if self.document_undo_history.len() > crate::consts::MAX_UNDO_HISTORY_LEN {
self.document_undo_history.pop_front();
}
@ -1005,33 +1032,31 @@ impl DocumentMessageHandler {
responses.add(NodeGraphMessage::SendGraph { should_rerender: true });
}
pub fn current_identifier(&self) -> u64 {
// We can use the last state of the document to serve as the identifier to compare against
// This is useful since when the document is empty the identifier will be 0
self.document_undo_history.iter().last().map(|document| document.state_identifier.finish()).unwrap_or(0)
pub fn current_hash(&self) -> Option<u64> {
self.document_undo_history.iter().last().map(|network| network.current_hash())
}
pub fn is_auto_saved(&self) -> bool {
self.current_identifier() == self.auto_saved_document_identifier
self.current_hash() == self.auto_saved_hash
}
pub fn is_saved(&self) -> bool {
self.current_identifier() == self.saved_document_identifier
self.current_hash() == self.saved_hash
}
pub fn set_auto_save_state(&mut self, is_saved: bool) {
if is_saved {
self.auto_saved_document_identifier = self.current_identifier();
self.auto_saved_hash = self.current_hash();
} else {
self.auto_saved_document_identifier = generate_uuid();
self.auto_saved_hash = None;
}
}
pub fn set_save_state(&mut self, is_saved: bool) {
if is_saved {
self.saved_document_identifier = self.current_identifier();
self.saved_hash = self.current_hash();
} else {
self.saved_document_identifier = generate_uuid();
self.saved_hash = None;
}
}
@ -1177,7 +1202,7 @@ impl DocumentMessageHandler {
.on_update(|_| NavigationMessage::SetCanvasZoom { zoom_factor: 1. }.into())
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(Some(self.navigation_handler.snapped_scale() * 100.))
NumberInput::new(Some(self.navigation_handler.snapped_scale(self.navigation.zoom) * 100.))
.unit("%")
.min(0.000001)
.max(1000000.)
@ -1193,7 +1218,7 @@ impl DocumentMessageHandler {
.increment_callback_increase(|_| NavigationMessage::IncreaseCanvasZoom { center_on_mouse: false }.into())
.widget_holder(),
];
let rotation_value = self.navigation_handler.snapped_angle() / (std::f64::consts::PI / 180.);
let rotation_value = self.navigation_handler.snapped_angle(self.navigation.tilt) / (std::f64::consts::PI / 180.);
if rotation_value.abs() > 0.00001 {
widgets.extend([
Separator::new(SeparatorType::Related).widget_holder(),

View file

@ -14,7 +14,6 @@ pub enum NavigationMessage {
},
FitViewportToBounds {
bounds: [DVec2; 2],
padding_scale_factor: Option<f32>,
prevent_zoom_past_100: bool,
},
FitViewportToSelection,

View file

@ -6,6 +6,7 @@ use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion};
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
use crate::messages::portfolio::document::utility_types::document_metadata::DocumentMetadata;
use crate::messages::portfolio::document::utility_types::misc::PTZ;
use crate::messages::prelude::*;
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
@ -30,27 +31,16 @@ enum TransformOperation {
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq)]
pub struct NavigationMessageHandler {
pub pan: DVec2,
pub tilt: f64,
pub zoom: f64,
#[serde(skip)]
transform_operation: TransformOperation,
#[serde(skip)]
mouse_position: ViewportPosition,
#[serde(skip)]
finish_operation_with_click: bool,
}
impl Default for NavigationMessageHandler {
fn default() -> Self {
Self {
pan: DVec2::ZERO,
tilt: 0.,
zoom: 1.,
mouse_position: ViewportPosition::default(),
finish_operation_with_click: false,
transform_operation: TransformOperation::None,
@ -58,30 +48,29 @@ impl Default for NavigationMessageHandler {
}
}
impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &InputPreprocessorMessageHandler, Option<[DVec2; 2]>)> for NavigationMessageHandler {
impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &InputPreprocessorMessageHandler, Option<[DVec2; 2]>, &mut PTZ)> for NavigationMessageHandler {
#[remain::check]
fn process_message(
&mut self,
message: NavigationMessage,
responses: &mut VecDeque<Message>,
(document_metadata, document_bounds, ipp, selection_bounds): (&DocumentMetadata, Option<[DVec2; 2]>, &InputPreprocessorMessageHandler, Option<[DVec2; 2]>),
(document_metadata, document_bounds, ipp, selection_bounds, ptz): (&DocumentMetadata, Option<[DVec2; 2]>, &InputPreprocessorMessageHandler, Option<[DVec2; 2]>, &mut PTZ),
) {
use NavigationMessage::*;
let old_zoom = self.zoom;
let old_zoom = ptz.zoom;
#[remain::sorted]
match message {
DecreaseCanvasZoom { center_on_mouse } => {
let new_scale = *VIEWPORT_ZOOM_LEVELS.iter().rev().find(|scale| **scale < self.zoom).unwrap_or(&self.zoom);
let new_scale = *VIEWPORT_ZOOM_LEVELS.iter().rev().find(|scale| **scale < ptz.zoom).unwrap_or(&ptz.zoom);
if center_on_mouse {
responses.add(self.center_zoom(ipp.viewport_bounds.size(), new_scale / self.zoom, ipp.mouse.position));
responses.add(self.center_zoom(ipp.viewport_bounds.size(), new_scale / ptz.zoom, ipp.mouse.position));
}
responses.add(SetCanvasZoom { zoom_factor: new_scale });
}
FitViewportToBounds {
bounds: [pos1, pos2],
padding_scale_factor,
prevent_zoom_past_100,
} => {
let v1 = document_metadata.document_to_viewport.inverse().transform_point2(DVec2::ZERO);
@ -92,33 +81,32 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
let size = 1. / size;
let new_scale = size.min_element();
self.pan += center;
self.zoom *= new_scale;
ptz.pan += center;
ptz.zoom *= new_scale * VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR;
self.zoom /= padding_scale_factor.unwrap_or(1.) as f64;
if self.zoom > 1. && prevent_zoom_past_100 {
self.zoom = 1.
// Keep the canvas filling less than the full available viewport bounds if requested.
// And if the zoom is close to the full viewport bounds, we ignore the padding because 100% is preferrable if it still fits.
if prevent_zoom_past_100 && ptz.zoom > VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR {
ptz.zoom = 1.;
}
responses.add(BroadcastEvent::DocumentIsDirty);
responses.add(PortfolioMessage::UpdateDocumentWidgets);
self.create_document_transform(ipp.viewport_bounds.center(), responses);
self.create_document_transform(ipp.viewport_bounds.center(), &ptz, responses);
}
FitViewportToSelection => {
if let Some(bounds) = selection_bounds {
let transform = document_metadata.document_to_viewport.inverse();
responses.add(FitViewportToBounds {
bounds: [transform.transform_point2(bounds[0]), transform.transform_point2(bounds[1])],
padding_scale_factor: Some(VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR),
prevent_zoom_past_100: false,
})
}
}
IncreaseCanvasZoom { center_on_mouse } => {
let new_scale = *VIEWPORT_ZOOM_LEVELS.iter().find(|scale| **scale > self.zoom).unwrap_or(&self.zoom);
let new_scale = *VIEWPORT_ZOOM_LEVELS.iter().find(|scale| **scale > ptz.zoom).unwrap_or(&ptz.zoom);
if center_on_mouse {
responses.add(self.center_zoom(ipp.viewport_bounds.size(), new_scale / self.zoom, ipp.mouse.position));
responses.add(self.center_zoom(ipp.viewport_bounds.size(), new_scale / ptz.zoom, ipp.mouse.position));
}
responses.add(SetCanvasZoom { zoom_factor: new_scale });
}
@ -144,7 +132,7 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
if !(wait_for_snap_angle_release && new_snap && !snap_tilt_released) {
// When disabling snap, keep the viewed rotation as it was previously.
if !new_snap && snap_tilt {
self.tilt = self.snapped_angle();
ptz.tilt = self.snapped_angle(ptz.tilt);
}
self.transform_operation = TransformOperation::Rotate {
pre_commit_tilt,
@ -160,15 +148,15 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
start_offset.angle_between(end_offset)
};
responses.add(SetCanvasRotation { angle_radians: self.tilt + rotation });
responses.add(SetCanvasRotation { angle_radians: ptz.tilt + rotation });
}
TransformOperation::Zoom { snap_zoom_enabled, pre_commit_zoom } => {
let zoom_start = self.snapped_scale();
let zoom_start = self.snapped_scale(ptz.zoom);
let new_snap = ipp.keyboard.get(snap_zoom as usize);
// When disabling snap, keep the viewed zoom as it was previously
if !new_snap && snap_zoom_enabled {
self.zoom = self.snapped_scale();
ptz.zoom = self.snapped_scale(ptz.zoom);
}
if snap_zoom_enabled != new_snap {
@ -181,16 +169,16 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
let difference = self.mouse_position.y - ipp.mouse.position.y;
let amount = 1. + difference * VIEWPORT_ZOOM_MOUSE_RATE;
self.zoom *= amount;
self.zoom *= Self::clamp_zoom(self.zoom, document_bounds, old_zoom, ipp);
ptz.zoom *= amount;
ptz.zoom *= Self::clamp_zoom(ptz.zoom, document_bounds, old_zoom, ipp);
if let Some(mouse) = zoom_from_viewport {
let zoom_factor = self.snapped_scale() / zoom_start;
let zoom_factor = self.snapped_scale(ptz.zoom) / zoom_start;
responses.add(SetCanvasZoom { zoom_factor: self.zoom });
responses.add(SetCanvasZoom { zoom_factor: ptz.zoom });
responses.add(self.center_zoom(ipp.viewport_bounds.size(), zoom_factor, mouse));
} else {
responses.add(SetCanvasZoom { zoom_factor: self.zoom });
responses.add(SetCanvasZoom { zoom_factor: ptz.zoom });
}
}
}
@ -215,7 +203,7 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
});
self.transform_operation = TransformOperation::Rotate {
pre_commit_tilt: self.tilt,
pre_commit_tilt: ptz.tilt,
snap_tilt_released: false,
snap_tilt: false,
};
@ -224,17 +212,17 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
self.finish_operation_with_click = was_dispatched_from_menu;
}
SetCanvasRotation { angle_radians } => {
self.tilt = angle_radians;
self.create_document_transform(ipp.viewport_bounds.center(), responses);
ptz.tilt = angle_radians;
self.create_document_transform(ipp.viewport_bounds.center(), &ptz, responses);
responses.add(BroadcastEvent::DocumentIsDirty);
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
SetCanvasZoom { zoom_factor } => {
self.zoom = zoom_factor.clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX);
self.zoom *= Self::clamp_zoom(self.zoom, document_bounds, old_zoom, ipp);
ptz.zoom = zoom_factor.clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX);
ptz.zoom *= Self::clamp_zoom(ptz.zoom, document_bounds, old_zoom, ipp);
responses.add(BroadcastEvent::DocumentIsDirty);
responses.add(PortfolioMessage::UpdateDocumentWidgets);
self.create_document_transform(ipp.viewport_bounds.center(), responses);
self.create_document_transform(ipp.viewport_bounds.center(), &ptz, responses);
}
TransformCanvasEnd { abort_transform } => {
if abort_transform {
@ -244,19 +232,19 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
responses.add(SetCanvasRotation { angle_radians: pre_commit_tilt });
}
TransformOperation::Pan { pre_commit_pan, .. } => {
self.pan = pre_commit_pan;
self.create_document_transform(ipp.viewport_bounds.center(), responses);
ptz.pan = pre_commit_pan;
self.create_document_transform(ipp.viewport_bounds.center(), &ptz, responses);
}
TransformOperation::Zoom { pre_commit_zoom, .. } => {
self.zoom = pre_commit_zoom;
ptz.zoom = pre_commit_zoom;
responses.add(PortfolioMessage::UpdateDocumentWidgets);
self.create_document_transform(ipp.viewport_bounds.center(), responses);
self.create_document_transform(ipp.viewport_bounds.center(), &ptz, responses);
}
}
}
self.tilt = self.snapped_angle();
self.zoom = self.snapped_scale();
ptz.tilt = self.snapped_angle(ptz.tilt);
ptz.zoom = self.snapped_scale(ptz.zoom);
responses.add(BroadcastEvent::CanvasTransformed);
responses.add(BroadcastEvent::DocumentIsDirty);
responses.add(ToolMessage::UpdateCursor);
@ -271,10 +259,10 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
TranslateCanvas { delta } => {
let transformed_delta = document_metadata.document_to_viewport.inverse().transform_vector2(delta);
self.pan += transformed_delta;
ptz.pan += transformed_delta;
responses.add(BroadcastEvent::CanvasTransformed);
responses.add(BroadcastEvent::DocumentIsDirty);
self.create_document_transform(ipp.viewport_bounds.center(), responses);
self.create_document_transform(ipp.viewport_bounds.center(), &ptz, responses);
}
TranslateCanvasBegin => {
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Grabbing });
@ -284,14 +272,14 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
});
self.mouse_position = ipp.mouse.position;
self.transform_operation = TransformOperation::Pan { pre_commit_pan: self.pan };
self.transform_operation = TransformOperation::Pan { pre_commit_pan: ptz.pan };
}
TranslateCanvasByViewportFraction { delta } => {
let transformed_delta = document_metadata.document_to_viewport.inverse().transform_vector2(delta * ipp.viewport_bounds.size());
self.pan += transformed_delta;
ptz.pan += transformed_delta;
responses.add(BroadcastEvent::DocumentIsDirty);
self.create_document_transform(ipp.viewport_bounds.center(), responses);
self.create_document_transform(ipp.viewport_bounds.center(), &ptz, responses);
}
WheelCanvasTranslate { use_y_as_x } => {
let delta = match use_y_as_x {
@ -306,10 +294,10 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
if ipp.mouse.scroll_delta.y > 0 {
zoom_factor = 1. / zoom_factor
}
zoom_factor *= Self::clamp_zoom(self.zoom * zoom_factor, document_bounds, old_zoom, ipp);
zoom_factor *= Self::clamp_zoom(ptz.zoom * zoom_factor, document_bounds, old_zoom, ipp);
responses.add(self.center_zoom(ipp.viewport_bounds.size(), zoom_factor, ipp.mouse.position));
responses.add(SetCanvasZoom { zoom_factor: self.zoom * zoom_factor });
responses.add(SetCanvasZoom { zoom_factor: ptz.zoom * zoom_factor });
}
ZoomCanvasBegin => {
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::ZoomIn });
@ -328,7 +316,7 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
});
self.transform_operation = TransformOperation::Zoom {
pre_commit_zoom: self.zoom,
pre_commit_zoom: ptz.zoom,
snap_zoom_enabled: false,
};
self.mouse_position = ipp.mouse.position;
@ -372,43 +360,40 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
}
impl NavigationMessageHandler {
pub fn snapped_angle(&self) -> f64 {
pub fn snapped_angle(&self, tilt: f64) -> f64 {
let increment_radians: f64 = VIEWPORT_ROTATE_SNAP_INTERVAL.to_radians();
if let TransformOperation::Rotate { snap_tilt: true, .. } = self.transform_operation {
(self.tilt / increment_radians).round() * increment_radians
(tilt / increment_radians).round() * increment_radians
} else {
self.tilt
tilt
}
}
pub fn snapped_scale(&self) -> f64 {
pub fn snapped_scale(&self, zoom: f64) -> f64 {
if let TransformOperation::Zoom { snap_zoom_enabled: true, .. } = self.transform_operation {
*VIEWPORT_ZOOM_LEVELS
.iter()
.min_by(|a, b| (**a - self.zoom).abs().partial_cmp(&(**b - self.zoom).abs()).unwrap())
.unwrap_or(&self.zoom)
*VIEWPORT_ZOOM_LEVELS.iter().min_by(|a, b| (**a - zoom).abs().partial_cmp(&(**b - zoom).abs()).unwrap()).unwrap_or(&zoom)
} else {
self.zoom
zoom
}
}
pub fn calculate_offset_transform(&self, viewport_center: DVec2) -> DAffine2 {
let scaled_centre = viewport_center / self.snapped_scale();
pub fn calculate_offset_transform(&self, viewport_center: DVec2, pan: DVec2, tilt: f64, zoom: f64) -> DAffine2 {
let scaled_centre = viewport_center / self.snapped_scale(zoom);
// Try to avoid fractional coordinates to reduce anti aliasing.
let scale = self.snapped_scale();
let rounded_pan = ((self.pan + scaled_centre) * scale).round() / scale - scaled_centre;
let scale = self.snapped_scale(zoom);
let rounded_pan = ((pan + scaled_centre) * scale).round() / scale - scaled_centre;
// TODO: replace with DAffine2::from_scale_angle_translation and fix the errors
let offset_transform = DAffine2::from_translation(scaled_centre);
let scale_transform = DAffine2::from_scale(DVec2::splat(scale));
let angle_transform = DAffine2::from_angle(self.snapped_angle());
let angle_transform = DAffine2::from_angle(self.snapped_angle(tilt));
let translation_transform = DAffine2::from_translation(rounded_pan);
scale_transform * offset_transform * angle_transform * translation_transform
}
fn create_document_transform(&self, viewport_center: DVec2, responses: &mut VecDeque<Message>) {
let transform = self.calculate_offset_transform(viewport_center);
fn create_document_transform(&self, viewport_center: DVec2, ptz: &PTZ, responses: &mut VecDeque<Message>) {
let transform = self.calculate_offset_transform(viewport_center, ptz.pan, ptz.tilt, ptz.zoom);
responses.add(DocumentMessage::UpdateDocumentTransform { transform });
}

View file

@ -122,12 +122,11 @@ impl FrontendNodeType {
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[derive(Debug, Clone, PartialEq)]
pub struct NodeGraphMessageHandler {
pub layer_path: Option<Vec<LayerId>>,
pub network: Vec<NodeId>,
has_selection: bool,
#[serde(skip)]
pub widgets: [LayoutGroup; 2],
}

View file

@ -4,9 +4,7 @@ use crate::messages::portfolio::document::node_graph::NodePropertiesContext;
use crate::messages::portfolio::utility_types::PersistentData;
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[derive(Debug, Clone, Default)]
pub struct PropertiesPanelMessageHandler;
impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPanelMessageHandlerData<'a>)> for PropertiesPanelMessageHandler {

View file

@ -3,6 +3,7 @@ use crate::messages::portfolio::document::utility_types::LayerId;
use graphene_core::raster::color::Color;
use glam::DVec2;
use serde::{Deserialize, Serialize};
use std::fmt;
@ -59,7 +60,7 @@ pub enum DocumentRenderMode<'a> {
LayerCutout(&'a [LayerId], Color),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug)]
/// SnappingState determines the current individual snapping states
pub struct SnappingState {
pub snapping_enabled: bool,
@ -91,3 +92,16 @@ impl fmt::Display for SnappingOptions {
}
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct PTZ {
pub pan: DVec2,
pub tilt: f64,
pub zoom: f64,
}
impl Default for PTZ {
fn default() -> Self {
Self { pan: DVec2::ZERO, tilt: 0., zoom: 1. }
}
}

View file

@ -614,10 +614,13 @@ impl Fsm for SelectToolFsmState {
}
tool_data.drag_current = mouse_position + closest_move;
if input.keyboard.key(duplicate) && tool_data.not_duplicated_layers.is_none() {
tool_data.start_duplicates(document, responses);
} else if !input.keyboard.key(duplicate) && tool_data.not_duplicated_layers.is_some() {
tool_data.stop_duplicates(document, responses);
// TODO: Reenable this feature after fixing it
if false {
if input.keyboard.key(duplicate) && tool_data.not_duplicated_layers.is_none() {
tool_data.start_duplicates(document, responses);
} else if !input.keyboard.key(duplicate) && tool_data.not_duplicated_layers.is_some() {
tool_data.stop_duplicates(document, responses);
}
}
SelectToolFsmState::Dragging
@ -625,7 +628,8 @@ impl Fsm for SelectToolFsmState {
(SelectToolFsmState::ResizingBounds, SelectToolMessage::PointerMove { axis_align, center, .. }) => {
if let Some(bounds) = &mut tool_data.bounding_box_manager {
if let Some(movement) = &mut bounds.selected_edges {
let (center, axis_align) = (input.keyboard.key(center), input.keyboard.key(axis_align));
let (_center, axis_align) = (input.keyboard.key(center), input.keyboard.key(axis_align));
let center = false; // TODO: Reenable this feature after fixing it
let mouse_position = input.mouse.position;

View file

@ -1,11 +1,14 @@
use crate::document::value::TaggedValue;
use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput};
use graphene_core::{GraphicGroup, ProtoNodeIdentifier, Type};
use dyn_any::{DynAny, StaticType};
use glam::IVec2;
pub use graphene_core::uuid::generate_uuid;
use graphene_core::{GraphicGroup, ProtoNodeIdentifier, Type};
use glam::IVec2;
use std::collections::hash_map::DefaultHasher;
use std::collections::{HashMap, HashSet};
use std::hash::{Hash, Hasher};
pub mod value;
@ -14,8 +17,7 @@ pub type NodeId = u64;
/// Hash two IDs together, returning a new ID that is always consistant for two input IDs in a specific order.
/// This is used during [`NodeNetwork::flatten`] in order to ensure consistant yet non-conflicting IDs for inner networks.
fn merge_ids(a: u64, b: u64) -> u64 {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
let mut hasher = DefaultHasher::new();
a.hash(&mut hasher);
b.hash(&mut hasher);
hasher.finish()
@ -448,6 +450,12 @@ impl std::hash::Hash for NodeNetwork {
/// Graph modification functions
impl NodeNetwork {
pub fn current_hash(&self) -> u64 {
let mut hasher = DefaultHasher::new();
self.hash(&mut hasher);
hasher.finish()
}
/// Get the original output nodes of this network, ignoring any preview node
pub fn original_outputs(&self) -> &Vec<NodeOutput> {
self.previous_outputs.as_ref().unwrap_or(&self.outputs)