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_ROTATE_SNAP_INTERVAL: f64 = 15.;
pub const VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR: f64 = 0.95;
// Snapping axis // Snapping axis
pub const SNAP_AXIS_TOLERANCE: f64 = 3.; pub const SNAP_AXIS_TOLERANCE: f64 = 3.;
pub const SNAP_AXIS_OVERLAY_FADE_DISTANCE: f64 = 15.; 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)"; pub const DEFAULT_FONT_STYLE: &str = "Normal (400)";
// Document // 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 DEFAULT_DOCUMENT_NAME: &str = "Untitled Document";
pub const FILE_SAVE_SUFFIX: &str = ".graphite"; pub const FILE_SAVE_SUFFIX: &str = ".graphite";
pub const MAX_UNDO_HISTORY_LEN: usize = 100; // TODO: Add this to user preferences 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::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*; use crate::messages::prelude::*;
@ -34,7 +33,6 @@ impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHa
}); });
responses.add(NavigationMessage::FitViewportToBounds { responses.add(NavigationMessage::FitViewportToBounds {
bounds: [DVec2::ZERO, self.dimensions.as_dvec2()], bounds: [DVec2::ZERO, self.dimensions.as_dvec2()],
padding_scale_factor: Some(VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR),
prevent_zoom_past_100: true, 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`). // it as an available action in the respective message handler file (such as the bottom of `document_message_handler.rs`).
let mappings = mapping![ let mappings = mapping![
// HIGHER PRIORITY: // ===============
// HIGHER PRIORITY
// ===============
// //
// NavigationMessage // NavigationMessage
entry!( entry!(
@ -41,7 +43,9 @@ pub fn default_mapping() -> Mapping {
entry!(KeyDown(Lmb); action_dispatch=NavigationMessage::TransformFromMenuEnd { commit_key: Key::Lmb }), entry!(KeyDown(Lmb); action_dispatch=NavigationMessage::TransformFromMenuEnd { commit_key: Key::Lmb }),
entry!(KeyDown(Mmb); action_dispatch=NavigationMessage::TransformFromMenuEnd { commit_key: Key::Mmb }), entry!(KeyDown(Mmb); action_dispatch=NavigationMessage::TransformFromMenuEnd { commit_key: Key::Mmb }),
entry!(KeyDown(Rmb); action_dispatch=NavigationMessage::TransformFromMenuEnd { commit_key: Key::Rmb }), entry!(KeyDown(Rmb); action_dispatch=NavigationMessage::TransformFromMenuEnd { commit_key: Key::Rmb }),
// NORMAL PRIORITY: // ===============
// NORMAL PRIORITY
// ===============
// //
// NodeGraphMessage // NodeGraphMessage
entry!(KeyDown(Delete); modifiers=[Accel], action_dispatch=NodeGraphMessage::DeleteSelectedNodes { reconnect: false }), 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), entry!(KeyDown(KeyS); action_dispatch=TransformLayerMessage::BeginScale),
// //
// NavigationMessage // 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=[Alt], action_dispatch=NavigationMessage::RotateCanvasBegin { was_dispatched_from_menu: false }),
entry!(KeyDown(Mmb); modifiers=[Shift], action_dispatch=NavigationMessage::ZoomCanvasBegin), entry!(KeyDown(Mmb); modifiers=[Shift], action_dispatch=NavigationMessage::ZoomCanvasBegin),
entry!(KeyDown(Lmb); modifiers=[Shift, Space], 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::portfolio::document::utility_types::LayerId;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use graph_craft::document::NodeId; use graph_craft::document::{NodeId, NodeNetwork};
use graphene_core::raster::BlendMode; use graphene_core::raster::BlendMode;
use graphene_core::raster::Image; use graphene_core::raster::Image;
use graphene_core::vector::style::ViewMode; use graphene_core::vector::style::ViewMode;
@ -40,7 +40,7 @@ pub enum DocumentMessage {
aggregate: AlignAggregate, aggregate: AlignAggregate,
}, },
BackupDocument { BackupDocument {
document: DocumentMessageHandler, network: NodeNetwork,
}, },
ClearLayerTree, ClearLayerTree,
CommitTransaction, CommitTransaction,

View File

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

View File

@ -14,7 +14,6 @@ pub enum NavigationMessage {
}, },
FitViewportToBounds { FitViewportToBounds {
bounds: [DVec2; 2], bounds: [DVec2; 2],
padding_scale_factor: Option<f32>,
prevent_zoom_past_100: bool, prevent_zoom_past_100: bool,
}, },
FitViewportToSelection, 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_keyboard::{Key, KeysGroup, MouseMotion};
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition; 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::document_metadata::DocumentMetadata;
use crate::messages::portfolio::document::utility_types::misc::PTZ;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; 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 struct NavigationMessageHandler {
pub pan: DVec2,
pub tilt: f64,
pub zoom: f64,
#[serde(skip)]
transform_operation: TransformOperation, transform_operation: TransformOperation,
#[serde(skip)]
mouse_position: ViewportPosition, mouse_position: ViewportPosition,
#[serde(skip)]
finish_operation_with_click: bool, finish_operation_with_click: bool,
} }
impl Default for NavigationMessageHandler { impl Default for NavigationMessageHandler {
fn default() -> Self { fn default() -> Self {
Self { Self {
pan: DVec2::ZERO,
tilt: 0.,
zoom: 1.,
mouse_position: ViewportPosition::default(), mouse_position: ViewportPosition::default(),
finish_operation_with_click: false, finish_operation_with_click: false,
transform_operation: TransformOperation::None, 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] #[remain::check]
fn process_message( fn process_message(
&mut self, &mut self,
message: NavigationMessage, message: NavigationMessage,
responses: &mut VecDeque<Message>, 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::*; use NavigationMessage::*;
let old_zoom = self.zoom; let old_zoom = ptz.zoom;
#[remain::sorted] #[remain::sorted]
match message { match message {
DecreaseCanvasZoom { center_on_mouse } => { 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 { 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 }); responses.add(SetCanvasZoom { zoom_factor: new_scale });
} }
FitViewportToBounds { FitViewportToBounds {
bounds: [pos1, pos2], bounds: [pos1, pos2],
padding_scale_factor,
prevent_zoom_past_100, prevent_zoom_past_100,
} => { } => {
let v1 = document_metadata.document_to_viewport.inverse().transform_point2(DVec2::ZERO); 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 size = 1. / size;
let new_scale = size.min_element(); let new_scale = size.min_element();
self.pan += center; ptz.pan += center;
self.zoom *= new_scale; ptz.zoom *= new_scale * VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR;
self.zoom /= padding_scale_factor.unwrap_or(1.) as f64; // 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 self.zoom > 1. && prevent_zoom_past_100 { if prevent_zoom_past_100 && ptz.zoom > VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR {
self.zoom = 1. ptz.zoom = 1.;
} }
responses.add(BroadcastEvent::DocumentIsDirty); responses.add(BroadcastEvent::DocumentIsDirty);
responses.add(PortfolioMessage::UpdateDocumentWidgets); responses.add(PortfolioMessage::UpdateDocumentWidgets);
self.create_document_transform(ipp.viewport_bounds.center(), responses); self.create_document_transform(ipp.viewport_bounds.center(), &ptz, responses);
} }
FitViewportToSelection => { FitViewportToSelection => {
if let Some(bounds) = selection_bounds { if let Some(bounds) = selection_bounds {
let transform = document_metadata.document_to_viewport.inverse(); let transform = document_metadata.document_to_viewport.inverse();
responses.add(FitViewportToBounds { responses.add(FitViewportToBounds {
bounds: [transform.transform_point2(bounds[0]), transform.transform_point2(bounds[1])], 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, prevent_zoom_past_100: false,
}) })
} }
} }
IncreaseCanvasZoom { center_on_mouse } => { 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 { 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 }); 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) { if !(wait_for_snap_angle_release && new_snap && !snap_tilt_released) {
// When disabling snap, keep the viewed rotation as it was previously. // When disabling snap, keep the viewed rotation as it was previously.
if !new_snap && snap_tilt { if !new_snap && snap_tilt {
self.tilt = self.snapped_angle(); ptz.tilt = self.snapped_angle(ptz.tilt);
} }
self.transform_operation = TransformOperation::Rotate { self.transform_operation = TransformOperation::Rotate {
pre_commit_tilt, pre_commit_tilt,
@ -160,15 +148,15 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
start_offset.angle_between(end_offset) 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 } => { 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); let new_snap = ipp.keyboard.get(snap_zoom as usize);
// When disabling snap, keep the viewed zoom as it was previously // When disabling snap, keep the viewed zoom as it was previously
if !new_snap && snap_zoom_enabled { if !new_snap && snap_zoom_enabled {
self.zoom = self.snapped_scale(); ptz.zoom = self.snapped_scale(ptz.zoom);
} }
if snap_zoom_enabled != new_snap { 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 difference = self.mouse_position.y - ipp.mouse.position.y;
let amount = 1. + difference * VIEWPORT_ZOOM_MOUSE_RATE; let amount = 1. + difference * VIEWPORT_ZOOM_MOUSE_RATE;
self.zoom *= amount; ptz.zoom *= amount;
self.zoom *= Self::clamp_zoom(self.zoom, document_bounds, old_zoom, ipp); ptz.zoom *= Self::clamp_zoom(ptz.zoom, document_bounds, old_zoom, ipp);
if let Some(mouse) = zoom_from_viewport { 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)); responses.add(self.center_zoom(ipp.viewport_bounds.size(), zoom_factor, mouse));
} else { } 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 { self.transform_operation = TransformOperation::Rotate {
pre_commit_tilt: self.tilt, pre_commit_tilt: ptz.tilt,
snap_tilt_released: false, snap_tilt_released: false,
snap_tilt: false, snap_tilt: false,
}; };
@ -224,17 +212,17 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
self.finish_operation_with_click = was_dispatched_from_menu; self.finish_operation_with_click = was_dispatched_from_menu;
} }
SetCanvasRotation { angle_radians } => { SetCanvasRotation { angle_radians } => {
self.tilt = angle_radians; ptz.tilt = angle_radians;
self.create_document_transform(ipp.viewport_bounds.center(), responses); self.create_document_transform(ipp.viewport_bounds.center(), &ptz, responses);
responses.add(BroadcastEvent::DocumentIsDirty); responses.add(BroadcastEvent::DocumentIsDirty);
responses.add(PortfolioMessage::UpdateDocumentWidgets); responses.add(PortfolioMessage::UpdateDocumentWidgets);
} }
SetCanvasZoom { zoom_factor } => { SetCanvasZoom { zoom_factor } => {
self.zoom = zoom_factor.clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX); ptz.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 *= Self::clamp_zoom(ptz.zoom, document_bounds, old_zoom, ipp);
responses.add(BroadcastEvent::DocumentIsDirty); responses.add(BroadcastEvent::DocumentIsDirty);
responses.add(PortfolioMessage::UpdateDocumentWidgets); 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 } => { TransformCanvasEnd { abort_transform } => {
if abort_transform { if abort_transform {
@ -244,19 +232,19 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
responses.add(SetCanvasRotation { angle_radians: pre_commit_tilt }); responses.add(SetCanvasRotation { angle_radians: pre_commit_tilt });
} }
TransformOperation::Pan { pre_commit_pan, .. } => { TransformOperation::Pan { pre_commit_pan, .. } => {
self.pan = pre_commit_pan; ptz.pan = pre_commit_pan;
self.create_document_transform(ipp.viewport_bounds.center(), responses); self.create_document_transform(ipp.viewport_bounds.center(), &ptz, responses);
} }
TransformOperation::Zoom { pre_commit_zoom, .. } => { TransformOperation::Zoom { pre_commit_zoom, .. } => {
self.zoom = pre_commit_zoom; ptz.zoom = pre_commit_zoom;
responses.add(PortfolioMessage::UpdateDocumentWidgets); 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(); ptz.tilt = self.snapped_angle(ptz.tilt);
self.zoom = self.snapped_scale(); ptz.zoom = self.snapped_scale(ptz.zoom);
responses.add(BroadcastEvent::CanvasTransformed); responses.add(BroadcastEvent::CanvasTransformed);
responses.add(BroadcastEvent::DocumentIsDirty); responses.add(BroadcastEvent::DocumentIsDirty);
responses.add(ToolMessage::UpdateCursor); responses.add(ToolMessage::UpdateCursor);
@ -271,10 +259,10 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
TranslateCanvas { delta } => { TranslateCanvas { delta } => {
let transformed_delta = document_metadata.document_to_viewport.inverse().transform_vector2(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::CanvasTransformed);
responses.add(BroadcastEvent::DocumentIsDirty); responses.add(BroadcastEvent::DocumentIsDirty);
self.create_document_transform(ipp.viewport_bounds.center(), responses); self.create_document_transform(ipp.viewport_bounds.center(), &ptz, responses);
} }
TranslateCanvasBegin => { TranslateCanvasBegin => {
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Grabbing }); 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.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 } => { TranslateCanvasByViewportFraction { delta } => {
let transformed_delta = document_metadata.document_to_viewport.inverse().transform_vector2(delta * ipp.viewport_bounds.size()); 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); 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 } => { WheelCanvasTranslate { use_y_as_x } => {
let delta = match 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 { if ipp.mouse.scroll_delta.y > 0 {
zoom_factor = 1. / zoom_factor 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(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 => { ZoomCanvasBegin => {
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::ZoomIn }); responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::ZoomIn });
@ -328,7 +316,7 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
}); });
self.transform_operation = TransformOperation::Zoom { self.transform_operation = TransformOperation::Zoom {
pre_commit_zoom: self.zoom, pre_commit_zoom: ptz.zoom,
snap_zoom_enabled: false, snap_zoom_enabled: false,
}; };
self.mouse_position = ipp.mouse.position; self.mouse_position = ipp.mouse.position;
@ -372,43 +360,40 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
} }
impl NavigationMessageHandler { 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(); let increment_radians: f64 = VIEWPORT_ROTATE_SNAP_INTERVAL.to_radians();
if let TransformOperation::Rotate { snap_tilt: true, .. } = self.transform_operation { if let TransformOperation::Rotate { snap_tilt: true, .. } = self.transform_operation {
(self.tilt / increment_radians).round() * increment_radians (tilt / increment_radians).round() * increment_radians
} else { } 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 { if let TransformOperation::Zoom { snap_zoom_enabled: true, .. } = self.transform_operation {
*VIEWPORT_ZOOM_LEVELS *VIEWPORT_ZOOM_LEVELS.iter().min_by(|a, b| (**a - zoom).abs().partial_cmp(&(**b - zoom).abs()).unwrap()).unwrap_or(&zoom)
.iter()
.min_by(|a, b| (**a - self.zoom).abs().partial_cmp(&(**b - self.zoom).abs()).unwrap())
.unwrap_or(&self.zoom)
} else { } else {
self.zoom zoom
} }
} }
pub fn calculate_offset_transform(&self, viewport_center: DVec2) -> DAffine2 { pub fn calculate_offset_transform(&self, viewport_center: DVec2, pan: DVec2, tilt: f64, zoom: f64) -> DAffine2 {
let scaled_centre = viewport_center / self.snapped_scale(); let scaled_centre = viewport_center / self.snapped_scale(zoom);
// Try to avoid fractional coordinates to reduce anti aliasing. // Try to avoid fractional coordinates to reduce anti aliasing.
let scale = self.snapped_scale(); let scale = self.snapped_scale(zoom);
let rounded_pan = ((self.pan + scaled_centre) * scale).round() / scale - scaled_centre; let rounded_pan = ((pan + scaled_centre) * scale).round() / scale - scaled_centre;
// TODO: replace with DAffine2::from_scale_angle_translation and fix the errors // TODO: replace with DAffine2::from_scale_angle_translation and fix the errors
let offset_transform = DAffine2::from_translation(scaled_centre); let offset_transform = DAffine2::from_translation(scaled_centre);
let scale_transform = DAffine2::from_scale(DVec2::splat(scale)); 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); let translation_transform = DAffine2::from_translation(rounded_pan);
scale_transform * offset_transform * angle_transform * translation_transform scale_transform * offset_transform * angle_transform * translation_transform
} }
fn create_document_transform(&self, viewport_center: DVec2, responses: &mut VecDeque<Message>) { fn create_document_transform(&self, viewport_center: DVec2, ptz: &PTZ, responses: &mut VecDeque<Message>) {
let transform = self.calculate_offset_transform(viewport_center); let transform = self.calculate_offset_transform(viewport_center, ptz.pan, ptz.tilt, ptz.zoom);
responses.add(DocumentMessage::UpdateDocumentTransform { transform }); 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 struct NodeGraphMessageHandler {
pub layer_path: Option<Vec<LayerId>>, pub layer_path: Option<Vec<LayerId>>,
pub network: Vec<NodeId>, pub network: Vec<NodeId>,
has_selection: bool, has_selection: bool,
#[serde(skip)]
pub widgets: [LayoutGroup; 2], 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::portfolio::utility_types::PersistentData;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Default)]
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PropertiesPanelMessageHandler; pub struct PropertiesPanelMessageHandler;
impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPanelMessageHandlerData<'a>)> for 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 graphene_core::raster::color::Color;
use glam::DVec2;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
@ -59,7 +60,7 @@ pub enum DocumentRenderMode<'a> {
LayerCutout(&'a [LayerId], Color), LayerCutout(&'a [LayerId], Color),
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug)]
/// SnappingState determines the current individual snapping states /// SnappingState determines the current individual snapping states
pub struct SnappingState { pub struct SnappingState {
pub snapping_enabled: bool, 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; tool_data.drag_current = mouse_position + closest_move;
if input.keyboard.key(duplicate) && tool_data.not_duplicated_layers.is_none() { // TODO: Reenable this feature after fixing it
tool_data.start_duplicates(document, responses); if false {
} else if !input.keyboard.key(duplicate) && tool_data.not_duplicated_layers.is_some() { if input.keyboard.key(duplicate) && tool_data.not_duplicated_layers.is_none() {
tool_data.stop_duplicates(document, responses); 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 SelectToolFsmState::Dragging
@ -625,7 +628,8 @@ impl Fsm for SelectToolFsmState {
(SelectToolFsmState::ResizingBounds, SelectToolMessage::PointerMove { axis_align, center, .. }) => { (SelectToolFsmState::ResizingBounds, SelectToolMessage::PointerMove { axis_align, center, .. }) => {
if let Some(bounds) = &mut tool_data.bounding_box_manager { if let Some(bounds) = &mut tool_data.bounding_box_manager {
if let Some(movement) = &mut bounds.selected_edges { 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; let mouse_position = input.mouse.position;

View File

@ -1,11 +1,14 @@
use crate::document::value::TaggedValue; use crate::document::value::TaggedValue;
use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput}; use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput};
use graphene_core::{GraphicGroup, ProtoNodeIdentifier, Type};
use dyn_any::{DynAny, StaticType}; use dyn_any::{DynAny, StaticType};
use glam::IVec2;
pub use graphene_core::uuid::generate_uuid; 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::collections::{HashMap, HashSet};
use std::hash::{Hash, Hasher};
pub mod value; 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. /// 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. /// 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 { fn merge_ids(a: u64, b: u64) -> u64 {
use std::hash::{Hash, Hasher}; let mut hasher = DefaultHasher::new();
let mut hasher = std::collections::hash_map::DefaultHasher::new();
a.hash(&mut hasher); a.hash(&mut hasher);
b.hash(&mut hasher); b.hash(&mut hasher);
hasher.finish() hasher.finish()
@ -448,6 +450,12 @@ impl std::hash::Hash for NodeNetwork {
/// Graph modification functions /// Graph modification functions
impl NodeNetwork { 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 /// Get the original output nodes of this network, ignoring any preview node
pub fn original_outputs(&self) -> &Vec<NodeOutput> { pub fn original_outputs(&self) -> &Vec<NodeOutput> {
self.previous_outputs.as_ref().unwrap_or(&self.outputs) self.previous_outputs.as_ref().unwrap_or(&self.outputs)