diff --git a/editor/src/dispatcher.rs b/editor/src/dispatcher.rs index 94aa76f7bf..ba970c451d 100644 --- a/editor/src/dispatcher.rs +++ b/editor/src/dispatcher.rs @@ -26,6 +26,7 @@ pub struct DispatcherMessageHandlers { pub portfolio_message_handler: PortfolioMessageHandler, preferences_message_handler: PreferencesMessageHandler, tool_message_handler: ToolMessageHandler, + viewport_message_handler: ViewportMessageHandler, } impl DispatcherMessageHandlers { @@ -146,7 +147,6 @@ impl Dispatcher { let context = DialogMessageContext { portfolio: &self.message_handlers.portfolio_message_handler, preferences: &self.message_handlers.preferences_message_handler, - viewport_bounds: &self.message_handlers.input_preprocessor_message_handler.viewport_bounds, }; self.message_handlers.dialog_message_handler.process_message(message, &mut queue, context); } @@ -169,9 +169,14 @@ impl Dispatcher { Message::InputPreprocessor(message) => { let keyboard_platform = GLOBAL_PLATFORM.get().copied().unwrap_or_default().as_keyboard_platform_layout(); - self.message_handlers - .input_preprocessor_message_handler - .process_message(message, &mut queue, InputPreprocessorMessageContext { keyboard_platform }); + self.message_handlers.input_preprocessor_message_handler.process_message( + message, + &mut queue, + InputPreprocessorMessageContext { + keyboard_platform, + viewport: &self.message_handlers.viewport_message_handler, + }, + ); } Message::KeyMapping(message) => { let input = &self.message_handlers.input_preprocessor_message_handler; @@ -195,6 +200,7 @@ impl Dispatcher { let reset_node_definitions_on_open = self.message_handlers.portfolio_message_handler.reset_node_definitions_on_open; let timing_information = self.message_handlers.animation_message_handler.timing_information(); let animation = &self.message_handlers.animation_message_handler; + let viewport = &self.message_handlers.viewport_message_handler; self.message_handlers.portfolio_message_handler.process_message( message, @@ -207,6 +213,7 @@ impl Dispatcher { reset_node_definitions_on_open, timing_information, animation, + viewport, }, ); } @@ -230,10 +237,14 @@ impl Dispatcher { persistent_data: &self.message_handlers.portfolio_message_handler.persistent_data, node_graph: &self.message_handlers.portfolio_message_handler.executor, preferences: &self.message_handlers.preferences_message_handler, + viewport: &self.message_handlers.viewport_message_handler, }; self.message_handlers.tool_message_handler.process_message(message, &mut queue, context); } + Message::Viewport(message) => { + self.message_handlers.viewport_message_handler.process_message(message, &mut queue, ()); + } Message::NoOp => {} Message::Batched { messages } => { messages.into_iter().for_each(|message| self.handle_message(message, false)); diff --git a/editor/src/messages/dialog/dialog_message_handler.rs b/editor/src/messages/dialog/dialog_message_handler.rs index 42f17a22e2..569f80dd01 100644 --- a/editor/src/messages/dialog/dialog_message_handler.rs +++ b/editor/src/messages/dialog/dialog_message_handler.rs @@ -1,14 +1,11 @@ -use super::new_document_dialog::NewDocumentDialogMessageContext; use super::simple_dialogs::{self, AboutGraphiteDialog, ComingSoonDialog, DemoArtworkDialog, LicensesDialog}; use crate::messages::dialog::simple_dialogs::LicensesThirdPartyDialog; -use crate::messages::input_mapper::utility_types::input_mouse::ViewportBounds; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::prelude::*; #[derive(ExtractField)] pub struct DialogMessageContext<'a> { pub portfolio: &'a PortfolioMessageHandler, - pub viewport_bounds: &'a ViewportBounds, pub preferences: &'a PreferencesMessageHandler, } @@ -23,15 +20,11 @@ pub struct DialogMessageHandler { #[message_handler_data] impl MessageHandler> for DialogMessageHandler { fn process_message(&mut self, message: DialogMessage, responses: &mut VecDeque, context: DialogMessageContext) { - let DialogMessageContext { - portfolio, - preferences, - viewport_bounds, - } = context; + let DialogMessageContext { portfolio, preferences } = context; match message { DialogMessage::ExportDialog(message) => self.export_dialog.process_message(message, responses, ExportDialogMessageContext { portfolio }), - DialogMessage::NewDocumentDialog(message) => self.new_document_dialog.process_message(message, responses, NewDocumentDialogMessageContext { viewport_bounds }), + DialogMessage::NewDocumentDialog(message) => self.new_document_dialog.process_message(message, responses, ()), DialogMessage::PreferencesDialog(message) => self.preferences_dialog.process_message(message, responses, PreferencesDialogMessageContext { preferences }), DialogMessage::CloseAllDocumentsWithConfirmation => { diff --git a/editor/src/messages/dialog/new_document_dialog/mod.rs b/editor/src/messages/dialog/new_document_dialog/mod.rs index cf9f7455da..c179de74d1 100644 --- a/editor/src/messages/dialog/new_document_dialog/mod.rs +++ b/editor/src/messages/dialog/new_document_dialog/mod.rs @@ -4,4 +4,4 @@ mod new_document_dialog_message_handler; #[doc(inline)] pub use new_document_dialog_message::{NewDocumentDialogMessage, NewDocumentDialogMessageDiscriminant}; #[doc(inline)] -pub use new_document_dialog_message_handler::{NewDocumentDialogMessageContext, NewDocumentDialogMessageHandler}; +pub use new_document_dialog_message_handler::NewDocumentDialogMessageHandler; diff --git a/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs b/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs index 45fb6772d7..2b9308ed7d 100644 --- a/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs +++ b/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs @@ -1,13 +1,8 @@ +use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::prelude::*; -use crate::messages::{input_mapper::utility_types::input_mouse::ViewportBounds, layout::utility_types::widget_prelude::*}; use glam::{IVec2, UVec2}; use graph_craft::document::NodeId; -#[derive(ExtractField)] -pub struct NewDocumentDialogMessageContext<'a> { - pub viewport_bounds: &'a ViewportBounds, -} - /// A dialog to allow users to set some initial options about a new document. #[derive(Debug, Clone, Default, ExtractField)] pub struct NewDocumentDialogMessageHandler { @@ -17,8 +12,8 @@ pub struct NewDocumentDialogMessageHandler { } #[message_handler_data] -impl<'a> MessageHandler> for NewDocumentDialogMessageHandler { - fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque, context: NewDocumentDialogMessageContext<'a>) { +impl<'a> MessageHandler for NewDocumentDialogMessageHandler { + fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque, _: ()) { match message { NewDocumentDialogMessage::Name { name } => self.name = name, NewDocumentDialogMessage::Infinite { infinite } => self.infinite = infinite, @@ -35,12 +30,9 @@ impl<'a> MessageHandler Self { - Self { - top_left: DVec2::from_slice(&slice[0..2]), - bottom_right: DVec2::from_slice(&slice[2..4]), - } - } - - pub fn size(&self) -> DVec2 { - (self.bottom_right - self.top_left).ceil() - } - - pub fn center(&self) -> DVec2 { - (self.bottom_right - self.top_left).ceil() / 2. - } - - pub fn in_bounds(&self, position: ViewportPosition) -> bool { - position.x >= 0. && position.y >= 0. && position.x <= self.bottom_right.x && position.y <= self.bottom_right.y - } -} - -use std::hash::{Hash, Hasher}; - #[derive(Debug, Copy, Clone, Default, serde::Serialize, serde::Deserialize)] pub struct ScrollDelta { pub x: f64, @@ -113,9 +85,9 @@ impl EditorMouseState { } } - pub fn to_mouse_state(&self, active_viewport_bounds: &ViewportBounds) -> MouseState { + pub fn to_mouse_state(&self, viewport: &ViewportMessageHandler) -> MouseState { MouseState { - position: self.editor_position - active_viewport_bounds.top_left, + position: (viewport.logical(self.editor_position) - viewport.offset()).into(), mouse_keys: self.mouse_keys, scroll_delta: self.scroll_delta, } diff --git a/editor/src/messages/input_preprocessor/input_preprocessor_message.rs b/editor/src/messages/input_preprocessor/input_preprocessor_message.rs index dcfbac728a..db7932fdf4 100644 --- a/editor/src/messages/input_preprocessor/input_preprocessor_message.rs +++ b/editor/src/messages/input_preprocessor/input_preprocessor_message.rs @@ -1,11 +1,10 @@ use crate::messages::input_mapper::utility_types::input_keyboard::{Key, ModifierKeys}; -use crate::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, ViewportBounds}; +use crate::messages::input_mapper::utility_types::input_mouse::EditorMouseState; use crate::messages::prelude::*; #[impl_message(Message, InputPreprocessor)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] pub enum InputPreprocessorMessage { - BoundsOfViewports { bounds_of_viewports: Vec }, DoubleClick { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys }, KeyDown { key: Key, key_repeat: bool, modifier_keys: ModifierKeys }, KeyUp { key: Key, key_repeat: bool, modifier_keys: ModifierKeys }, diff --git a/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs b/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs index 52e20d1d7b..3464f0a07e 100644 --- a/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs +++ b/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs @@ -1,14 +1,14 @@ use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeyStates, ModifierKeys}; -use crate::messages::input_mapper::utility_types::input_mouse::{MouseButton, MouseKeys, MouseState, ViewportBounds}; +use crate::messages::input_mapper::utility_types::input_mouse::{MouseButton, MouseKeys, MouseState}; use crate::messages::input_mapper::utility_types::misc::FrameTimeInfo; use crate::messages::portfolio::utility_types::KeyboardPlatformLayout; use crate::messages::prelude::*; -use glam::DVec2; use std::time::Duration; #[derive(ExtractField)] -pub struct InputPreprocessorMessageContext { +pub struct InputPreprocessorMessageContext<'a> { pub keyboard_platform: KeyboardPlatformLayout, + pub viewport: &'a ViewportMessageHandler, } #[derive(Debug, Default, ExtractField)] @@ -17,38 +17,18 @@ pub struct InputPreprocessorMessageHandler { pub time: u64, pub keyboard: KeyStates, pub mouse: MouseState, - pub viewport_bounds: ViewportBounds, } #[message_handler_data] -impl MessageHandler for InputPreprocessorMessageHandler { - fn process_message(&mut self, message: InputPreprocessorMessage, responses: &mut VecDeque, context: InputPreprocessorMessageContext) { - let InputPreprocessorMessageContext { keyboard_platform } = context; +impl<'a> MessageHandler> for InputPreprocessorMessageHandler { + fn process_message(&mut self, message: InputPreprocessorMessage, responses: &mut VecDeque, context: InputPreprocessorMessageContext<'a>) { + let InputPreprocessorMessageContext { keyboard_platform, viewport } = context; match message { - InputPreprocessorMessage::BoundsOfViewports { bounds_of_viewports } => { - assert_eq!(bounds_of_viewports.len(), 1, "Only one viewport is currently supported"); - - for bounds in bounds_of_viewports { - // TODO: Extend this to multiple viewports instead of setting it to the value of this last loop iteration - self.viewport_bounds = bounds; - - responses.add(NavigationMessage::CanvasPan { delta: DVec2::ZERO }); - responses.add(NodeGraphMessage::SetGridAlignedEdges); - } - responses.add(DeferMessage::AfterGraphRun { - messages: vec![ - DeferMessage::AfterGraphRun { - messages: vec![DeferMessage::TriggerNavigationReady.into()], - } - .into(), - ], - }); - } InputPreprocessorMessage::DoubleClick { editor_mouse_state, modifier_keys } => { self.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses); - let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds); + let mouse_state = editor_mouse_state.to_mouse_state(viewport); self.mouse.position = mouse_state.position; for key in mouse_state.mouse_keys { @@ -81,7 +61,7 @@ impl MessageHandler f InputPreprocessorMessage::PointerDown { editor_mouse_state, modifier_keys } => { self.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses); - let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds); + let mouse_state = editor_mouse_state.to_mouse_state(viewport); self.mouse.position = mouse_state.position; self.translate_mouse_event(mouse_state, true, responses); @@ -89,7 +69,7 @@ impl MessageHandler f InputPreprocessorMessage::PointerMove { editor_mouse_state, modifier_keys } => { self.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses); - let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds); + let mouse_state = editor_mouse_state.to_mouse_state(viewport); self.mouse.position = mouse_state.position; responses.add(InputMapperMessage::PointerMove); @@ -100,7 +80,7 @@ impl MessageHandler f InputPreprocessorMessage::PointerUp { editor_mouse_state, modifier_keys } => { self.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses); - let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds); + let mouse_state = editor_mouse_state.to_mouse_state(viewport); self.mouse.position = mouse_state.position; self.translate_mouse_event(mouse_state, false, responses); @@ -108,7 +88,7 @@ impl MessageHandler f InputPreprocessorMessage::PointerShake { editor_mouse_state, modifier_keys } => { self.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses); - let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds); + let mouse_state = editor_mouse_state.to_mouse_state(viewport); self.mouse.position = mouse_state.position; responses.add(InputMapperMessage::PointerShake); @@ -121,7 +101,7 @@ impl MessageHandler f InputPreprocessorMessage::WheelScroll { editor_mouse_state, modifier_keys } => { self.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses); - let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds); + let mouse_state = editor_mouse_state.to_mouse_state(viewport); self.mouse.position = mouse_state.position; self.mouse.scroll_delta = mouse_state.scroll_delta; @@ -202,11 +182,6 @@ impl InputPreprocessorMessageHandler { responses.add(InputMapperMessage::KeyDown(key)); } } - - pub fn document_bounds(&self) -> [DVec2; 2] { - // IPP bounds are relative to the entire application - [(0., 0.).into(), self.viewport_bounds.bottom_right - self.viewport_bounds.top_left] - } } #[cfg(test)] @@ -232,6 +207,7 @@ mod test { let context = InputPreprocessorMessageContext { keyboard_platform: KeyboardPlatformLayout::Standard, + viewport: &ViewportMessageHandler::default(), }; input_preprocessor.process_message(message, &mut responses, context); @@ -251,6 +227,7 @@ mod test { let context = InputPreprocessorMessageContext { keyboard_platform: KeyboardPlatformLayout::Standard, + viewport: &ViewportMessageHandler::default(), }; input_preprocessor.process_message(message, &mut responses, context); @@ -270,6 +247,7 @@ mod test { let context = InputPreprocessorMessageContext { keyboard_platform: KeyboardPlatformLayout::Standard, + viewport: &ViewportMessageHandler::default(), }; input_preprocessor.process_message(message, &mut responses, context); @@ -291,6 +269,7 @@ mod test { let context = InputPreprocessorMessageContext { keyboard_platform: KeyboardPlatformLayout::Standard, + viewport: &ViewportMessageHandler::default(), }; input_preprocessor.process_message(message, &mut responses, context); @@ -311,6 +290,7 @@ mod test { let context = InputPreprocessorMessageContext { keyboard_platform: KeyboardPlatformLayout::Standard, + viewport: &ViewportMessageHandler::default(), }; input_preprocessor.process_message(message, &mut responses, context); diff --git a/editor/src/messages/message.rs b/editor/src/messages/message.rs index b23d530e29..22e6eff0ca 100644 --- a/editor/src/messages/message.rs +++ b/editor/src/messages/message.rs @@ -33,6 +33,8 @@ pub enum Message { Preferences(PreferencesMessage), #[child] Tool(ToolMessage), + #[child] + Viewport(ViewportMessage), // Messages Batched { diff --git a/editor/src/messages/mod.rs b/editor/src/messages/mod.rs index 6b2656df84..ff662a1a7d 100644 --- a/editor/src/messages/mod.rs +++ b/editor/src/messages/mod.rs @@ -16,3 +16,4 @@ pub mod portfolio; pub mod preferences; pub mod prelude; pub mod tool; +pub mod viewport; diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 222913f238..d9a6905997 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -52,10 +52,10 @@ pub struct DocumentMessageContext<'a> { pub executor: &'a mut NodeGraphExecutor, pub current_tool: &'a ToolType, pub preferences: &'a PreferencesMessageHandler, - pub device_pixel_ratio: f64, pub data_panel_open: bool, pub layers_panel_open: bool, pub properties_panel_open: bool, + pub viewport: &'a ViewportMessageHandler, } #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ExtractField)] @@ -195,9 +195,9 @@ impl MessageHandler> for DocumentMes ipp, persistent_data, executor, + viewport, current_tool, preferences, - device_pixel_ratio, data_panel_open, layers_panel_open, properties_panel_open, @@ -213,6 +213,7 @@ impl MessageHandler> for DocumentMes document_ptz: &mut self.document_ptz, graph_view_overlay_open: self.graph_view_overlay_open, preferences, + viewport, }; self.navigation_handler.process_message(message, responses, context); @@ -221,15 +222,8 @@ impl MessageHandler> for DocumentMes let visibility_settings = self.overlays_visibility_settings; // Send the overlays message to the overlays message handler - self.overlays_message_handler.process_message( - message, - responses, - OverlaysMessageContext { - visibility_settings, - ipp, - device_pixel_ratio, - }, - ); + self.overlays_message_handler + .process_message(message, responses, OverlaysMessageContext { visibility_settings, viewport }); } DocumentMessage::PropertiesPanel(message) => { let context = PropertiesPanelMessageContext { @@ -268,6 +262,7 @@ impl MessageHandler> for DocumentMes navigation_handler: &self.navigation_handler, preferences, layers_panel_open, + viewport, }, ); } @@ -380,8 +375,8 @@ impl MessageHandler> for DocumentMes responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] }); self.layer_range_selection_reference = None; } - DocumentMessage::DocumentHistoryBackward => self.undo_with_history(ipp, responses), - DocumentMessage::DocumentHistoryForward => self.redo_with_history(ipp, responses), + DocumentMessage::DocumentHistoryBackward => self.undo_with_history(viewport, responses), + DocumentMessage::DocumentHistoryForward => self.redo_with_history(viewport, responses), DocumentMessage::DocumentStructureChanged => { if layers_panel_open { self.network_interface.load_structure(); @@ -836,7 +831,7 @@ impl MessageHandler> for DocumentMes let scale = DAffine2::from_scale(enlargement_factor); let pivot = DAffine2::from_translation(pivot); let transformation = pivot * scale * pivot.inverse(); - let document_to_viewport = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz); + let document_to_viewport = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz); for layer in self.network_interface.shallowest_unique_layers(&[]).filter(|layer| can_move(*layer)) { let to = document_to_viewport.inverse() * self.metadata().downstream_transform_to_viewport(layer); @@ -861,10 +856,10 @@ impl MessageHandler> for DocumentMes let image_size = DVec2::new(image.width as f64, image.height as f64); // Align the layer with the mouse or center of viewport - let viewport_location = mouse.map_or(ipp.viewport_bounds.center() + ipp.viewport_bounds.top_left, |pos| pos.into()); + let viewport_location = mouse.map_or(viewport.center_in_viewport_space().into_dvec2() + viewport.offset().into_dvec2(), |pos| pos.into()); - let document_to_viewport = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz); - let center_in_viewport = DAffine2::from_translation(document_to_viewport.inverse().transform_point2(viewport_location - ipp.viewport_bounds.top_left)); + let document_to_viewport = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz); + let center_in_viewport = DAffine2::from_translation(document_to_viewport.inverse().transform_point2(viewport_location - viewport.offset().into_dvec2())); let center_in_viewport_layerspace = center_in_viewport; // Make layer the size of the image @@ -913,9 +908,9 @@ impl MessageHandler> for DocumentMes mouse, parent_and_insert_index, } => { - let document_to_viewport = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz); - let viewport_location = mouse.map_or(ipp.viewport_bounds.center() + ipp.viewport_bounds.top_left, |pos| pos.into()); - let center_in_viewport = DAffine2::from_translation(document_to_viewport.inverse().transform_point2(viewport_location - ipp.viewport_bounds.top_left)); + let document_to_viewport = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz); + let viewport_location = mouse.map_or(viewport.center_in_viewport_space().into_dvec2() + viewport.offset().into_dvec2(), |pos| pos.into()); + let center_in_viewport = DAffine2::from_translation(document_to_viewport.inverse().transform_point2(viewport_location - viewport.offset().into_dvec2())); let layer_node_id = NodeId::new(); let layer_id = LayerNodeIdentifier::new_unchecked(layer_node_id); @@ -970,7 +965,7 @@ impl MessageHandler> for DocumentMes } else { &self.document_ptz }; - let document_to_viewport = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), current_ptz); + let document_to_viewport = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), current_ptz); let ruler_scale = if !self.graph_view_overlay_open { self.navigation_handler.snapped_zoom(current_ptz.zoom()) @@ -1006,12 +1001,12 @@ impl MessageHandler> for DocumentMes }); } DocumentMessage::RenderScrollbars => { - let document_transform_scale = self.navigation_handler.snapped_zoom(self.document_ptz.zoom()); + let document_transform_scale = self.navigation_handler.snapped_zoom(self.document_ptz.zoom()) / viewport.scale(); let scale = 0.5 + ASYMPTOTIC_EFFECT + document_transform_scale * SCALE_EFFECT; - let viewport_size = ipp.viewport_bounds.size(); - let viewport_mid = ipp.viewport_bounds.center(); + let viewport_size = viewport.size().into_dvec2(); + let viewport_mid = viewport.center_in_viewport_space().into_dvec2(); let [bounds1, bounds2] = if !self.graph_view_overlay_open { self.metadata().document_bounds_viewport_space().unwrap_or([viewport_mid; 2]) } else { @@ -1333,7 +1328,7 @@ impl MessageHandler> for DocumentMes } for _ in 0..undo_count { - self.undo(ipp, responses); + self.undo(viewport, responses); } self.network_interface.finish_transaction(); @@ -1483,7 +1478,7 @@ impl MessageHandler> for DocumentMes } DocumentMessage::PTZUpdate => { if !self.graph_view_overlay_open { - let transform = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz); + let transform = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz); self.network_interface.set_document_to_viewport_transform(transform); // Ensure selection box is kept in sync with the pointer when the PTZ changes responses.add(SelectToolMessage::PointerMove { @@ -1502,7 +1497,7 @@ impl MessageHandler> for DocumentMes let transform = self .navigation_handler - .calculate_offset_transform(ipp.viewport_bounds.center(), &network_metadata.persistent_metadata.navigation_metadata.node_graph_ptz); + .calculate_offset_transform(viewport.center_in_viewport_space().into(), &network_metadata.persistent_metadata.navigation_metadata.node_graph_ptz); self.network_interface.set_transform(transform, &self.breadcrumb_network_path); responses.add(DocumentMessage::RenderRulers); @@ -1656,29 +1651,30 @@ impl MessageHandler> for DocumentMes impl DocumentMessageHandler { /// Runs an intersection test with all layers and a viewport space quad - pub fn intersect_quad<'a>(&'a self, viewport_quad: graphene_std::renderer::Quad, ipp: &InputPreprocessorMessageHandler) -> impl Iterator + use<'a> { - let document_to_viewport = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz); + pub fn intersect_quad<'a>(&'a self, viewport_quad: graphene_std::renderer::Quad, viewport: &ViewportMessageHandler) -> impl Iterator + use<'a> { + let document_to_viewport = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz); let document_quad = document_to_viewport.inverse() * viewport_quad; ClickXRayIter::new(&self.network_interface, XRayTarget::Quad(document_quad)) } /// Runs an intersection test with all layers and a viewport space quad; ignoring artboards - pub fn intersect_quad_no_artboards<'a>(&'a self, viewport_quad: graphene_std::renderer::Quad, ipp: &InputPreprocessorMessageHandler) -> impl Iterator + use<'a> { - self.intersect_quad(viewport_quad, ipp).filter(|layer| !self.network_interface.is_artboard(&layer.to_node(), &[])) + pub fn intersect_quad_no_artboards<'a>(&'a self, viewport_quad: graphene_std::renderer::Quad, viewport: &ViewportMessageHandler) -> impl Iterator + use<'a> { + self.intersect_quad(viewport_quad, viewport).filter(|layer| !self.network_interface.is_artboard(&layer.to_node(), &[])) } /// Runs an intersection test with all layers and a viewport space subpath - pub fn intersect_polygon<'a>(&'a self, mut viewport_polygon: Subpath, ipp: &InputPreprocessorMessageHandler) -> impl Iterator + use<'a> { - let document_to_viewport = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz); + pub fn intersect_polygon<'a>(&'a self, mut viewport_polygon: Subpath, viewport: &ViewportMessageHandler) -> impl Iterator + use<'a> { + let document_to_viewport = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz); viewport_polygon.apply_transform(document_to_viewport.inverse()); ClickXRayIter::new(&self.network_interface, XRayTarget::Polygon(viewport_polygon)) } /// Runs an intersection test with all layers and a viewport space subpath; ignoring artboards - pub fn intersect_polygon_no_artboards<'a>(&'a self, viewport_polygon: Subpath, ipp: &InputPreprocessorMessageHandler) -> impl Iterator + use<'a> { - self.intersect_polygon(viewport_polygon, ipp).filter(|layer| !self.network_interface.is_artboard(&layer.to_node(), &[])) + pub fn intersect_polygon_no_artboards<'a>(&'a self, viewport_polygon: Subpath, viewport: &ViewportMessageHandler) -> impl Iterator + use<'a> { + self.intersect_polygon(viewport_polygon, viewport) + .filter(|layer| !self.network_interface.is_artboard(&layer.to_node(), &[])) } pub fn is_layer_fully_inside(&self, layer: &LayerNodeIdentifier, quad: graphene_std::renderer::Quad) -> bool { @@ -1704,8 +1700,8 @@ impl DocumentMessageHandler { layer_left >= quad_left && layer_right <= quad_right && layer_top <= quad_top && layer_bottom >= quad_bottom } - pub fn is_layer_fully_inside_polygon(&self, layer: &LayerNodeIdentifier, ipp: &InputPreprocessorMessageHandler, mut viewport_polygon: Subpath) -> bool { - let document_to_viewport = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz); + pub fn is_layer_fully_inside_polygon(&self, layer: &LayerNodeIdentifier, viewport: &ViewportMessageHandler, mut viewport_polygon: Subpath) -> bool { + let document_to_viewport = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz); viewport_polygon.apply_transform(document_to_viewport.inverse()); let layer_click_targets = self.network_interface.document_metadata().click_targets(*layer); @@ -1728,8 +1724,8 @@ impl DocumentMessageHandler { } /// Find all of the layers that were clicked on from a viewport space location - pub fn click_xray(&self, ipp: &InputPreprocessorMessageHandler) -> impl Iterator + use<'_> { - let document_to_viewport = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz); + pub fn click_xray(&self, ipp: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) -> impl Iterator + use<'_> { + let document_to_viewport = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz); let point = document_to_viewport.inverse().transform_point2(ipp.mouse.position); ClickXRayIter::new(&self.network_interface, XRayTarget::Point(point)) } @@ -1750,8 +1746,8 @@ impl DocumentMessageHandler { } /// Find layers under the location in viewport space that was clicked, listed by their depth in the layer tree hierarchy. - pub fn click_list<'a>(&'a self, ipp: &InputPreprocessorMessageHandler) -> impl Iterator + use<'a> { - self.click_xray(ipp) + pub fn click_list<'a>(&'a self, ipp: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) -> impl Iterator + use<'a> { + self.click_xray(ipp, viewport) .filter(move |&layer| !self.network_interface.is_artboard(&layer.to_node(), &[])) .skip_while(|&layer| layer == LayerNodeIdentifier::ROOT_PARENT) .scan(true, |last_had_children, layer| { @@ -1765,8 +1761,8 @@ impl DocumentMessageHandler { } /// Find layers (including artboards) under the location in viewport space that was clicked, listed by their depth in the layer tree hierarchy. - pub fn click_list_with_artboards<'a>(&'a self, ipp: &InputPreprocessorMessageHandler) -> impl Iterator + use<'a> { - self.click_xray(ipp) + pub fn click_list_with_artboards<'a>(&'a self, ipp: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) -> impl Iterator + use<'a> { + self.click_xray(ipp, viewport) .skip_while(|&layer| layer == LayerNodeIdentifier::ROOT_PARENT) .scan(true, |last_had_children, layer| { if *last_had_children { @@ -1778,14 +1774,14 @@ impl DocumentMessageHandler { }) } - pub fn click_list_no_parents<'a>(&'a self, ipp: &InputPreprocessorMessageHandler) -> impl Iterator + use<'a> { - self.click_xray(ipp) + pub fn click_list_no_parents<'a>(&'a self, ipp: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) -> impl Iterator + use<'a> { + self.click_xray(ipp, viewport) .filter(move |&layer| !self.network_interface.is_artboard(&layer.to_node(), &[]) && !layer.has_children(self.network_interface.document_metadata())) } /// Find the deepest layer that has been clicked on from a location in viewport space. - pub fn click(&self, ipp: &InputPreprocessorMessageHandler) -> Option { - self.click_list(ipp).last() + pub fn click(&self, ipp: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) -> Option { + self.click_list(ipp, viewport).last() } pub fn click_based_on_position(&self, mouse_snapped_positon: DVec2) -> Option { @@ -1943,8 +1939,8 @@ impl DocumentMessageHandler { structure_section.as_slice().into() } - pub fn undo_with_history(&mut self, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { - let Some(previous_network) = self.undo(ipp, responses) else { return }; + pub fn undo_with_history(&mut self, viewport: &ViewportMessageHandler, responses: &mut VecDeque) { + let Some(previous_network) = self.undo(viewport, responses) else { return }; self.document_redo_history.push_back(previous_network); if self.document_redo_history.len() > crate::consts::MAX_UNDO_HISTORY_LEN { @@ -1952,7 +1948,7 @@ impl DocumentMessageHandler { } } - pub fn undo(&mut self, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque) -> Option { + pub fn undo(&mut self, viewport: &ViewportMessageHandler, responses: &mut VecDeque) -> Option { // If there is no history return and don't broadcast SelectionChanged let mut network_interface = self.document_undo_history.pop_back()?; @@ -1961,7 +1957,7 @@ impl DocumentMessageHandler { std::mem::swap(&mut network_interface.resolved_types, &mut self.network_interface.resolved_types); //Update the metadata transform based on document PTZ - let transform = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz); + let transform = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz); network_interface.set_document_to_viewport_transform(transform); // Ensure document structure is loaded so that updating the selected nodes has the correct metadata @@ -1980,9 +1976,9 @@ impl DocumentMessageHandler { Some(previous_network) } - pub fn redo_with_history(&mut self, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { + pub fn redo_with_history(&mut self, viewport: &ViewportMessageHandler, responses: &mut VecDeque) { // Push the UpdateOpenDocumentsList message to the queue in order to update the save status of the open documents - let Some(previous_network) = self.redo(ipp, responses) else { return }; + let Some(previous_network) = self.redo(viewport, responses) else { return }; self.document_undo_history.push_back(previous_network); if self.document_undo_history.len() > crate::consts::MAX_UNDO_HISTORY_LEN { @@ -1990,7 +1986,7 @@ impl DocumentMessageHandler { } } - pub fn redo(&mut self, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque) -> Option { + pub fn redo(&mut self, viewport: &ViewportMessageHandler, responses: &mut VecDeque) -> Option { // If there is no history return and don't broadcast SelectionChanged let mut network_interface = self.document_redo_history.pop_back()?; @@ -1999,7 +1995,7 @@ impl DocumentMessageHandler { std::mem::swap(&mut network_interface.resolved_types, &mut self.network_interface.resolved_types); //Update the metadata transform based on document PTZ - let transform = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz); + let transform = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz); network_interface.set_document_to_viewport_transform(transform); let previous_network = std::mem::replace(&mut self.network_interface, network_interface); @@ -2045,11 +2041,11 @@ impl DocumentMessageHandler { } /// Finds the artboard that bounds the point in viewport space and be the container of any newly added layers. - pub fn new_layer_bounding_artboard(&self, ipp: &InputPreprocessorMessageHandler) -> LayerNodeIdentifier { + pub fn new_layer_bounding_artboard(&self, ipp: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) -> LayerNodeIdentifier { let container_based_on_selection = self.new_layer_parent(true); let container_based_on_clicked_artboard = self - .click_xray(ipp) + .click_xray(ipp, viewport) .find(|layer| self.network_interface.is_artboard(&layer.to_node(), &[])) .unwrap_or(LayerNodeIdentifier::ROOT_PARENT); diff --git a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs index 259b60e995..ec1ce9aab5 100644 --- a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs +++ b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs @@ -21,6 +21,7 @@ pub struct NavigationMessageContext<'a> { pub document_ptz: &'a mut PTZ, pub graph_view_overlay_open: bool, pub preferences: &'a PreferencesMessageHandler, + pub viewport: &'a ViewportMessageHandler, } #[derive(Debug, Clone, PartialEq, Default, ExtractField)] @@ -41,6 +42,7 @@ impl MessageHandler> for Navigat document_ptz, graph_view_overlay_open, preferences, + viewport, } = context; fn get_ptz<'a>(document_ptz: &'a PTZ, network_interface: &'a NodeNetworkInterface, graph_view_overlay_open: bool, breadcrumb_network_path: &[NodeId]) -> Option<&'a PTZ> { @@ -135,7 +137,7 @@ impl MessageHandler> for Navigat log::error!("Could not get PTZ in CanvasPan"); return; }; - let document_to_viewport = self.calculate_offset_transform(ipp.viewport_bounds.center(), ptz); + let document_to_viewport = self.calculate_offset_transform(viewport.center_in_viewport_space().into_dvec2(), ptz); let transformed_delta = document_to_viewport.inverse().transform_vector2(delta); ptz.pan += transformed_delta; @@ -169,8 +171,8 @@ impl MessageHandler> for Navigat log::error!("Could not get node graph PTZ in CanvasPanByViewportFraction"); return; }; - let document_to_viewport = self.calculate_offset_transform(ipp.viewport_bounds.center(), ptz); - let transformed_delta = document_to_viewport.inverse().transform_vector2(delta * ipp.viewport_bounds.size()); + let document_to_viewport = self.calculate_offset_transform(viewport.center_in_viewport_space().into_dvec2(), ptz); + let transformed_delta = document_to_viewport.inverse().transform_vector2(delta * viewport.size().into_dvec2()); ptz.pan += transformed_delta; responses.add(DocumentMessage::PTZUpdate); @@ -214,7 +216,7 @@ impl MessageHandler> for Navigat 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 / ptz.zoom(), ipp.mouse.position)); + responses.add(self.center_zoom(viewport.size().into(), new_scale / ptz.zoom(), ipp.mouse.position)); } responses.add(NavigationMessage::CanvasZoomSet { zoom_factor: new_scale }); } @@ -225,7 +227,7 @@ impl MessageHandler> for Navigat 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 / ptz.zoom(), ipp.mouse.position)); + responses.add(self.center_zoom(viewport.size().into(), new_scale / ptz.zoom(), ipp.mouse.position)); } responses.add(NavigationMessage::CanvasZoomSet { zoom_factor: new_scale }); } @@ -245,9 +247,9 @@ impl MessageHandler> for Navigat return; }; - zoom_factor *= Self::clamp_zoom(ptz.zoom() * zoom_factor, document_bounds, old_zoom, ipp); + zoom_factor *= Self::clamp_zoom(ptz.zoom() * zoom_factor, document_bounds, old_zoom, viewport); - responses.add(self.center_zoom(ipp.viewport_bounds.size(), zoom_factor, ipp.mouse.position)); + responses.add(self.center_zoom(viewport.size().into(), zoom_factor, ipp.mouse.position)); responses.add(NavigationMessage::CanvasZoomSet { zoom_factor: ptz.zoom() * zoom_factor, }); @@ -264,7 +266,7 @@ impl MessageHandler> for Navigat return; }; let zoom = zoom_factor.clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX); - let zoom = zoom * Self::clamp_zoom(zoom, document_bounds, old_zoom, ipp); + let zoom = zoom * Self::clamp_zoom(zoom, document_bounds, old_zoom, viewport); ptz.set_zoom(zoom); if graph_view_overlay_open { responses.add(NodeGraphMessage::UpdateGraphBarRight); @@ -343,7 +345,7 @@ impl MessageHandler> for Navigat let (pos1, pos2) = (pos1.min(pos2), pos1.max(pos2)); let diagonal = pos2 - pos1; - if diagonal.length() < f64::EPSILON * 1000. || ipp.viewport_bounds.size() == DVec2::ZERO { + if diagonal.length() < f64::EPSILON * 1000. || viewport.size().into_dvec2() == DVec2::ZERO { warn!("Cannot center since the viewport size is 0"); return; } @@ -352,10 +354,10 @@ impl MessageHandler> for Navigat log::error!("Could not get node graph PTZ in CanvasPanByViewportFraction"); return; }; - let document_to_viewport = self.calculate_offset_transform(ipp.viewport_bounds.center(), ptz); + let document_to_viewport = self.calculate_offset_transform(viewport.center_in_viewport_space().into_dvec2(), ptz); let v1 = document_to_viewport.inverse().transform_point2(DVec2::ZERO); - let v2 = document_to_viewport.inverse().transform_point2(ipp.viewport_bounds.size()); + let v2 = document_to_viewport.inverse().transform_point2(viewport.size().into_dvec2()); let center = ((v2 + v1) - (pos2 + pos1)) / 2.; let size = (v2 - v1) / diagonal; @@ -397,7 +399,8 @@ impl MessageHandler> for Navigat log::error!("Could not get node graph PTZ in FitViewportToSelection"); return; }; - let document_to_viewport = self.calculate_offset_transform(ipp.viewport_bounds.center(), ptz); + + let document_to_viewport = self.calculate_offset_transform(viewport.center_in_viewport_space().into_dvec2(), ptz); responses.add(NavigationMessage::FitViewportToBounds { bounds: [document_to_viewport.inverse().transform_point2(bounds[0]), document_to_viewport.inverse().transform_point2(bounds[1])], prevent_zoom_past_100: false, @@ -419,7 +422,7 @@ impl MessageHandler> for Navigat let tilt_raw_not_snapped = { // Compute the angle in document space to counter for the canvas being flipped let viewport_to_document = network_interface.document_metadata().document_to_viewport.inverse(); - let half_viewport = ipp.viewport_bounds.size() / 2.; + let half_viewport = viewport.center_in_viewport_space().into_dvec2(); let start_offset = viewport_to_document.transform_vector2(self.mouse_position - half_viewport); let end_offset = viewport_to_document.transform_vector2(ipp.mouse.position - half_viewport); let angle = start_offset.angle_to(end_offset); @@ -459,7 +462,7 @@ impl MessageHandler> for Navigat network_interface.graph_bounds_viewport_space(breadcrumb_network_path) }; - updated_zoom * Self::clamp_zoom(updated_zoom, document_bounds, old_zoom, ipp) + updated_zoom * Self::clamp_zoom(updated_zoom, document_bounds, old_zoom, viewport) }; let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else { log::error!("Could not get mutable PTZ in Zoom"); @@ -563,9 +566,9 @@ impl NavigationMessageHandler { NavigationMessage::CanvasPan { delta }.into() } - pub fn clamp_zoom(zoom: f64, document_bounds: Option<[DVec2; 2]>, old_zoom: f64, ipp: &InputPreprocessorMessageHandler) -> f64 { + pub fn clamp_zoom(zoom: f64, document_bounds: Option<[DVec2; 2]>, old_zoom: f64, viewport: &ViewportMessageHandler) -> f64 { let document_size = (document_bounds.map(|[min, max]| max - min).unwrap_or_default() / old_zoom) * zoom; - let scale_factor = (document_size / ipp.viewport_bounds.size()).max_element(); + let scale_factor = (document_size / viewport.size().into_dvec2()).max_element(); if scale_factor <= f64::EPSILON * 100. || !scale_factor.is_finite() || scale_factor >= VIEWPORT_ZOOM_MIN_FRACTION_COVER { return 1.; diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index e8fbc4342f..9fd08765cd 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -20,6 +20,7 @@ use crate::messages::tool::common_functionality::graph_modification_utils::{self use crate::messages::tool::common_functionality::utility_functions::make_path_editable_is_allowed; use crate::messages::tool::tool_messages::tool_prelude::{Key, MouseMotion}; use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; +use crate::messages::viewport::{Position, Rect}; use glam::{DAffine2, DVec2, IVec2}; use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput}; use graph_craft::proto::GraphErrors; @@ -43,6 +44,7 @@ pub struct NodeGraphMessageContext<'a> { pub navigation_handler: &'a NavigationMessageHandler, pub preferences: &'a PreferencesMessageHandler, pub layers_panel_open: bool, + pub viewport: &'a ViewportMessageHandler, } #[derive(Debug, Clone, ExtractField)] @@ -113,6 +115,7 @@ impl<'a> MessageHandler> for NodeG navigation_handler, preferences, layers_panel_open, + viewport, } = context; match message { @@ -783,12 +786,12 @@ impl<'a> MessageHandler> for NodeG // TODO: Create function let node_graph_shift = if matches!(context_menu_data, ContextMenuData::CreateNode { compatible_type: None }) { - let appear_right_of_mouse = if click.x > ipp.viewport_bounds.size().x - 180. { -180. } else { 0. }; - let appear_above_mouse = if click.y > ipp.viewport_bounds.size().y - 200. { -200. } else { 0. }; + let appear_right_of_mouse = if click.x > viewport.size().x() - 180. { -180. } else { 0. }; + let appear_above_mouse = if click.y > viewport.size().y() - 200. { -200. } else { 0. }; DVec2::new(appear_right_of_mouse, appear_above_mouse) / network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.matrix2.x_axis.x } else { - let appear_right_of_mouse = if click.x > ipp.viewport_bounds.size().x - 173. { -173. } else { 0. }; - let appear_above_mouse = if click.y > ipp.viewport_bounds.size().y - 34. { -34. } else { 0. }; + let appear_right_of_mouse = if click.x > viewport.size().x() - 173. { -173. } else { 0. }; + let appear_above_mouse = if click.y > viewport.size().y() - 34. { -34. } else { 0. }; DVec2::new(appear_right_of_mouse, appear_above_mouse) / network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.matrix2.x_axis.x }; @@ -983,7 +986,7 @@ impl<'a> MessageHandler> for NodeG // Auto-panning let messages = [NodeGraphMessage::PointerOutsideViewport { shift }.into(), NodeGraphMessage::PointerMove { shift }.into()]; - self.auto_panning.setup_by_mouse_position(ipp, &messages, responses); + self.auto_panning.setup_by_mouse_position(ipp, viewport, &messages, responses); let viewport_location = ipp.mouse.position; let point = network_metadata @@ -1218,8 +1221,8 @@ impl<'a> MessageHandler> for NodeG _ => Some(format!("type:{}", output_type.nested_type())), }; - let appear_right_of_mouse = if ipp.mouse.position.x > ipp.viewport_bounds.size().x - 173. { -173. } else { 0. }; - let appear_above_mouse = if ipp.mouse.position.y > ipp.viewport_bounds.size().y - 34. { -34. } else { 0. }; + let appear_right_of_mouse = if ipp.mouse.position.x > viewport.size().y() - 173. { -173. } else { 0. }; + let appear_above_mouse = if ipp.mouse.position.y > viewport.size().y() - 34. { -34. } else { 0. }; let node_graph_shift = DVec2::new(appear_right_of_mouse, appear_above_mouse) / network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.matrix2.x_axis.x; self.context_menu = Some(ContextMenuInformation { @@ -1403,7 +1406,7 @@ impl<'a> MessageHandler> for NodeG } NodeGraphMessage::PointerOutsideViewport { shift } => { if self.drag_start.is_some() || self.box_selection_start.is_some() || (self.wire_in_progress_from_connector.is_some() && self.context_menu.is_none()) { - let _ = self.auto_panning.shift_viewport(ipp, responses); + let _ = self.auto_panning.shift_viewport(ipp, viewport, responses); } else { // Auto-panning let messages = [NodeGraphMessage::PointerOutsideViewport { shift }.into(), NodeGraphMessage::PointerMove { shift }.into()]; @@ -1611,7 +1614,8 @@ impl<'a> MessageHandler> for NodeG return; }; - let viewport_bbox = ipp.document_bounds(); + let viewport_bounds = viewport.bounds(); + let viewport_bbox: [DVec2; 2] = viewport_bounds.into(); let document_bbox: [DVec2; 2] = viewport_bbox.map(|p| network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(p)); let mut nodes = Vec::new(); @@ -1657,7 +1661,8 @@ impl<'a> MessageHandler> for NodeG } NodeGraphMessage::SetGridAlignedEdges => { if graph_view_overlay_open { - network_interface.set_grid_aligned_edges(DVec2::new(ipp.viewport_bounds.bottom_right.x - ipp.viewport_bounds.top_left.x, 0.), breadcrumb_network_path); + let viewport_bounds = viewport.bounds(); + network_interface.set_grid_aligned_edges(DVec2::new(viewport_bounds.x() - viewport_bounds.width(), 0.), breadcrumb_network_path); // Send the new edges to the frontend responses.add(NodeGraphMessage::UpdateImportsExports); } diff --git a/editor/src/messages/portfolio/document/overlays/grid_overlays.rs b/editor/src/messages/portfolio/document/overlays/grid_overlays.rs index 9155e7bce2..d54a5f2ad0 100644 --- a/editor/src/messages/portfolio/document/overlays/grid_overlays.rs +++ b/editor/src/messages/portfolio/document/overlays/grid_overlays.rs @@ -13,9 +13,11 @@ fn grid_overlay_rectangular(document: &DocumentMessageHandler, overlay_context: let Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.document_ptz) else { return; }; - let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz); + let document_to_viewport = document + .navigation_handler + .calculate_offset_transform(overlay_context.viewport.center_in_viewport_space().into(), &document.document_ptz); - let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.size]); + let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.viewport.size().into()]); for primary in 0..2 { let secondary = 1 - primary; @@ -52,9 +54,11 @@ fn grid_overlay_rectangular_dot(document: &DocumentMessageHandler, overlay_conte let Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.document_ptz) else { return; }; - let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz); + let document_to_viewport = document + .navigation_handler + .calculate_offset_transform(overlay_context.viewport.center_in_viewport_space().into(), &document.document_ptz); - let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.size]); + let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.viewport.size().into()]); let min = bounds.0.iter().map(|corner| corner.y).min_by(|a, b| a.partial_cmp(b).unwrap()).unwrap_or_default(); let max = bounds.0.iter().map(|corner| corner.y).max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap_or_default(); @@ -85,9 +89,11 @@ fn grid_overlay_isometric(document: &DocumentMessageHandler, overlay_context: &m let grid_color = "#".to_string() + &document.snapping_state.grid.grid_color.to_rgba_hex_srgb(); let cmp = |a: &f64, b: &f64| a.partial_cmp(b).unwrap(); let origin = document.snapping_state.grid.origin; - let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz); + let document_to_viewport = document + .navigation_handler + .calculate_offset_transform(overlay_context.viewport.center_in_viewport_space().into(), &document.document_ptz); - let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.size]); + let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.viewport.size().into()]); let tan_a = angle_a.to_radians().tan(); let tan_b = angle_b.to_radians().tan(); let spacing = DVec2::new(y_axis_spacing / (tan_a + tan_b), y_axis_spacing); @@ -128,9 +134,11 @@ fn grid_overlay_isometric_dot(document: &DocumentMessageHandler, overlay_context let grid_color = "#".to_string() + &document.snapping_state.grid.grid_color.to_rgba_hex_srgb(); let cmp = |a: &f64, b: &f64| a.partial_cmp(b).unwrap(); let origin = document.snapping_state.grid.origin; - let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz); + let document_to_viewport = document + .navigation_handler + .calculate_offset_transform(overlay_context.viewport.center_in_viewport_space().into(), &document.document_ptz); - let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.size]); + let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.viewport.size().into()]); let tan_a = angle_a.to_radians().tan(); let tan_b = angle_b.to_radians().tan(); let spacing = DVec2::new(y_axis_spacing / (tan_a + tan_b), y_axis_spacing); diff --git a/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs b/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs index e6da0a9bcb..3bf436251c 100644 --- a/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs +++ b/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs @@ -4,8 +4,7 @@ use crate::messages::prelude::*; #[derive(ExtractField)] pub struct OverlaysMessageContext<'a> { pub visibility_settings: OverlaysVisibilitySettings, - pub ipp: &'a InputPreprocessorMessageHandler, - pub device_pixel_ratio: f64, + pub viewport: &'a ViewportMessageHandler, } #[derive(Debug, Clone, Default, ExtractField)] @@ -20,19 +19,14 @@ pub struct OverlaysMessageHandler { #[message_handler_data] impl MessageHandler> for OverlaysMessageHandler { fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque, context: OverlaysMessageContext) { - let OverlaysMessageContext { - visibility_settings, - ipp, - device_pixel_ratio, - .. - } = context; + let OverlaysMessageContext { visibility_settings, viewport, .. } = context; match message { #[cfg(target_family = "wasm")] OverlaysMessage::Draw => { use super::utility_functions::overlay_canvas_element; use super::utility_types::OverlayContext; - use glam::{DAffine2, DVec2}; + use crate::messages::viewport::{Position, ToPhysical}; use wasm_bindgen::JsCast; let canvas = match &self.canvas { @@ -48,28 +42,26 @@ impl MessageHandler> for OverlaysMes canvas_context.dyn_into().expect("Context should be a canvas 2d context") }); - let size = ipp.viewport_bounds.size().as_uvec2(); + let size_logical = viewport.size(); + let size_physical = size_logical.to_physical(); + let width = size_logical.x().max(size_physical.x()); + let height = size_logical.y().max(size_physical.y()); - let [a, b, c, d, e, f] = DAffine2::from_scale(DVec2::splat(device_pixel_ratio)).to_cols_array(); - let _ = canvas_context.set_transform(a, b, c, d, e, f); - canvas_context.clear_rect(0., 0., ipp.viewport_bounds.size().x, ipp.viewport_bounds.size().y); - let _ = canvas_context.reset_transform(); + canvas_context.clear_rect(0., 0., width, height); if visibility_settings.all() { responses.add(DocumentMessage::GridOverlays { context: OverlayContext { render_context: canvas_context.clone(), - size: size.as_dvec2(), - device_pixel_ratio, visibility_settings: visibility_settings.clone(), + viewport: *viewport, }, }); for provider in &self.overlay_providers { responses.add(provider(OverlayContext { render_context: canvas_context.clone(), - size: size.as_dvec2(), - device_pixel_ratio, visibility_settings: visibility_settings.clone(), + viewport: *viewport, })); } } @@ -78,9 +70,7 @@ impl MessageHandler> for OverlaysMes OverlaysMessage::Draw => { use super::utility_types::OverlayContext; - let size = ipp.viewport_bounds.size(); - - let overlay_context = OverlayContext::new(size, device_pixel_ratio, visibility_settings); + let overlay_context = OverlayContext::new(*viewport, visibility_settings); if visibility_settings.all() { responses.add(DocumentMessage::GridOverlays { context: overlay_context.clone() }); @@ -93,7 +83,7 @@ impl MessageHandler> for OverlaysMes } #[cfg(all(not(target_family = "wasm"), test))] OverlaysMessage::Draw => { - let _ = (responses, visibility_settings, ipp, device_pixel_ratio); + let _ = (responses, visibility_settings, viewport); } OverlaysMessage::AddProvider { provider: message } => { self.overlay_providers.insert(message); diff --git a/editor/src/messages/portfolio/document/overlays/utility_functions.rs b/editor/src/messages/portfolio/document/overlays/utility_functions.rs index fa0f3a0e8e..da9dc09fca 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_functions.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_functions.rs @@ -207,7 +207,6 @@ pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: & let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue; }; - //let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz); let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface); let selected = shape_editor.selected_shape_state.get(&layer); let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_point_selected(point)); diff --git a/editor/src/messages/portfolio/document/overlays/utility_types.rs b/editor/src/messages/portfolio/document/overlays/utility_types.rs index f02372ff11..e950c2b185 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types.rs @@ -2,10 +2,11 @@ use super::utility_functions::overlay_canvas_context; use crate::consts::{ ARC_SWEEP_GIZMO_RADIUS, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COLOR_OVERLAY_YELLOW_DULL, COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, DOWEL_PIN_RADIUS, MANIPULATOR_GROUP_MARKER_SIZE, - PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, SEGMENT_SELECTED_THICKNESS, + PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, RESIZE_HANDLE_SIZE, SEGMENT_SELECTED_THICKNESS, SKEW_TRIANGLE_OFFSET, SKEW_TRIANGLE_SIZE, }; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::prelude::Message; +use crate::messages::viewport::ViewportMessageHandler; use core::borrow::Borrow; use core::f64::consts::{FRAC_PI_2, PI, TAU}; use glam::{DAffine2, DVec2}; @@ -141,10 +142,7 @@ pub struct OverlayContext { #[serde(skip, default = "overlay_canvas_context")] #[specta(skip)] pub render_context: web_sys::CanvasRenderingContext2d, - pub size: DVec2, - // The device pixel ratio is a property provided by the browser window and is the CSS pixel size divided by the physical monitor's pixel size. - // It allows better pixel density of visualizations on high-DPI displays where the OS display scaling is not 100%, or where the browser is zoomed. - pub device_pixel_ratio: f64, + pub viewport: ViewportMessageHandler, pub visibility_settings: OverlaysVisibilitySettings, } // Message hashing isn't used but is required by the message system macros @@ -499,11 +497,25 @@ impl OverlayContext { self.square(position, None, Some(color_fill), Some(COLOR_OVERLAY_BLUE)); } + pub fn resize_handle(&mut self, position: DVec2, rotation: f64) { + let quad = DAffine2::from_angle_translation(rotation, position) * Quad::from_box([DVec2::splat(-RESIZE_HANDLE_SIZE / 2.), DVec2::splat(RESIZE_HANDLE_SIZE / 2.)]); + self.quad(quad, None, Some(COLOR_OVERLAY_WHITE)); + } + + pub fn skew_handles(&mut self, edge_start: DVec2, edge_end: DVec2) { + let edge_dir = (edge_end - edge_start).normalize(); + let mid = edge_end.midpoint(edge_start); + + for edge in [edge_dir, -edge_dir] { + self.draw_triangle(mid + edge * (3. + SKEW_TRIANGLE_OFFSET), edge, SKEW_TRIANGLE_SIZE, None, None); + } + } + /// Transforms the canvas context to adjust for DPI scaling /// /// Overwrites all existing tranforms. This operation can be reversed with [`Self::reset_transform`]. fn start_dpi_aware_transform(&self) { - let [a, b, c, d, e, f] = DAffine2::from_scale(DVec2::splat(self.device_pixel_ratio)).to_cols_array(); + let [a, b, c, d, e, f] = DAffine2::from_scale(DVec2::splat(self.viewport.scale())).to_cols_array(); self.render_context .set_transform(a, b, c, d, e, f) .expect("transform should be able to be set to be able to account for DPI"); @@ -967,7 +979,7 @@ impl OverlayContext { Pivot::End => -padding, }; - let [a, b, c, d, e, f] = (DAffine2::from_scale(DVec2::splat(self.device_pixel_ratio)) * transform * DAffine2::from_translation(DVec2::new(x, y))).to_cols_array(); + let [a, b, c, d, e, f] = (DAffine2::from_scale(DVec2::splat(self.viewport.scale())) * transform * DAffine2::from_translation(DVec2::new(x, y))).to_cols_array(); self.render_context.set_transform(a, b, c, d, e, f).expect("Failed to rotate the render context to the specified angle"); if let Some(background) = background_color { diff --git a/editor/src/messages/portfolio/document/overlays/utility_types_vello.rs b/editor/src/messages/portfolio/document/overlays/utility_types_vello.rs index a1e52df2ea..202fec98a8 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types_vello.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types_vello.rs @@ -1,10 +1,11 @@ use crate::consts::{ ARC_SWEEP_GIZMO_RADIUS, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COLOR_OVERLAY_YELLOW_DULL, COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, DOWEL_PIN_RADIUS, MANIPULATOR_GROUP_MARKER_SIZE, - PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, + PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, RESIZE_HANDLE_SIZE, SKEW_TRIANGLE_OFFSET, SKEW_TRIANGLE_SIZE, }; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::prelude::Message; +use crate::messages::prelude::ViewportMessageHandler; use core::borrow::Borrow; use core::f64::consts::{FRAC_PI_2, PI, TAU}; use glam::{DAffine2, DVec2}; @@ -157,24 +158,18 @@ pub struct OverlayContext { #[serde(skip)] #[specta(skip)] internal: Arc>, - pub size: DVec2, - // The device pixel ratio is a property provided by the browser window and is the CSS pixel size divided by the physical monitor's pixel size. - // It allows better pixel density of visualizations on high-DPI displays where the OS display scaling is not 100%, or where the browser is zoomed. - pub device_pixel_ratio: f64, + pub viewport: ViewportMessageHandler, pub visibility_settings: OverlaysVisibilitySettings, } impl Clone for OverlayContext { fn clone(&self) -> Self { let internal = self.internal.lock().expect("Failed to lock internal overlay context"); - let size = internal.size; - let device_pixel_ratio = internal.device_pixel_ratio; let visibility_settings = internal.visibility_settings; drop(internal); // Explicitly release the lock before cloning the Arc> Self { internal: self.internal.clone(), - size, - device_pixel_ratio, + viewport: self.viewport, visibility_settings, } } @@ -183,7 +178,7 @@ impl Clone for OverlayContext { // Manual implementations since Scene doesn't implement PartialEq or Debug impl PartialEq for OverlayContext { fn eq(&self, other: &Self) -> bool { - self.size == other.size && self.device_pixel_ratio == other.device_pixel_ratio && self.visibility_settings == other.visibility_settings + self.viewport == other.viewport && self.visibility_settings == other.visibility_settings } } @@ -191,8 +186,7 @@ impl std::fmt::Debug for OverlayContext { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("OverlayContext") .field("scene", &"Scene { ... }") - .field("size", &self.size) - .field("device_pixel_ratio", &self.device_pixel_ratio) + .field("viewport", &self.viewport) .field("visibility_settings", &self.visibility_settings) .finish() } @@ -203,8 +197,7 @@ impl Default for OverlayContext { fn default() -> Self { Self { internal: Mutex::new(OverlayContextInternal::default()).into(), - size: DVec2::ZERO, - device_pixel_ratio: 1.0, + viewport: ViewportMessageHandler::default(), visibility_settings: OverlaysVisibilitySettings::default(), } } @@ -217,11 +210,10 @@ impl core::hash::Hash for OverlayContext { impl OverlayContext { #[allow(dead_code)] - pub(super) fn new(size: DVec2, device_pixel_ratio: f64, visibility_settings: OverlaysVisibilitySettings) -> Self { + pub(super) fn new(viewport: ViewportMessageHandler, visibility_settings: OverlaysVisibilitySettings) -> Self { Self { - internal: Arc::new(Mutex::new(OverlayContextInternal::new(size, device_pixel_ratio, visibility_settings))), - size, - device_pixel_ratio, + internal: Arc::new(Mutex::new(OverlayContextInternal::new(viewport, visibility_settings))), + viewport, visibility_settings, } } @@ -280,6 +272,14 @@ impl OverlayContext { self.internal().manipulator_anchor(position, selected, color); } + pub fn resize_handle(&mut self, position: DVec2, rotation: f64) { + self.internal().resize_handle(position, rotation); + } + + pub fn skew_handles(&mut self, edge_start: DVec2, edge_end: DVec2) { + self.internal().skew_handles(edge_start, edge_end); + } + pub fn square(&mut self, position: DVec2, size: Option, color_fill: Option<&str>, color_stroke: Option<&str>) { self.internal().square(position, size, color_fill, color_stroke); } @@ -292,6 +292,7 @@ impl OverlayContext { self.internal().circle(position, radius, color_fill, color_stroke); } + #[allow(clippy::too_many_arguments)] pub fn dashed_ellipse( &mut self, center: DVec2, @@ -421,23 +422,21 @@ pub enum DrawHandles { pub(super) struct OverlayContextInternal { scene: Scene, - size: DVec2, - device_pixel_ratio: f64, + viewport: ViewportMessageHandler, visibility_settings: OverlaysVisibilitySettings, } impl Default for OverlayContextInternal { fn default() -> Self { - Self::new(DVec2::new(100., 100.), 1., OverlaysVisibilitySettings::default()) + Self::new(ViewportMessageHandler::default(), OverlaysVisibilitySettings::default()) } } impl OverlayContextInternal { - pub(super) fn new(size: DVec2, device_pixel_ratio: f64, visibility_settings: OverlaysVisibilitySettings) -> Self { + pub(super) fn new(viewport: ViewportMessageHandler, visibility_settings: OverlaysVisibilitySettings) -> Self { Self { scene: Scene::new(), - size, - device_pixel_ratio, + viewport, visibility_settings, } } @@ -473,7 +472,7 @@ impl OverlayContextInternal { self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color_fill), None, &path); - self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color_stroke), None, &path); + self.scene.stroke(&kurbo::Stroke::new(1.), transform, Self::parse_color(color_stroke), None, &path); } fn dashed_quad(&mut self, quad: Quad, stroke_color: Option<&str>, color_fill: Option<&str>, dash_width: Option, dash_gap_width: Option, dash_offset: Option) { @@ -506,7 +505,7 @@ impl OverlayContextInternal { } let stroke_color = stroke_color.unwrap_or(COLOR_OVERLAY_BLUE); - let mut stroke = kurbo::Stroke::new(1.0); + let mut stroke = kurbo::Stroke::new(1.); if let Some(dash_width) = dash_width { let dash_gap = dash_gap_width.unwrap_or(1.); @@ -551,7 +550,7 @@ impl OverlayContextInternal { self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(fill), None, &circle); self.scene - .stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color.unwrap_or(COLOR_OVERLAY_BLUE)), None, &circle); + .stroke(&kurbo::Stroke::new(1.), transform, Self::parse_color(color.unwrap_or(COLOR_OVERLAY_BLUE)), None, &circle); } fn hover_manipulator_handle(&mut self, position: DVec2, selected: bool) { @@ -563,13 +562,13 @@ impl OverlayContextInternal { let fill = COLOR_OVERLAY_BLUE_50; self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(fill), None, &circle); - self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(COLOR_OVERLAY_BLUE_50), None, &circle); + self.scene.stroke(&kurbo::Stroke::new(1.), transform, Self::parse_color(COLOR_OVERLAY_BLUE_50), None, &circle); let inner_circle = kurbo::Circle::new((position.x, position.y), MANIPULATOR_GROUP_MARKER_SIZE / 2.); let color_fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE }; self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color_fill), None, &circle); - self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &inner_circle); + self.scene.stroke(&kurbo::Stroke::new(1.), transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &inner_circle); } fn manipulator_anchor(&mut self, position: DVec2, selected: bool, color: Option<&str>) { @@ -584,8 +583,22 @@ impl OverlayContextInternal { self.square(position, None, Some(color_fill), Some(COLOR_OVERLAY_BLUE)); } + fn resize_handle(&mut self, position: DVec2, rotation: f64) { + let quad = DAffine2::from_angle_translation(rotation, position) * Quad::from_box([DVec2::splat(-RESIZE_HANDLE_SIZE / 2.), DVec2::splat(RESIZE_HANDLE_SIZE / 2.)]); + self.quad(quad, None, Some(COLOR_OVERLAY_WHITE)); + } + + fn skew_handles(&mut self, edge_start: DVec2, edge_end: DVec2) { + let edge_dir = (edge_end - edge_start).normalize(); + let mid = edge_end.midpoint(edge_start); + + for edge in [edge_dir, -edge_dir] { + self.draw_triangle(mid + edge * 3. + SKEW_TRIANGLE_OFFSET, edge, SKEW_TRIANGLE_SIZE, None, None); + } + } + fn get_transform(&self) -> kurbo::Affine { - kurbo::Affine::scale(self.device_pixel_ratio) + kurbo::Affine::scale(self.viewport.scale()) } fn square(&mut self, position: DVec2, size: Option, color_fill: Option<&str>, color_stroke: Option<&str>) { @@ -601,7 +614,7 @@ impl OverlayContextInternal { self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color_fill), None, &rect); - self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color_stroke), None, &rect); + self.scene.stroke(&kurbo::Stroke::new(1.), transform, Self::parse_color(color_stroke), None, &rect); } fn pixel(&mut self, position: DVec2, color: Option<&str>) { @@ -627,9 +640,10 @@ impl OverlayContextInternal { self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color_fill), None, &circle); - self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color_stroke), None, &circle); + self.scene.stroke(&kurbo::Stroke::new(1.), transform, Self::parse_color(color_stroke), None, &circle); } + #[allow(clippy::too_many_arguments)] fn dashed_ellipse( &mut self, _center: DVec2, @@ -678,7 +692,7 @@ impl OverlayContextInternal { ); } - self.scene.stroke(&kurbo::Stroke::new(1.0), self.get_transform(), Self::parse_color(COLOR_OVERLAY_BLUE), None, &path); + self.scene.stroke(&kurbo::Stroke::new(1.), self.get_transform(), Self::parse_color(COLOR_OVERLAY_BLUE), None, &path); } fn draw_arc_gizmo_angle(&mut self, pivot: DVec2, bold_radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) { @@ -700,7 +714,7 @@ impl OverlayContextInternal { let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.05).to_rgba_hex_srgb(); fill_color.insert(0, '#'); let fill_color = Some(fill_color.as_str()); - self.line(start + DVec2::X * radius * sign, start + DVec2::X * (radius * scale), None, None); + self.line(start + DVec2::X * radius * sign, start + DVec2::X * radius * scale.abs(), None, None); self.circle(start, radius, fill_color, None); self.circle(start, radius * scale.abs(), fill_color, None); self.text( @@ -714,14 +728,14 @@ impl OverlayContextInternal { } fn compass_rose(&mut self, compass_center: DVec2, angle: f64, show_compass_with_hover_ring: Option) { - const HOVER_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_HOVER_RING_DIAMETER / 2.; - const MAIN_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_MAIN_RING_DIAMETER / 2.; - const MAIN_RING_INNER_RADIUS: f64 = COMPASS_ROSE_RING_INNER_DIAMETER / 2.; - const ARROW_RADIUS: f64 = COMPASS_ROSE_ARROW_SIZE / 2.; - const HOVER_RING_STROKE_WIDTH: f64 = HOVER_RING_OUTER_RADIUS - MAIN_RING_INNER_RADIUS; - const HOVER_RING_CENTERLINE_RADIUS: f64 = (HOVER_RING_OUTER_RADIUS + MAIN_RING_INNER_RADIUS) / 2.; - const MAIN_RING_STROKE_WIDTH: f64 = MAIN_RING_OUTER_RADIUS - MAIN_RING_INNER_RADIUS; - const MAIN_RING_CENTERLINE_RADIUS: f64 = (MAIN_RING_OUTER_RADIUS + MAIN_RING_INNER_RADIUS) / 2.; + let hover_ring_outer_radius: f64 = COMPASS_ROSE_HOVER_RING_DIAMETER / 2.; + let main_ring_outer_radius: f64 = COMPASS_ROSE_MAIN_RING_DIAMETER / 2.; + let main_ring_inner_radius: f64 = COMPASS_ROSE_RING_INNER_DIAMETER / 2.; + let arrow_radius: f64 = COMPASS_ROSE_ARROW_SIZE / 2.; + let hover_ring_stroke_width: f64 = hover_ring_outer_radius - main_ring_inner_radius; + let hover_ring_centerline_radius: f64 = (hover_ring_outer_radius + main_ring_inner_radius) / 2.; + let main_ring_stroke_width: f64 = main_ring_outer_radius - main_ring_inner_radius; + let main_ring_centerline_radius: f64 = (main_ring_outer_radius + main_ring_inner_radius) / 2.; let Some(show_hover_ring) = show_compass_with_hover_ring else { return }; @@ -733,9 +747,9 @@ impl OverlayContextInternal { let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.5).to_rgba_hex_srgb(); fill_color.insert(0, '#'); - let circle = kurbo::Circle::new((center.x, center.y), HOVER_RING_CENTERLINE_RADIUS); + let circle = kurbo::Circle::new((center.x, center.y), hover_ring_centerline_radius); self.scene - .stroke(&kurbo::Stroke::new(HOVER_RING_STROKE_WIDTH), transform, Self::parse_color(&fill_color), None, &circle); + .stroke(&kurbo::Stroke::new(hover_ring_stroke_width), transform, Self::parse_color(&fill_color), None, &circle); } // Arrows @@ -743,11 +757,11 @@ impl OverlayContextInternal { let direction = DVec2::from_angle(i as f64 * FRAC_PI_2 + angle); let color = if i % 2 == 0 { COLOR_OVERLAY_RED } else { COLOR_OVERLAY_GREEN }; - let tip = center + direction * HOVER_RING_OUTER_RADIUS; - let base = center + direction * (MAIN_RING_INNER_RADIUS + MAIN_RING_OUTER_RADIUS) / 2.; + let tip = center + direction * hover_ring_outer_radius; + let base = center + direction * (main_ring_inner_radius + main_ring_outer_radius) / 2.; - let r = (ARROW_RADIUS.powi(2) + MAIN_RING_INNER_RADIUS.powi(2)).sqrt(); - let (cos, sin) = (MAIN_RING_INNER_RADIUS / r, ARROW_RADIUS / r); + let r = (arrow_radius.powi(2) + main_ring_inner_radius.powi(2)).sqrt(); + let (cos, sin) = (main_ring_inner_radius / r, arrow_radius / r); let side1 = center + r * DVec2::new(cos * direction.x - sin * direction.y, sin * direction.x + direction.y * cos); let side2 = center + r * DVec2::new(cos * direction.x + sin * direction.y, -sin * direction.x + direction.y * cos); @@ -764,9 +778,9 @@ impl OverlayContextInternal { } // Main ring - let circle = kurbo::Circle::new((center.x, center.y), MAIN_RING_CENTERLINE_RADIUS); + let circle = kurbo::Circle::new((center.x, center.y), main_ring_centerline_radius); self.scene - .stroke(&kurbo::Stroke::new(MAIN_RING_STROKE_WIDTH), transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &circle); + .stroke(&kurbo::Stroke::new(main_ring_stroke_width), transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &circle); } fn pivot(&mut self, position: DVec2, angle: f64) { @@ -780,22 +794,22 @@ impl OverlayContextInternal { self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(COLOR_OVERLAY_YELLOW), None, &circle); // Crosshair - const CROSSHAIR_RADIUS: f64 = (PIVOT_CROSSHAIR_LENGTH - PIVOT_CROSSHAIR_THICKNESS) / 2.; + let crosshair_radius: f64 = (PIVOT_CROSSHAIR_LENGTH - PIVOT_CROSSHAIR_THICKNESS) / 2.; let mut stroke = kurbo::Stroke::new(PIVOT_CROSSHAIR_THICKNESS); stroke = stroke.with_caps(kurbo::Cap::Round); // Horizontal line let mut path = BezPath::new(); - path.move_to(kurbo::Point::new(x + CROSSHAIR_RADIUS * uv.x, y + CROSSHAIR_RADIUS * uv.y)); - path.line_to(kurbo::Point::new(x - CROSSHAIR_RADIUS * uv.x, y - CROSSHAIR_RADIUS * uv.y)); + path.move_to(kurbo::Point::new(x + crosshair_radius * uv.x, y + crosshair_radius * uv.y)); + path.line_to(kurbo::Point::new(x - crosshair_radius * uv.x, y - crosshair_radius * uv.y)); self.scene.stroke(&stroke, transform, Self::parse_color(COLOR_OVERLAY_YELLOW), None, &path); // Vertical line let mut path = BezPath::new(); - path.move_to(kurbo::Point::new(x - CROSSHAIR_RADIUS * uv.y, y + CROSSHAIR_RADIUS * uv.x)); - path.line_to(kurbo::Point::new(x + CROSSHAIR_RADIUS * uv.y, y - CROSSHAIR_RADIUS * uv.x)); + path.move_to(kurbo::Point::new(x - crosshair_radius * uv.y, y + crosshair_radius * uv.x)); + path.line_to(kurbo::Point::new(x + crosshair_radius * uv.y, y - crosshair_radius * uv.x)); self.scene.stroke(&stroke, transform, Self::parse_color(COLOR_OVERLAY_YELLOW), None, &path); } @@ -809,15 +823,15 @@ impl OverlayContextInternal { // Draw the background circle with a white fill and colored outline let circle = kurbo::Circle::new((x, y), DOWEL_PIN_RADIUS); self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(COLOR_OVERLAY_WHITE), None, &circle); - self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color), None, &circle); + self.scene.stroke(&kurbo::Stroke::new(1.), transform, Self::parse_color(color), None, &circle); // Draw the two filled sectors using paths let mut path = BezPath::new(); // Top-left sector path.move_to(kurbo::Point::new(x, y)); - let end_x = x + DOWEL_PIN_RADIUS * (FRAC_PI_2 + angle).cos(); - let end_y = y + DOWEL_PIN_RADIUS * (FRAC_PI_2 + angle).sin(); + let end_x = x + DOWEL_PIN_RADIUS * (FRAC_PI_2 + angle.cos()); + let end_y = y + DOWEL_PIN_RADIUS * (FRAC_PI_2 + angle.sin()); path.line_to(kurbo::Point::new(end_x, end_y)); // Draw arc manually let arc = kurbo::Arc::new((x, y), (DOWEL_PIN_RADIUS, DOWEL_PIN_RADIUS), FRAC_PI_2 + angle, FRAC_PI_2, 0.0); @@ -828,8 +842,8 @@ impl OverlayContextInternal { // Bottom-right sector path.move_to(kurbo::Point::new(x, y)); - let end_x = x + DOWEL_PIN_RADIUS * (PI + FRAC_PI_2 + angle).cos(); - let end_y = y + DOWEL_PIN_RADIUS * (PI + FRAC_PI_2 + angle).sin(); + let end_x = x + DOWEL_PIN_RADIUS * (PI + FRAC_PI_2 + angle.cos()); + let end_y = y + DOWEL_PIN_RADIUS * (PI + FRAC_PI_2 + angle.sin()); path.line_to(kurbo::Point::new(end_x, end_y)); // Draw arc manually let arc = kurbo::Arc::new((x, y), (DOWEL_PIN_RADIUS, DOWEL_PIN_RADIUS), PI + FRAC_PI_2 + angle, FRAC_PI_2, 0.0); @@ -861,7 +875,7 @@ impl OverlayContextInternal { self.bezier_to_path(bezier, transform, move_to, &mut path); } - self.scene.stroke(&kurbo::Stroke::new(1.0), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path); + self.scene.stroke(&kurbo::Stroke::new(1.), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path); } /// Used by the Pen tool in order to show how the bezier curve would look like. @@ -870,7 +884,7 @@ impl OverlayContextInternal { let mut path = BezPath::new(); self.bezier_to_path(bezier, transform, true, &mut path); - self.scene.stroke(&kurbo::Stroke::new(1.0), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path); + self.scene.stroke(&kurbo::Stroke::new(1.), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path); } /// Used by the path tool segment mode in order to show the selected segments. @@ -963,7 +977,7 @@ impl OverlayContextInternal { let path = self.push_path(subpaths.iter(), transform); let color = color.unwrap_or(COLOR_OVERLAY_BLUE); - self.scene.stroke(&kurbo::Stroke::new(1.0), self.get_transform(), Self::parse_color(color), None, &path); + self.scene.stroke(&kurbo::Stroke::new(1.), self.get_transform(), Self::parse_color(color), None, &path); } } @@ -1010,7 +1024,7 @@ impl OverlayContextInternal { x_extend: peniko::Extend::Repeat, y_extend: peniko::Extend::Repeat, quality: peniko::ImageQuality::default(), - alpha: 1.0, + alpha: 1., }, }; diff --git a/editor/src/messages/portfolio/portfolio_message.rs b/editor/src/messages/portfolio/portfolio_message.rs index 763d73b629..58209fe649 100644 --- a/editor/src/messages/portfolio/portfolio_message.rs +++ b/editor/src/messages/portfolio/portfolio_message.rs @@ -112,9 +112,6 @@ pub enum PortfolioMessage { SetActivePanel { panel: PanelType, }, - SetDevicePixelRatio { - ratio: f64, - }, SelectDocument { document_id: DocumentId, }, diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 4e70ceb68c..f034d85a65 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -42,6 +42,7 @@ pub struct PortfolioMessageContext<'a> { pub message_logging_verbosity: MessageLoggingVerbosity, pub reset_node_definitions_on_open: bool, pub timing_information: TimingInformation, + pub viewport: &'a ViewportMessageHandler, } #[derive(Debug, Derivative, ExtractField)] @@ -56,7 +57,6 @@ pub struct PortfolioMessageHandler { pub persistent_data: PersistentData, pub executor: NodeGraphExecutor, pub selection_mode: SelectionMode, - device_pixel_ratio: Option, pub reset_node_definitions_on_open: bool, pub data_panel_open: bool, #[derivative(Default(value = "true"))] @@ -76,6 +76,7 @@ impl MessageHandler> for Portfolio message_logging_verbosity, reset_node_definitions_on_open, timing_information, + viewport, } = context; match message { @@ -124,7 +125,7 @@ impl MessageHandler> for Portfolio executor: &mut self.executor, current_tool, preferences, - device_pixel_ratio: self.device_pixel_ratio.unwrap_or(1.), + viewport, data_panel_open: self.data_panel_open, layers_panel_open: self.layers_panel_open, properties_panel_open: self.properties_panel_open, @@ -165,7 +166,7 @@ impl MessageHandler> for Portfolio executor: &mut self.executor, current_tool, preferences, - device_pixel_ratio: self.device_pixel_ratio.unwrap_or(1.), + viewport, data_panel_open: self.data_panel_open, layers_panel_open: self.layers_panel_open, properties_panel_open: self.properties_panel_open, @@ -361,10 +362,15 @@ impl MessageHandler> for Portfolio self.executor.update_font_cache(self.persistent_data.font_cache.clone()); for document_id in self.document_ids.iter() { let node_to_inspect = self.node_to_inspect(); + + let scale = viewport.scale(); + let resolution = viewport.size().into_dvec2().round().as_uvec2(); + if let Ok(message) = self.executor.submit_node_graph_evaluation( self.documents.get_mut(document_id).expect("Tried to render non-existent document"), *document_id, - ipp.viewport_bounds.size().as_uvec2(), + resolution, + scale, timing_information, node_to_inspect, true, @@ -699,7 +705,7 @@ impl MessageHandler> for Portfolio } PortfolioMessage::CenterPastedLayers { layers } => { if let Some(document) = self.active_document_mut() { - let viewport_bounds_quad_pixels = Quad::from_box([DVec2::ZERO, ipp.viewport_bounds.size()]); + let viewport_bounds_quad_pixels = Quad::from_box([DVec2::ZERO, viewport.size().into_dvec2()]); // In viewport pixel coordinates let viewport_center_pixels = viewport_bounds_quad_pixels.center(); // In viewport pixel coordinates let doc_to_viewport_transform = document.metadata().document_to_viewport; @@ -878,10 +884,6 @@ impl MessageHandler> for Portfolio self.active_panel = panel; responses.add(DocumentMessage::SetActivePanel { active_panel: self.active_panel }); } - PortfolioMessage::SetDevicePixelRatio { ratio } => { - self.device_pixel_ratio = Some(ratio); - responses.add(OverlaysMessage::Draw); - } PortfolioMessage::SelectDocument { document_id } => { // Auto-save the document we are leaving let mut node_graph_open = false; @@ -961,15 +963,18 @@ impl MessageHandler> for Portfolio } PortfolioMessage::SubmitGraphRender { document_id, ignore_hash } => { let node_to_inspect = self.node_to_inspect(); + let Some(document) = self.documents.get_mut(&document_id) else { log::error!("Tried to render non-existent document"); return; }; - let viewport_resolution = ipp.viewport_bounds.size().as_uvec2(); + + let scale = viewport.scale(); + let resolution = viewport.size().into_dvec2().round().as_uvec2(); let result = self .executor - .submit_node_graph_evaluation(document, document_id, viewport_resolution, timing_information, node_to_inspect, ignore_hash); + .submit_node_graph_evaluation(document, document_id, resolution, scale, timing_information, node_to_inspect, ignore_hash); match result { Err(description) => { diff --git a/editor/src/messages/prelude.rs b/editor/src/messages/prelude.rs index 7a696f9ede..d71c6a26f0 100644 --- a/editor/src/messages/prelude.rs +++ b/editor/src/messages/prelude.rs @@ -31,6 +31,7 @@ pub use crate::messages::portfolio::{PortfolioMessage, PortfolioMessageContext, pub use crate::messages::preferences::{PreferencesMessage, PreferencesMessageDiscriminant, PreferencesMessageHandler}; pub use crate::messages::tool::transform_layer::{TransformLayerMessage, TransformLayerMessageDiscriminant, TransformLayerMessageHandler}; pub use crate::messages::tool::{ToolMessage, ToolMessageContext, ToolMessageDiscriminant, ToolMessageHandler}; +pub use crate::messages::viewport::{ViewportMessage, ViewportMessageDiscriminant, ViewportMessageHandler}; // Message, MessageDiscriminant pub use crate::messages::message::{Message, MessageDiscriminant}; diff --git a/editor/src/messages/tool/common_functionality/auto_panning.rs b/editor/src/messages/tool/common_functionality/auto_panning.rs index 0ba5346c8a..f30342f2f5 100644 --- a/editor/src/messages/tool/common_functionality/auto_panning.rs +++ b/editor/src/messages/tool/common_functionality/auto_panning.rs @@ -34,9 +34,9 @@ impl AutoPanning { } } - pub fn setup_by_mouse_position(&mut self, input: &InputPreprocessorMessageHandler, messages: &[Message], responses: &mut VecDeque) { + pub fn setup_by_mouse_position(&mut self, input: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler, messages: &[Message], responses: &mut VecDeque) { let mouse_position = input.mouse.position; - let viewport_size = input.viewport_bounds.size(); + let viewport_size = viewport.size().into_dvec2(); let is_pointer_outside_edge = mouse_position.x < 0. || mouse_position.x > viewport_size.x || mouse_position.y < 0. || mouse_position.y > viewport_size.y; match is_pointer_outside_edge { @@ -50,12 +50,12 @@ impl AutoPanning { /// If the mouse was beyond any edge, it returns the amount shifted. Otherwise it returns None. /// The shift is proportional to the distance between edge and mouse, and to the duration of the frame. /// It is also guaranteed to be integral. - pub fn shift_viewport(&self, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) -> Option { + pub fn shift_viewport(&self, input: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler, responses: &mut VecDeque) -> Option { if !self.subscribed_to_animation_frame { return None; } - let viewport_size = input.viewport_bounds.size(); + let viewport_size = viewport.size().into_dvec2(); let mouse_position = input.mouse.position.clamp( DVec2::ZERO - DVec2::splat(DRAG_BEYOND_VIEWPORT_MAX_OVEREXTENSION_PIXELS), viewport_size + DVec2::splat(DRAG_BEYOND_VIEWPORT_MAX_OVEREXTENSION_PIXELS), diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/point_radius_handle.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/point_radius_handle.rs index 25516284bd..55c131dce1 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/point_radius_handle.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/point_radius_handle.rs @@ -142,7 +142,7 @@ impl PointRadiusHandle { } } - pub fn overlays(&self, selected_star_layer: Option, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, overlay_context: &mut OverlayContext) { + pub fn overlays(&self, selected_star_layer: Option, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) { match &self.handle_state { PointRadiusHandleState::Inactive => { let Some(layer) = selected_star_layer else { return }; @@ -187,7 +187,7 @@ impl PointRadiusHandle { let viewport = document.metadata().transform_to_viewport(layer); let center = viewport.transform_point2(DVec2::ZERO); - let viewport_diagonal = input.viewport_bounds.size().length(); + let viewport_diagonal = overlay_context.viewport.size().into_dvec2().length(); // Star if let Some((sides, radius1, radius2)) = extract_star_parameters(Some(layer), document) { diff --git a/editor/src/messages/tool/common_functionality/resize.rs b/editor/src/messages/tool/common_functionality/resize.rs index 47143503c0..032666ca27 100644 --- a/editor/src/messages/tool/common_functionality/resize.rs +++ b/editor/src/messages/tool/common_functionality/resize.rs @@ -15,10 +15,10 @@ pub struct Resize { impl Resize { /// Starts a resize, assigning the snap targets and snapping the starting position. - pub fn start(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler) { + pub fn start(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) { let root_transform = document.metadata().document_to_viewport; let point = SnapCandidatePoint::handle(root_transform.inverse().transform_point2(input.mouse.position)); - let snapped = self.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); + let snapped = self.snap_manager.free_snap(&SnapData::new(document, input, viewport), &point, SnapTypeConfiguration::default()); self.drag_start = snapped.snapped_point_document; } @@ -30,7 +30,14 @@ impl Resize { /// Compute the drag start and end based on the current mouse position. If the layer doesn't exist, returns [`None`]. /// If you want to draw even without a layer, use [`Resize::calculate_points_ignore_layer`]. - pub fn calculate_points(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, center: Key, lock_ratio: Key) -> Option<[DVec2; 2]> { + pub fn calculate_points( + &mut self, + document: &DocumentMessageHandler, + input: &InputPreprocessorMessageHandler, + viewport: &ViewportMessageHandler, + center: Key, + lock_ratio: Key, + ) -> Option<[DVec2; 2]> { let layer = self.layer?; if layer == LayerNodeIdentifier::ROOT_PARENT { @@ -42,21 +49,37 @@ impl Resize { self.layer.take(); return None; } - Some(self.calculate_points_ignore_layer(document, input, center, lock_ratio, false)) + Some(self.calculate_points_ignore_layer(document, input, viewport, center, lock_ratio, false)) } /// Compute the drag start and end based on the current mouse position. Ignores the state of the layer. /// If you want to only draw whilst a layer exists, use [`Resize::calculate_points`]. - pub fn calculate_points_ignore_layer(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, center: Key, lock_ratio: Key, in_document: bool) -> [DVec2; 2] { + pub fn calculate_points_ignore_layer( + &mut self, + document: &DocumentMessageHandler, + input: &InputPreprocessorMessageHandler, + viewport: &ViewportMessageHandler, + center: Key, + lock_ratio: Key, + in_document: bool, + ) -> [DVec2; 2] { let ratio = input.keyboard.get(lock_ratio as usize); let center = input.keyboard.get(center as usize); // Use shared snapping logic with optional center and ratio constraints, considering if coordinates are in document space. - self.compute_snapped_resize_points(document, input, center, ratio, in_document) + self.compute_snapped_resize_points(document, input, viewport, center, ratio, in_document) } - pub fn calculate_transform(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, center: Key, lock_ratio: Key, skip_rerender: bool) -> Option { - let points_viewport = self.calculate_points(document, input, center, lock_ratio)?; + pub fn calculate_transform( + &mut self, + document: &DocumentMessageHandler, + input: &InputPreprocessorMessageHandler, + viewport: &ViewportMessageHandler, + center: Key, + lock_ratio: Key, + skip_rerender: bool, + ) -> Option { + let points_viewport = self.calculate_points(document, input, viewport, center, lock_ratio)?; Some( GraphOperationMessage::TransformSet { layer: self.layer?, @@ -68,23 +91,33 @@ impl Resize { ) } - pub fn calculate_circle_points(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, center: Key) -> [DVec2; 2] { + pub fn calculate_circle_points(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler, center: Key) -> [DVec2; 2] { let center = input.keyboard.get(center as usize); // Use shared snapping logic with enforced aspect ratio and optional center snapping. - self.compute_snapped_resize_points(document, input, center, true, false) + self.compute_snapped_resize_points(document, input, viewport, center, true, false) } /// Calculates two points in viewport space from a drag, applying snapping, optional center mode, and aspect ratio locking. - fn compute_snapped_resize_points(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, center: bool, lock_ratio: bool, in_document: bool) -> [DVec2; 2] { + fn compute_snapped_resize_points( + &mut self, + document: &DocumentMessageHandler, + input: &InputPreprocessorMessageHandler, + viewport: &ViewportMessageHandler, + center: bool, + lock_ratio: bool, + in_document: bool, + ) -> [DVec2; 2] { let start = self.viewport_drag_start(document); let mouse = input.mouse.position; - let document_to_viewport = document.navigation_handler.calculate_offset_transform(input.viewport_bounds.center(), &document.document_ptz); + let document_to_viewport = document + .navigation_handler + .calculate_offset_transform(viewport.center_in_viewport_space().into(), &document.document_ptz); let drag_start = self.drag_start; let mut points_viewport = [start, mouse]; let ignore = if let Some(layer) = self.layer { vec![layer] } else { vec![] }; - let snap_data = &SnapData::ignore(document, input, &ignore); + let snap_data = &SnapData::ignore(document, input, viewport, &ignore); if lock_ratio { let viewport_size = points_viewport[1] - points_viewport[0]; diff --git a/editor/src/messages/tool/common_functionality/shape_editor.rs b/editor/src/messages/tool/common_functionality/shape_editor.rs index aa50e23b66..bc84cfa0b0 100644 --- a/editor/src/messages/tool/common_functionality/shape_editor.rs +++ b/editor/src/messages/tool/common_functionality/shape_editor.rs @@ -503,8 +503,16 @@ impl ShapeState { } // Snap, returning a viewport delta - pub fn snap(&self, snap_manager: &mut SnapManager, snap_cache: &SnapCache, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, previous_mouse: DVec2) -> DVec2 { - let snap_data = SnapData::new_snap_cache(document, input, snap_cache); + pub fn snap( + &self, + snap_manager: &mut SnapManager, + snap_cache: &SnapCache, + document: &DocumentMessageHandler, + input: &InputPreprocessorMessageHandler, + viewport: &ViewportMessageHandler, + previous_mouse: DVec2, + ) -> DVec2 { + let snap_data = SnapData::new_snap_cache(document, input, viewport, snap_cache); let mouse_delta = document .network_interface diff --git a/editor/src/messages/tool/common_functionality/shapes/arc_shape.rs b/editor/src/messages/tool/common_functionality/shapes/arc_shape.rs index 68803598de..a40ea4160e 100644 --- a/editor/src/messages/tool/common_functionality/shapes/arc_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/arc_shape.rs @@ -146,13 +146,14 @@ impl Arc { pub fn update_shape( document: &DocumentMessageHandler, ipp: &InputPreprocessorMessageHandler, + viewport: &ViewportMessageHandler, layer: LayerNodeIdentifier, shape_tool_data: &mut ShapeToolData, modifier: ShapeToolModifierKey, responses: &mut VecDeque, ) { let (center, lock_ratio) = (modifier[0], modifier[1]); - if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) { + if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, viewport, center, lock_ratio) { let Some(node_id) = graph_modification_utils::get_arc_id(layer, &document.network_interface) else { return; }; diff --git a/editor/src/messages/tool/common_functionality/shapes/circle_shape.rs b/editor/src/messages/tool/common_functionality/shapes/circle_shape.rs index d622950260..fa59535b57 100644 --- a/editor/src/messages/tool/common_functionality/shapes/circle_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/circle_shape.rs @@ -89,13 +89,14 @@ impl Circle { pub fn update_shape( document: &DocumentMessageHandler, ipp: &InputPreprocessorMessageHandler, + viewport: &ViewportMessageHandler, layer: LayerNodeIdentifier, shape_tool_data: &mut ShapeToolData, modifier: ShapeToolModifierKey, responses: &mut VecDeque, ) { let center = modifier[0]; - let [start, end] = shape_tool_data.data.calculate_circle_points(document, ipp, center); + let [start, end] = shape_tool_data.data.calculate_circle_points(document, ipp, viewport, center); let Some(node_id) = graph_modification_utils::get_circle_id(layer, &document.network_interface) else { return; }; diff --git a/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs b/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs index 3cc86193dd..e235072a0e 100644 --- a/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs @@ -23,6 +23,7 @@ impl Ellipse { pub fn update_shape( document: &DocumentMessageHandler, ipp: &InputPreprocessorMessageHandler, + viewport: &ViewportMessageHandler, layer: LayerNodeIdentifier, shape_tool_data: &mut ShapeToolData, modifier: ShapeToolModifierKey, @@ -30,7 +31,7 @@ impl Ellipse { ) { let [center, lock_ratio, _] = modifier; - if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) { + if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, viewport, center, lock_ratio) { let Some(node_id) = graph_modification_utils::get_ellipse_id(layer, &document.network_interface) else { return; }; diff --git a/editor/src/messages/tool/common_functionality/shapes/line_shape.rs b/editor/src/messages/tool/common_functionality/shapes/line_shape.rs index 3fed8793e6..16b64a2315 100644 --- a/editor/src/messages/tool/common_functionality/shapes/line_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/line_shape.rs @@ -48,6 +48,7 @@ impl Line { pub fn update_shape( document: &DocumentMessageHandler, ipp: &InputPreprocessorMessageHandler, + viewport: &ViewportMessageHandler, layer: LayerNodeIdentifier, shape_tool_data: &mut ShapeToolData, modifier: ShapeToolModifierKey, @@ -59,7 +60,7 @@ impl Line { let keyboard = &ipp.keyboard; let ignore = [layer]; - let snap_data = SnapData::ignore(document, ipp, &ignore); + let snap_data = SnapData::ignore(document, ipp, viewport, &ignore); let mut document_points = generate_line(shape_tool_data, snap_data, keyboard.key(lock_angle), keyboard.key(snap_angle), keyboard.key(center)); if shape_tool_data.line_data.dragging_endpoint == Some(LineEnd::Start) { diff --git a/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs b/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs index 895bb4a212..0abce50c4e 100644 --- a/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs @@ -58,13 +58,13 @@ impl ShapeGizmoHandler for PolygonGizmoHandler { &self, document: &DocumentMessageHandler, selected_polygon_layer: Option, - input: &InputPreprocessorMessageHandler, + _input: &InputPreprocessorMessageHandler, shape_editor: &mut &mut ShapeState, mouse_position: DVec2, overlay_context: &mut OverlayContext, ) { self.number_of_points_dial.overlays(document, selected_polygon_layer, shape_editor, mouse_position, overlay_context); - self.point_radius_handle.overlays(selected_polygon_layer, document, input, overlay_context); + self.point_radius_handle.overlays(selected_polygon_layer, document, overlay_context); polygon_outline(selected_polygon_layer, document, overlay_context); } @@ -72,7 +72,7 @@ impl ShapeGizmoHandler for PolygonGizmoHandler { fn dragging_overlays( &self, document: &DocumentMessageHandler, - input: &InputPreprocessorMessageHandler, + _input: &InputPreprocessorMessageHandler, shape_editor: &mut &mut ShapeState, mouse_position: DVec2, overlay_context: &mut OverlayContext, @@ -82,7 +82,7 @@ impl ShapeGizmoHandler for PolygonGizmoHandler { } if self.point_radius_handle.is_dragging_or_snapped() { - self.point_radius_handle.overlays(None, document, input, overlay_context); + self.point_radius_handle.overlays(None, document, overlay_context); } } @@ -116,6 +116,7 @@ impl Polygon { pub fn update_shape( document: &DocumentMessageHandler, ipp: &InputPreprocessorMessageHandler, + viewport: &ViewportMessageHandler, layer: LayerNodeIdentifier, shape_tool_data: &mut ShapeToolData, modifier: ShapeToolModifierKey, @@ -123,7 +124,7 @@ impl Polygon { ) { let [center, lock_ratio, _] = modifier; - if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) { + if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, viewport, center, lock_ratio) { // TODO: We need to determine how to allow the polygon node to make irregular shapes update_radius_sign(end, start, layer, document, responses); diff --git a/editor/src/messages/tool/common_functionality/shapes/rectangle_shape.rs b/editor/src/messages/tool/common_functionality/shapes/rectangle_shape.rs index 56cdbe4d1f..16f95d151f 100644 --- a/editor/src/messages/tool/common_functionality/shapes/rectangle_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/rectangle_shape.rs @@ -23,6 +23,7 @@ impl Rectangle { pub fn update_shape( document: &DocumentMessageHandler, ipp: &InputPreprocessorMessageHandler, + viewport: &ViewportMessageHandler, layer: LayerNodeIdentifier, shape_tool_data: &mut ShapeToolData, modifier: ShapeToolModifierKey, @@ -30,7 +31,7 @@ impl Rectangle { ) { let [center, lock_ratio, _] = modifier; - if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) { + if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, viewport, center, lock_ratio) { let Some(node_id) = graph_modification_utils::get_rectangle_id(layer, &document.network_interface) else { return; }; diff --git a/editor/src/messages/tool/common_functionality/shapes/spiral_shape.rs b/editor/src/messages/tool/common_functionality/shapes/spiral_shape.rs index abddfd7ab0..caf0346beb 100644 --- a/editor/src/messages/tool/common_functionality/shapes/spiral_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/spiral_shape.rs @@ -36,13 +36,20 @@ impl Spiral { ]) } - pub fn update_shape(document: &DocumentMessageHandler, ipp: &InputPreprocessorMessageHandler, layer: LayerNodeIdentifier, shape_tool_data: &mut ShapeToolData, responses: &mut VecDeque) { + pub fn update_shape( + document: &DocumentMessageHandler, + ipp: &InputPreprocessorMessageHandler, + viewport: &ViewportMessageHandler, + layer: LayerNodeIdentifier, + shape_tool_data: &mut ShapeToolData, + responses: &mut VecDeque, + ) { use graphene_std::vector::generator_nodes::spiral::*; let viewport_drag_start = shape_tool_data.data.viewport_drag_start(document); let ignore = vec![layer]; - let snap_data = SnapData::ignore(document, ipp, &ignore); + let snap_data = SnapData::ignore(document, ipp, viewport, &ignore); let config = SnapTypeConfiguration::default(); let document_mouse = document.metadata().document_to_viewport.inverse().transform_point2(ipp.mouse.position); let snapped = shape_tool_data.data.snap_manager.free_snap(&snap_data, &SnapCandidatePoint::handle(document_mouse), config); diff --git a/editor/src/messages/tool/common_functionality/shapes/star_shape.rs b/editor/src/messages/tool/common_functionality/shapes/star_shape.rs index 4effa98957..874f464e9b 100644 --- a/editor/src/messages/tool/common_functionality/shapes/star_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/star_shape.rs @@ -58,13 +58,13 @@ impl ShapeGizmoHandler for StarGizmoHandler { &self, document: &DocumentMessageHandler, selected_star_layer: Option, - input: &InputPreprocessorMessageHandler, + _input: &InputPreprocessorMessageHandler, shape_editor: &mut &mut ShapeState, mouse_position: DVec2, overlay_context: &mut OverlayContext, ) { self.number_of_points_dial.overlays(document, selected_star_layer, shape_editor, mouse_position, overlay_context); - self.point_radius_handle.overlays(selected_star_layer, document, input, overlay_context); + self.point_radius_handle.overlays(selected_star_layer, document, overlay_context); star_outline(selected_star_layer, document, overlay_context); } @@ -72,7 +72,7 @@ impl ShapeGizmoHandler for StarGizmoHandler { fn dragging_overlays( &self, document: &DocumentMessageHandler, - input: &InputPreprocessorMessageHandler, + _input: &InputPreprocessorMessageHandler, shape_editor: &mut &mut ShapeState, mouse_position: DVec2, overlay_context: &mut OverlayContext, @@ -82,7 +82,7 @@ impl ShapeGizmoHandler for StarGizmoHandler { } if self.point_radius_handle.is_dragging_or_snapped() { - self.point_radius_handle.overlays(None, document, input, overlay_context); + self.point_radius_handle.overlays(None, document, overlay_context); } } @@ -121,6 +121,7 @@ impl Star { pub fn update_shape( document: &DocumentMessageHandler, ipp: &InputPreprocessorMessageHandler, + viewport: &ViewportMessageHandler, layer: LayerNodeIdentifier, shape_tool_data: &mut ShapeToolData, modifier: ShapeToolModifierKey, @@ -128,7 +129,7 @@ impl Star { ) { let [center, lock_ratio, _] = modifier; - if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, center, lock_ratio) { + if let Some([start, end]) = shape_tool_data.data.calculate_points(document, ipp, viewport, center, lock_ratio) { // TODO: We need to determine how to allow the polygon node to make irregular shapes update_radius_sign(end, start, layer, document, responses); diff --git a/editor/src/messages/tool/common_functionality/snapping.rs b/editor/src/messages/tool/common_functionality/snapping.rs index 27b658e9a9..63d79d95ba 100644 --- a/editor/src/messages/tool/common_functionality/snapping.rs +++ b/editor/src/messages/tool/common_functionality/snapping.rs @@ -206,29 +206,31 @@ pub struct SnapCache { pub struct SnapData<'a> { pub document: &'a DocumentMessageHandler, pub input: &'a InputPreprocessorMessageHandler, + pub viewport: &'a ViewportMessageHandler, pub ignore: &'a [LayerNodeIdentifier], pub node_snap_cache: Option<&'a SnapCache>, pub candidates: Option<&'a Vec>, pub alignment_candidates: Option<&'a Vec>, } impl<'a> SnapData<'a> { - pub fn new(document: &'a DocumentMessageHandler, input: &'a InputPreprocessorMessageHandler) -> Self { - Self::ignore(document, input, &[]) + pub fn new(document: &'a DocumentMessageHandler, input: &'a InputPreprocessorMessageHandler, viewport: &'a ViewportMessageHandler) -> Self { + Self::ignore(document, input, viewport, &[]) } - pub fn ignore(document: &'a DocumentMessageHandler, input: &'a InputPreprocessorMessageHandler, ignore: &'a [LayerNodeIdentifier]) -> Self { + pub fn ignore(document: &'a DocumentMessageHandler, input: &'a InputPreprocessorMessageHandler, viewport: &'a ViewportMessageHandler, ignore: &'a [LayerNodeIdentifier]) -> Self { Self { document, input, + viewport, ignore, candidates: None, alignment_candidates: None, node_snap_cache: None, } } - pub fn new_snap_cache(document: &'a DocumentMessageHandler, input: &'a InputPreprocessorMessageHandler, snap_cache: &'a SnapCache) -> Self { + pub fn new_snap_cache(document: &'a DocumentMessageHandler, input: &'a InputPreprocessorMessageHandler, viewport: &'a ViewportMessageHandler, snap_cache: &'a SnapCache) -> Self { Self { node_snap_cache: Some(snap_cache), - ..Self::new(document, input) + ..Self::new(document, input, viewport) } } fn get_candidates(&self) -> &[LayerNodeIdentifier] { @@ -301,7 +303,7 @@ impl SnapManager { for point in snapped_points { let viewport_point = document.metadata().document_to_viewport.transform_point2(point.snapped_point_document); - let on_screen = viewport_point.cmpgt(DVec2::ZERO).all() && viewport_point.cmplt(snap_data.input.viewport_bounds.size()).all(); + let on_screen = viewport_point.cmpgt(DVec2::ZERO).all() && viewport_point.cmplt(snap_data.viewport.size().into()).all(); if !on_screen && !off_screen { continue; } @@ -337,7 +339,7 @@ impl SnapManager { return; }; let layer_bounds = document.metadata().transform_to_document(layer) * Quad::from_box(bounds); - let screen_bounds = document.metadata().document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, snap_data.input.viewport_bounds.size()]); + let screen_bounds = document.metadata().document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, snap_data.viewport.size().into()]); if screen_bounds.intersects(layer_bounds) { if self.alignment_candidates.as_ref().is_none_or(|candidates| candidates.len() <= 100) { self.alignment_candidates.get_or_insert_with(Vec::new).push(layer); diff --git a/editor/src/messages/tool/common_functionality/snapping/distribution_snapper.rs b/editor/src/messages/tool/common_functionality/snapping/distribution_snapper.rs index 1622effaf9..543c2280ff 100644 --- a/editor/src/messages/tool/common_functionality/snapping/distribution_snapper.rs +++ b/editor/src/messages/tool/common_functionality/snapping/distribution_snapper.rs @@ -76,7 +76,7 @@ impl DistributionSnapper { self.down.clear(); self.up.clear(); - let screen_bounds = (document.metadata().document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, snap_data.input.viewport_bounds.size()])).bounding_box(); + let screen_bounds = (document.metadata().document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, snap_data.viewport.size().into()])).bounding_box(); let max_extent = (screen_bounds[1] - screen_bounds[0]).abs().max_element(); // Collect artboard bounds diff --git a/editor/src/messages/tool/common_functionality/transformation_cage.rs b/editor/src/messages/tool/common_functionality/transformation_cage.rs index cb5ce0e91f..58bac688ef 100644 --- a/editor/src/messages/tool/common_functionality/transformation_cage.rs +++ b/editor/src/messages/tool/common_functionality/transformation_cage.rs @@ -1,8 +1,8 @@ use super::snapping::{self, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnappedPoint}; use crate::consts::{ - BOUNDS_ROTATE_THRESHOLD, BOUNDS_SELECT_THRESHOLD, COLOR_OVERLAY_WHITE, MAX_LENGTH_FOR_NO_WIDTH_OR_HEIGHT, MAXIMUM_ALT_SCALE_FACTOR, MIN_LENGTH_FOR_CORNERS_VISIBILITY, - MIN_LENGTH_FOR_EDGE_RESIZE_PRIORITY_OVER_CORNERS, MIN_LENGTH_FOR_MIDPOINT_VISIBILITY, MIN_LENGTH_FOR_RESIZE_TO_INCLUDE_INTERIOR, MIN_LENGTH_FOR_SKEW_TRIANGLE_VISIBILITY, RESIZE_HANDLE_SIZE, - SELECTION_DRAG_ANGLE, SKEW_TRIANGLE_OFFSET, SKEW_TRIANGLE_SIZE, + BOUNDS_ROTATE_THRESHOLD, BOUNDS_SELECT_THRESHOLD, MAX_LENGTH_FOR_NO_WIDTH_OR_HEIGHT, MAXIMUM_ALT_SCALE_FACTOR, MIN_LENGTH_FOR_CORNERS_VISIBILITY, MIN_LENGTH_FOR_EDGE_RESIZE_PRIORITY_OVER_CORNERS, + MIN_LENGTH_FOR_MIDPOINT_VISIBILITY, MIN_LENGTH_FOR_RESIZE_TO_INCLUDE_INTERIOR, MIN_LENGTH_FOR_SKEW_TRIANGLE_VISIBILITY, RESIZE_HANDLE_SIZE, SELECTION_DRAG_ANGLE, SKEW_TRIANGLE_OFFSET, + SKEW_TRIANGLE_SIZE, }; use crate::messages::frontend::utility_types::MouseCursorIcon; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; @@ -489,13 +489,7 @@ impl BoundingBoxManager { if (end - start).length() < MIN_LENGTH_FOR_SKEW_TRIANGLE_VISIBILITY { return; } - - let edge_dir = (end - start).normalize(); - let mid = end.midpoint(start); - - for edge in [edge_dir, -edge_dir] { - overlay_context.draw_triangle(mid + edge * (3. + SKEW_TRIANGLE_OFFSET), edge, SKEW_TRIANGLE_SIZE, None, None); - } + overlay_context.skew_handles(start, end); }; if let Some([start, end]) = self.edge_endpoints_vector_from_edge_bool(hover_edge) { @@ -565,11 +559,6 @@ impl BoundingBoxManager { self.render_quad(overlay_context); } - let mut draw_handle = |point: DVec2, angle: f64| { - let quad = DAffine2::from_angle_translation(angle, point) * Quad::from_box([DVec2::splat(-RESIZE_HANDLE_SIZE / 2.), DVec2::splat(RESIZE_HANDLE_SIZE / 2.)]); - overlay_context.quad(quad, None, Some(COLOR_OVERLAY_WHITE)); - }; - let horizontal_angle = (quad.top_left() - quad.bottom_left()).to_angle(); let vertical_angle = (quad.top_left() - quad.top_right()).to_angle(); @@ -579,7 +568,7 @@ impl BoundingBoxManager { TransformCageSizeCategory::Full | TransformCageSizeCategory::Narrow | TransformCageSizeCategory::ReducedLandscape ) { for point in horizontal_edges { - draw_handle(point, horizontal_angle); + overlay_context.resize_handle(point, horizontal_angle); } } @@ -589,7 +578,7 @@ impl BoundingBoxManager { TransformCageSizeCategory::Full | TransformCageSizeCategory::Narrow | TransformCageSizeCategory::ReducedPortrait ) { for point in vertical_edges { - draw_handle(point, vertical_angle); + overlay_context.resize_handle(point, vertical_angle); } } @@ -606,14 +595,14 @@ impl BoundingBoxManager { TransformCageSizeCategory::Full | TransformCageSizeCategory::ReducedBoth | TransformCageSizeCategory::ReducedLandscape | TransformCageSizeCategory::ReducedPortrait ) { for point in quad.0 { - draw_handle(point, angle); + overlay_context.resize_handle(point, angle); } } // Draw the flat line endpoint drag handles if category == TransformCageSizeCategory::Flat { - draw_handle(self.transform.transform_point2(self.bounds[0]), angle); - draw_handle(self.transform.transform_point2(self.bounds[1]), angle); + overlay_context.resize_handle(self.transform.transform_point2(self.bounds[0]), angle); + overlay_context.resize_handle(self.transform.transform_point2(self.bounds[1]), angle); } } diff --git a/editor/src/messages/tool/common_functionality/utility_functions.rs b/editor/src/messages/tool/common_functionality/utility_functions.rs index 1ca97a2be0..ada904527e 100644 --- a/editor/src/messages/tool/common_functionality/utility_functions.rs +++ b/editor/src/messages/tool/common_functionality/utility_functions.rs @@ -262,6 +262,7 @@ pub fn resize_bounds( snap_manager: &mut SnapManager, snap_candidates: &mut Vec, input: &InputPreprocessorMessageHandler, + viewport: &ViewportMessageHandler, center: bool, constrain: bool, tool: ToolType, @@ -271,7 +272,7 @@ pub fn resize_bounds( let snap = Some(SizeSnapData { manager: snap_manager, points: snap_candidates, - snap_data: SnapData::ignore(document, input, dragging_layers), + snap_data: SnapData::ignore(document, input, viewport, dragging_layers), }); let (position, size) = movement.new_size(input.mouse.position, bounds.original_bound_transform, center, constrain, snap); let (delta, mut pivot) = movement.bounds_to_scale_transform(position, size); diff --git a/editor/src/messages/tool/tool_message_handler.rs b/editor/src/messages/tool/tool_message_handler.rs index 3e4a720006..2b336eb57e 100644 --- a/editor/src/messages/tool/tool_message_handler.rs +++ b/editor/src/messages/tool/tool_message_handler.rs @@ -21,6 +21,7 @@ pub struct ToolMessageContext<'a> { pub persistent_data: &'a PersistentData, pub node_graph: &'a NodeGraphExecutor, pub preferences: &'a PreferencesMessageHandler, + pub viewport: &'a ViewportMessageHandler, } #[derive(Debug, Default, ExtractField)] @@ -41,6 +42,7 @@ impl MessageHandler> for ToolMessageHandler persistent_data, node_graph, preferences, + viewport, } = context; let font_cache = &persistent_data.font_cache; @@ -54,6 +56,7 @@ impl MessageHandler> for ToolMessageHandler input, tool_data: &self.tool_state.tool_data, shape_editor: &mut self.shape_editor, + viewport, }, ), @@ -123,6 +126,7 @@ impl MessageHandler> for ToolMessageHandler shape_editor: &mut self.shape_editor, node_graph, preferences, + viewport, }; if let Some(tool_abort_message) = tool.event_to_message_map().tool_abort { @@ -230,6 +234,7 @@ impl MessageHandler> for ToolMessageHandler shape_editor: &mut self.shape_editor, node_graph, preferences, + viewport, }; // Set initial hints and cursor @@ -336,6 +341,7 @@ impl MessageHandler> for ToolMessageHandler shape_editor: &mut self.shape_editor, node_graph, preferences, + viewport, }; if matches!(tool_message, ToolMessage::UpdateHints) { if graph_view_overlay_open { diff --git a/editor/src/messages/tool/tool_messages/artboard_tool.rs b/editor/src/messages/tool/tool_messages/artboard_tool.rs index 14cab5acdc..3f1b120b1a 100644 --- a/editor/src/messages/tool/tool_messages/artboard_tool.rs +++ b/editor/src/messages/tool/tool_messages/artboard_tool.rs @@ -145,12 +145,12 @@ impl ArtboardToolData { } } - fn hovered_artboard(document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler) -> Option { - document.click_xray(input).find(|&layer| document.network_interface.is_artboard(&layer.to_node(), &[])) + fn hovered_artboard(document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) -> Option { + document.click_xray(input, viewport).find(|&layer| document.network_interface.is_artboard(&layer.to_node(), &[])) } - fn select_artboard(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) -> bool { - if let Some(intersection) = Self::hovered_artboard(document, input) { + fn select_artboard(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler, responses: &mut VecDeque) -> bool { + if let Some(intersection) = Self::hovered_artboard(document, input, viewport) { self.selected_artboard = Some(intersection); if let Some(bounds) = document.metadata().bounding_box_document(intersection) { @@ -171,7 +171,15 @@ impl ArtboardToolData { } } - fn resize_artboard(&mut self, responses: &mut VecDeque, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, from_center: bool, constrain_square: bool) { + fn resize_artboard( + &mut self, + responses: &mut VecDeque, + document: &DocumentMessageHandler, + input: &InputPreprocessorMessageHandler, + viewport: &ViewportMessageHandler, + from_center: bool, + constrain_square: bool, + ) { let Some(bounds) = &self.bounding_box_manager else { return; }; @@ -193,7 +201,7 @@ impl ArtboardToolData { let snap = Some(SizeSnapData { manager: &mut self.draw.snap_manager, points: &mut self.snap_candidates, - snap_data: SnapData::ignore(document, input, &ignore), + snap_data: SnapData::ignore(document, input, viewport, &ignore), }); let (min, size) = movement.new_size(input.mouse.position, bounds.transform, center, constrain_square, snap); let max = min + size; @@ -225,9 +233,9 @@ impl Fsm for ArtboardToolFsmState { type ToolOptions = (); fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionMessageContext, _tool_options: &(), responses: &mut VecDeque) -> Self { - let ToolActionMessageContext { document, input, .. } = tool_action_data; + let ToolActionMessageContext { document, input, viewport, .. } = tool_action_data; - let hovered = ArtboardToolData::hovered_artboard(document, input).is_some(); + let hovered = ArtboardToolData::hovered_artboard(document, input, viewport).is_some(); let ToolMessage::Artboard(event) = event else { return self }; match (self, event) { @@ -259,8 +267,8 @@ impl Fsm for ArtboardToolFsmState { let selected_artboard_bounds = tool_data.selected_artboard.and_then(|layer| document.metadata().bounding_box_document(layer)).map(Rect::from_box); // Find hovered artboard or regular layer - let hovered_artboard = ArtboardToolData::hovered_artboard(document, input); - let hovered_layer = document.click_xray(input).find(|&layer| !document.network_interface.is_artboard(&layer.to_node(), &[])); + let hovered_artboard = ArtboardToolData::hovered_artboard(document, input, viewport); + let hovered_layer = document.click_xray(input, viewport).find(|&layer| !document.network_interface.is_artboard(&layer.to_node(), &[])); // Get bounds for the hovered object (prioritize artboards) let hovered_bounds = if let Some(artboard) = hovered_artboard { @@ -281,7 +289,7 @@ impl Fsm for ArtboardToolFsmState { } } - tool_data.draw.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); + tool_data.draw.snap_manager.draw_overlays(SnapData::new(document, input, viewport), &mut overlay_context); self } @@ -295,11 +303,11 @@ impl Fsm for ArtboardToolFsmState { tool_data.start_resizing(selected_edges, document, input); tool_data.get_snap_candidates(document, input); ArtboardToolFsmState::ResizingBounds - } else if tool_data.select_artboard(document, input, responses) { + } else if tool_data.select_artboard(document, input, viewport, responses) { tool_data.get_snap_candidates(document, input); ArtboardToolFsmState::Dragging } else { - tool_data.draw.start(document, input); + tool_data.draw.start(document, input, viewport); ArtboardToolFsmState::Drawing }; @@ -309,14 +317,14 @@ impl Fsm for ArtboardToolFsmState { (ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => { let from_center = input.keyboard.get(center as usize); let constrain_square = input.keyboard.get(constrain_axis_or_aspect as usize); - tool_data.resize_artboard(responses, document, input, from_center, constrain_square); + tool_data.resize_artboard(responses, document, input, viewport, from_center, constrain_square); // Auto-panning let messages = [ ArtboardToolMessage::PointerOutsideViewport { constrain_axis_or_aspect, center }.into(), ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }.into(), ]; - tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); + tool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses); ArtboardToolFsmState::ResizingBounds } @@ -325,7 +333,7 @@ impl Fsm for ArtboardToolFsmState { let axis_align = input.keyboard.get(constrain_axis_or_aspect as usize); let ignore = tool_data.selected_artboard.map_or(Vec::new(), |layer| vec![layer]); - let snap_data = SnapData::ignore(document, input, &ignore); + let snap_data = SnapData::ignore(document, input, viewport, &ignore); let document_to_viewport = document.metadata().document_to_viewport; let [start, current] = [tool_data.drag_start, tool_data.drag_current].map(|point| document_to_viewport.transform_point2(point)); let mouse_delta = snap_drag(start, current, axis_align, Axis::None, snap_data, &mut tool_data.draw.snap_manager, &tool_data.snap_candidates); @@ -355,14 +363,14 @@ impl Fsm for ArtboardToolFsmState { ArtboardToolMessage::PointerOutsideViewport { constrain_axis_or_aspect, center }.into(), ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }.into(), ]; - tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); + tool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses); } ArtboardToolFsmState::Dragging } (ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => { // The draw.calculate_points_ignore_layer uses this value to avoid snapping to itself. tool_data.draw.layer = tool_data.selected_artboard; - let [start, end] = tool_data.draw.calculate_points_ignore_layer(document, input, center, constrain_axis_or_aspect, true); + let [start, end] = tool_data.draw.calculate_points_ignore_layer(document, input, viewport, center, constrain_axis_or_aspect, true); let viewport_to_document = document.metadata().document_to_viewport.inverse(); let [start, end] = [start, end].map(|point| viewport_to_document.transform_point2(point)); if let Some(artboard) = tool_data.selected_artboard { @@ -396,7 +404,7 @@ impl Fsm for ArtboardToolFsmState { ArtboardToolMessage::PointerOutsideViewport { constrain_axis_or_aspect, center }.into(), ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }.into(), ]; - tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); + tool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses); ArtboardToolFsmState::Drawing } @@ -408,7 +416,7 @@ impl Fsm for ArtboardToolFsmState { .map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, false, false, None)); if cursor == MouseCursorIcon::Default && !hovered { - tool_data.draw.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position); + tool_data.draw.snap_manager.preview_draw(&SnapData::new(document, input, viewport), input.mouse.position); responses.add(OverlaysMessage::Draw); cursor = MouseCursorIcon::Crosshair; } else { @@ -424,19 +432,19 @@ impl Fsm for ArtboardToolFsmState { } (ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerOutsideViewport { .. }) => { // Auto-panning - let _ = tool_data.auto_panning.shift_viewport(input, responses); + let _ = tool_data.auto_panning.shift_viewport(input, viewport, responses); ArtboardToolFsmState::ResizingBounds } (ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerOutsideViewport { .. }) => { // Auto-panning - tool_data.auto_panning.shift_viewport(input, responses); + tool_data.auto_panning.shift_viewport(input, viewport, responses); ArtboardToolFsmState::Dragging } (ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerOutsideViewport { .. }) => { // Auto-panning - tool_data.auto_panning.shift_viewport(input, responses); + tool_data.auto_panning.shift_viewport(input, viewport, responses); ArtboardToolFsmState::Drawing } @@ -545,7 +553,9 @@ impl Fsm for ArtboardToolFsmState { let scale = DAffine2::from_scale(enlargement_factor); let pivot = DAffine2::from_translation(pivot); let transformation = pivot * scale * pivot.inverse(); - let document_to_viewport = document.navigation_handler.calculate_offset_transform(input.viewport_bounds.center(), &document.document_ptz); + let document_to_viewport = document + .navigation_handler + .calculate_offset_transform(viewport.center_in_viewport_space().into(), &document.document_ptz); let to = document_to_viewport.inverse() * document.metadata().downstream_transform_to_viewport(selected_artboard); let original_transform = document.metadata().upstream_transform(selected_artboard.to_node()); let new = to.inverse() * transformation * to * original_transform; diff --git a/editor/src/messages/tool/tool_messages/eyedropper_tool.rs b/editor/src/messages/tool/tool_messages/eyedropper_tool.rs index d5a082cc21..b84b706956 100644 --- a/editor/src/messages/tool/tool_messages/eyedropper_tool.rs +++ b/editor/src/messages/tool/tool_messages/eyedropper_tool.rs @@ -81,7 +81,9 @@ impl Fsm for EyedropperToolFsmState { type ToolOptions = (); fn transition(self, event: ToolMessage, _tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionMessageContext, _tool_options: &(), responses: &mut VecDeque) -> Self { - let ToolActionMessageContext { global_tool_data, input, .. } = tool_action_data; + let ToolActionMessageContext { + global_tool_data, input, viewport, .. + } = tool_action_data; let ToolMessage::Eyedropper(event) = event else { return self }; match (self, event) { @@ -97,7 +99,8 @@ impl Fsm for EyedropperToolFsmState { } // Sampling -> Sampling (EyedropperToolFsmState::SamplingPrimary | EyedropperToolFsmState::SamplingSecondary, EyedropperToolMessage::PointerMove) => { - if input.viewport_bounds.in_bounds(input.mouse.position) { + let mouse_position = viewport.logical(input.mouse.position); + if viewport.is_in_bounds(mouse_position) { update_cursor_preview(responses, input, global_tool_data, None); } else { disable_cursor_preview(responses); diff --git a/editor/src/messages/tool/tool_messages/fill_tool.rs b/editor/src/messages/tool/tool_messages/fill_tool.rs index 228ab5a5f3..97ace27089 100644 --- a/editor/src/messages/tool/tool_messages/fill_tool.rs +++ b/editor/src/messages/tool/tool_messages/fill_tool.rs @@ -94,7 +94,11 @@ impl Fsm for FillToolFsmState { responses: &mut VecDeque, ) -> Self { let ToolActionMessageContext { - document, global_tool_data, input, .. + document, + global_tool_data, + input, + viewport, + .. } = handler_data; let ToolMessage::Fill(event) = event else { return self }; @@ -105,7 +109,7 @@ impl Fsm for FillToolFsmState { let preview_color = if use_secondary { global_tool_data.secondary_color } else { global_tool_data.primary_color }; // Get the layer the user is hovering over - if let Some(layer) = document.click(input) { + if let Some(layer) = document.click(input, viewport) { overlay_context.fill_path_pattern(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer), &preview_color); } @@ -117,7 +121,7 @@ impl Fsm for FillToolFsmState { self } (FillToolFsmState::Ready, color_event) => { - let Some(layer_identifier) = document.click(input) else { + let Some(layer_identifier) = document.click(input, viewport) else { return self; }; // If the layer is a raster layer, don't fill it, wait till the flood fill tool is implemented diff --git a/editor/src/messages/tool/tool_messages/freehand_tool.rs b/editor/src/messages/tool/tool_messages/freehand_tool.rs index f33b043563..3a0664c9a7 100644 --- a/editor/src/messages/tool/tool_messages/freehand_tool.rs +++ b/editor/src/messages/tool/tool_messages/freehand_tool.rs @@ -237,6 +237,7 @@ impl Fsm for FreehandToolFsmState { input, shape_editor, preferences, + viewport, .. } = tool_action_data; @@ -283,7 +284,7 @@ impl Fsm for FreehandToolFsmState { responses.add(DocumentMessage::DeselectAllLayers); - let parent = document.new_layer_bounding_artboard(input); + let parent = document.new_layer_bounding_artboard(input, viewport); let node_type = resolve_document_node_type("Path").expect("Path node does not exist"); let node = node_type.default_node_template(); diff --git a/editor/src/messages/tool/tool_messages/gradient_tool.rs b/editor/src/messages/tool/tool_messages/gradient_tool.rs index 848dfaf60f..eb974ae454 100644 --- a/editor/src/messages/tool/tool_messages/gradient_tool.rs +++ b/editor/src/messages/tool/tool_messages/gradient_tool.rs @@ -255,7 +255,11 @@ impl Fsm for GradientToolFsmState { responses: &mut VecDeque, ) -> Self { let ToolActionMessageContext { - document, global_tool_data, input, .. + document, + global_tool_data, + input, + viewport, + .. } = tool_action_data; let ToolMessage::Gradient(event) = event else { return self }; @@ -420,7 +424,7 @@ impl Fsm for GradientToolFsmState { let gradient_state = if dragging { GradientToolFsmState::Drawing } else { - let selected_layer = document.click(input); + let selected_layer = document.click(input, viewport); // Apply the gradient to the selected layer if let Some(layer) = selected_layer { @@ -464,13 +468,13 @@ impl Fsm for GradientToolFsmState { GradientToolMessage::PointerOutsideViewport { constrain_axis }.into(), GradientToolMessage::PointerMove { constrain_axis }.into(), ]; - tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); + tool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses); GradientToolFsmState::Drawing } (GradientToolFsmState::Drawing, GradientToolMessage::PointerOutsideViewport { .. }) => { // Auto-panning - if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) { + if let Some(shift) = tool_data.auto_panning.shift_viewport(input, viewport, responses) { if let Some(selected_gradient) = &mut tool_data.selected_gradient { selected_gradient.transform.translation += shift; } @@ -494,7 +498,7 @@ impl Fsm for GradientToolFsmState { let was_dragging = tool_data.selected_gradient.is_some(); if !was_dragging { - if let Some(selected_layer) = document.click(input) { + if let Some(selected_layer) = document.click(input, viewport) { if let Some(gradient) = get_gradient(selected_layer, &document.network_interface) { tool_data.selected_gradient = Some(SelectedGradient::new(gradient, selected_layer, document)); } diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index 96b05ec0cb..4d76501940 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -701,6 +701,7 @@ impl PathToolData { shape_editor: &mut ShapeState, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, + viewport: &ViewportMessageHandler, responses: &mut VecDeque, extend_selection: bool, lasso_select: bool, @@ -871,7 +872,7 @@ impl PathToolData { } } // If no other layers are selected and this is a single-click, then also select the layer (exception) - else if let Some(layer) = document.click(input) { + else if let Some(layer) = document.click(input, viewport) { if shape_editor.selected_shape_state.is_empty() { self.first_selected_with_single_click = true; // This ensures we don't need to double click a second time to get the drill through to work @@ -1109,8 +1110,9 @@ impl PathToolData { handle_position: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, + viewport: &ViewportMessageHandler, ) -> DVec2 { - let snap_data = SnapData::new(document, input); + let snap_data = SnapData::new(document, input, viewport); let snap_point = SnapCandidatePoint::handle_neighbors(new_handle_position, [anchor_position]); let snap_result = match using_angle_constraints { @@ -1380,6 +1382,7 @@ impl PathToolData { shape_editor: &mut ShapeState, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, + viewport: &ViewportMessageHandler, responses: &mut VecDeque, ) { // First check if selection is not just a single handle point @@ -1430,9 +1433,10 @@ impl PathToolData { handle_position, document, input, + viewport, ) } else { - shape_editor.snap(&mut self.snap_manager, &self.snap_cache, document, input, previous_mouse) + shape_editor.snap(&mut self.snap_manager, &self.snap_cache, document, input, viewport, previous_mouse) }; let handle_lengths = if equidistant { None } else { self.opposing_handle_lengths.take() }; @@ -1538,7 +1542,13 @@ impl Fsm for PathToolFsmState { tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { - let ToolActionMessageContext { document, input, shape_editor, .. } = tool_action_data; + let ToolActionMessageContext { + document, + input, + viewport, + shape_editor, + .. + } = tool_action_data; update_dynamic_hints(self, responses, shape_editor, document, tool_data, tool_options, input.mouse.position); @@ -1939,13 +1949,13 @@ impl Fsm for PathToolFsmState { } } Self::Dragging(_) => { - tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); + tool_data.snap_manager.draw_overlays(SnapData::new(document, input, viewport), &mut overlay_context); // Draw the snapping axis lines if tool_data.snapping_axis.is_some() { let Some(axis) = tool_data.snapping_axis else { return self }; let origin = tool_data.drag_start_pos; - let viewport_diagonal = input.viewport_bounds.size().length(); + let viewport_diagonal = viewport.size().into_dvec2().length(); let faded = |color: &str| { let mut color = graphene_std::Color::from_rgb_str(color.strip_prefix('#').unwrap()).unwrap().with_alpha(0.25).to_rgba_hex_srgb(); @@ -1996,6 +2006,7 @@ impl Fsm for PathToolFsmState { shape_editor, document, input, + viewport, responses, extend_selection, lasso_select, @@ -2056,7 +2067,7 @@ impl Fsm for PathToolFsmState { } .into(), ]; - tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); + tool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses); PathToolFsmState::Drawing { selection_shape } } @@ -2144,6 +2155,7 @@ impl Fsm for PathToolFsmState { tool_action_data.shape_editor, tool_action_data.document, input, + viewport, responses, ); } @@ -2173,7 +2185,7 @@ impl Fsm for PathToolFsmState { } .into(), ]; - tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); + tool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses); PathToolFsmState::Dragging(tool_data.dragging_state) } @@ -2203,7 +2215,7 @@ impl Fsm for PathToolFsmState { // When moving the cursor around we want to update the hovered layers let new_hovered_layers: Vec = document - .click_list_no_parents(input) + .click_list_no_parents(input, viewport) .filter(|&layer| { // Filter out artboards and parent holders, and already selected layers !document.network_interface.is_artboard(&layer.to_node(), &[]) @@ -2220,7 +2232,7 @@ impl Fsm for PathToolFsmState { } (PathToolFsmState::Drawing { selection_shape: selection_type }, PathToolMessage::PointerOutsideViewport { .. }) => { // Auto-panning - if let Some(offset) = tool_data.auto_panning.shift_viewport(input, responses) { + if let Some(offset) = tool_data.auto_panning.shift_viewport(input, viewport, responses) { tool_data.drag_start_pos += offset; } @@ -2228,7 +2240,7 @@ impl Fsm for PathToolFsmState { } (PathToolFsmState::Dragging(dragging_state), PathToolMessage::PointerOutsideViewport { .. }) => { // Auto-panning - if let Some(offset) = tool_data.auto_panning.shift_viewport(input, responses) { + if let Some(offset) = tool_data.auto_panning.shift_viewport(input, viewport, responses) { tool_data.drag_start_pos += offset; } @@ -2388,7 +2400,7 @@ impl Fsm for PathToolFsmState { if tool_data.drag_start_pos.distance(previous_mouse) < 1e-8 { // Clicked inside or outside the shape then deselect all of the points/segments - if document.click(input).is_some() && tool_data.stored_selection.is_none() { + if document.click(input, viewport).is_some() && tool_data.stored_selection.is_none() { tool_data.stored_selection = Some(shape_editor.selected_shape_state.clone()); } @@ -2941,7 +2953,7 @@ impl Fsm for PathToolFsmState { let nearest_point = shape_editor.find_nearest_point_indices(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD); let mut get_drill_through_layer = || -> Option { - let drill_through_layers = document.click_list_no_parents(input).collect::>(); + let drill_through_layers = document.click_list_no_parents(input, viewport).collect::>(); if drill_through_layers.is_empty() { tool_data.reset_drill_through_cycle(); None diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 0abab93f48..445c12b8f6 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -612,7 +612,7 @@ impl PenToolData { self.handle_end = None; self.handle_mode = HandleMode::Free; - self.store_clicked_endpoint(document, &transform, snap_data.input, preferences); + self.store_clicked_endpoint(document, &transform, snap_data.input, snap_data.viewport, preferences); if self.modifiers.lock_angle { self.set_lock_angle(&vector, id, self.prior_segment); @@ -668,7 +668,7 @@ impl PenToolData { self.handle_end_offset = None; self.path_closed = true; self.next_handle_start = self.next_point; - self.store_clicked_endpoint(document, transform, snap_data.input, preferences); + self.store_clicked_endpoint(document, transform, snap_data.input, snap_data.viewport, preferences); self.handle_mode = HandleMode::Free; if let (true, Some(prior_endpoint)) = (self.modifiers.lock_angle, self.prior_segment_endpoint) { self.set_lock_angle(vector, prior_endpoint, self.prior_segment); @@ -802,13 +802,14 @@ impl PenToolData { mouse: &DVec2, vector: &Vector, input: &InputPreprocessorMessageHandler, + viewport: &ViewportMessageHandler, ) -> Option { let reference_handle = if self.path_closed { TargetHandle::PreviewInHandle } else { TargetHandle::FuturePreviewOutHandle }; let end_handle = self.get_opposite_handle_type(reference_handle, vector); let end_handle_pos = self.target_handle_position(end_handle, vector); let ref_pos = self.target_handle_position(reference_handle, vector)?; let snap = &mut self.snap_manager; - let snap_data = SnapData::new_snap_cache(snap_data.document, input, &self.snap_cache); + let snap_data = SnapData::new_snap_cache(snap_data.document, input, viewport, &self.snap_cache); let handle_start_offset = self.handle_start_offset.unwrap_or(DVec2::ZERO); let document_pos = viewport_to_document.transform_point2(*mouse + handle_start_offset); @@ -964,6 +965,7 @@ impl PenToolData { } } + #[allow(clippy::too_many_arguments)] fn drag_handle( &mut self, snap_data: SnapData, @@ -972,6 +974,7 @@ impl PenToolData { responses: &mut VecDeque, layer: Option, input: &InputPreprocessorMessageHandler, + viewport: &ViewportMessageHandler, ) -> Option { let colinear = (self.handle_mode == HandleMode::ColinearEquidistant && self.modifiers.break_handle) || (self.handle_mode == HandleMode::ColinearLocked && !self.modifiers.break_handle); let document = snap_data.document; @@ -982,7 +985,7 @@ impl PenToolData { // Handles pressing Space to drag anchor and its handles if self.modifiers.move_anchor_with_handles { - let Some(delta) = self.space_anchor_handle_snap(&viewport_to_document, &transform, &snap_data, &mouse, &vector, input) else { + let Some(delta) = self.space_anchor_handle_snap(&viewport_to_document, &transform, &snap_data, &mouse, &vector, input, viewport) else { return Some(PenToolFsmState::DraggingHandle(self.handle_mode)); }; @@ -1221,6 +1224,7 @@ impl PenToolData { &mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, + viewport: &ViewportMessageHandler, responses: &mut VecDeque, tool_options: &PenOptions, append: bool, @@ -1228,21 +1232,21 @@ impl PenToolData { shape_editor: &mut ShapeState, ) { let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); - let snapped = self.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); - let viewport = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document); + let snapped = self.snap_manager.free_snap(&SnapData::new(document, input, viewport), &point, SnapTypeConfiguration::default()); + let viewport_vec = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document); self.handle_type = TargetHandle::FuturePreviewOutHandle; let selected_nodes = document.network_interface.selected_nodes(); self.handle_end = None; let tolerance = crate::consts::SNAP_POINT_TOLERANCE; - let extension_choice = should_extend(document, viewport, tolerance, selected_nodes.selected_layers(document.metadata()), preferences); + let extension_choice = should_extend(document, viewport_vec, tolerance, selected_nodes.selected_layers(document.metadata()), preferences); if let Some((layer, point, position)) = extension_choice { self.current_layer = Some(layer); self.extend_existing_path(document, layer, point, position); return; } else if preferences.vector_meshes { - if let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, viewport, tolerance) { + if let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, viewport_vec, tolerance) { let (point, segments) = closest_segment.adjusted_insert(responses); let layer = closest_segment.layer(); let position = closest_segment.closest_point_document(); @@ -1258,7 +1262,7 @@ impl PenToolData { } if append { - if let Some((layer, point, _)) = closest_point(document, viewport, tolerance, document.metadata().all_layers(), |_| false, preferences) { + if let Some((layer, point, _)) = closest_point(document, viewport_vec, tolerance, document.metadata().all_layers(), |_| false, preferences) { let vector = document.network_interface.compute_modified_vector(layer).unwrap(); let segment = vector.all_connected(point).collect::>().first().map(|s| s.segment); @@ -1271,12 +1275,12 @@ impl PenToolData { let existing_layer = selected_layers_except_artboards.next().filter(|_| selected_layers_except_artboards.next().is_none()); if let Some(layer) = existing_layer { // Add point to existing layer - responses.add(PenToolMessage::AddPointLayerPosition { layer, viewport }); + responses.add(PenToolMessage::AddPointLayerPosition { layer, viewport: viewport_vec }); return; } } - if let Some((layer, point, _position)) = closest_point(document, viewport, tolerance, document.metadata().all_layers(), |_| false, preferences) { + if let Some((layer, point, _position)) = closest_point(document, viewport_vec, tolerance, document.metadata().all_layers(), |_| false, preferences) { let vector = document.network_interface.compute_modified_vector(layer).unwrap(); let segment = vector.all_connected(point).collect::>().first().map(|s| s.segment); self.handle_mode = HandleMode::Free; @@ -1290,7 +1294,7 @@ impl PenToolData { let node_type = resolve_document_node_type("Path").expect("Path node does not exist"); let nodes = vec![(NodeId(0), node_type.default_node_template())]; - let parent = document.new_layer_bounding_artboard(input); + let parent = document.new_layer_bounding_artboard(input, viewport); let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, parent, responses); self.current_layer = Some(layer); tool_options.fill.apply_fill(layer, responses); @@ -1301,7 +1305,7 @@ impl PenToolData { // It is necessary to defer this until the transform of the layer can be accurately computed (quite hacky) responses.add(DeferMessage::AfterGraphRun { - messages: vec![PenToolMessage::AddPointLayerPosition { layer, viewport }.into()], + messages: vec![PenToolMessage::AddPointLayerPosition { layer, viewport: viewport_vec }.into()], }); responses.add(NodeGraphMessage::RunDocumentGraph); } @@ -1360,14 +1364,21 @@ impl PenToolData { } // Stores the segment and point ID of the clicked endpoint - fn store_clicked_endpoint(&mut self, document: &DocumentMessageHandler, transform: &DAffine2, input: &InputPreprocessorMessageHandler, preferences: &PreferencesMessageHandler) { + fn store_clicked_endpoint( + &mut self, + document: &DocumentMessageHandler, + transform: &DAffine2, + input: &InputPreprocessorMessageHandler, + viewport: &ViewportMessageHandler, + preferences: &PreferencesMessageHandler, + ) { let mut manipulators = HashMap::with_hasher(NoHashBuilder); let mut unselected = Vec::new(); let mut layer_manipulators = HashSet::with_hasher(NoHashBuilder); let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); - let snapped = self.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); + let snapped = self.snap_manager.free_snap(&SnapData::new(document, input, viewport), &point, SnapTypeConfiguration::default()); let viewport = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document); let tolerance = crate::consts::SNAP_POINT_TOLERANCE; @@ -1456,6 +1467,7 @@ impl Fsm for PenToolFsmState { input, shape_editor, preferences, + viewport, .. } = tool_action_data; let selected_nodes = document.network_interface.selected_nodes(); @@ -1613,19 +1625,19 @@ impl Fsm for PenToolFsmState { // If not check if there is a closest segment within threshold, if yes then draw overlay let tolerance = crate::consts::SNAP_POINT_TOLERANCE; let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); - let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); - let viewport = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document); + let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input, viewport), &point, SnapTypeConfiguration::default()); + let viewport_vec = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document); - let close_to_point = closest_point(document, viewport, tolerance, document.metadata().all_layers(), |_| false, preferences).is_some(); + let close_to_point = closest_point(document, viewport_vec, tolerance, document.metadata().all_layers(), |_| false, preferences).is_some(); if preferences.vector_meshes && !close_to_point { - if let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, viewport, tolerance) { + if let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, viewport_vec, tolerance) { let pos = closest_segment.closest_point_to_viewport(); let perp = closest_segment.calculate_perp(document); overlay_context.manipulator_anchor(pos, true, None); overlay_context.line(pos - perp * SEGMENT_OVERLAY_SIZE, pos + perp * SEGMENT_OVERLAY_SIZE, Some(COLOR_OVERLAY_BLUE), None); } } - tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); + tool_data.snap_manager.draw_overlays(SnapData::new(document, input, viewport), &mut overlay_context); self } (_, PenToolMessage::Overlays { context: mut overlay_context }) => { @@ -1743,7 +1755,7 @@ impl Fsm for PenToolFsmState { if self == PenToolFsmState::PlacingAnchor && preferences.vector_meshes { let tolerance = crate::consts::SNAP_POINT_TOLERANCE; let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); - let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); + let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input, viewport), &point, SnapTypeConfiguration::default()); let viewport = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document); let close_to_point = closest_point(document, viewport, tolerance, document.metadata().all_layers(), |_| false, preferences).is_some(); if !close_to_point { @@ -1805,7 +1817,7 @@ impl Fsm for PenToolFsmState { } // Draw the overlays that visualize current snapping - tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); + tool_data.snap_manager.draw_overlays(SnapData::new(document, input, viewport), &mut overlay_context); self } @@ -1822,8 +1834,8 @@ impl Fsm for PenToolFsmState { // Get the closest point and the segment it is on let append = input.keyboard.key(append_to_selected); - tool_data.store_clicked_endpoint(document, &transform, input, preferences); - tool_data.create_initial_point(document, input, responses, tool_options, append, preferences, shape_editor); + tool_data.store_clicked_endpoint(document, &transform, input, viewport, preferences); + tool_data.create_initial_point(document, input, viewport, responses, tool_options, append, preferences, shape_editor); // Enter the dragging handle state while the mouse is held down, allowing the user to move the mouse and position the handle PenToolFsmState::DraggingHandle(tool_data.handle_mode) @@ -1839,16 +1851,16 @@ impl Fsm for PenToolFsmState { } (PenToolFsmState::PlacingAnchor, PenToolMessage::DragStart { append_to_selected }) => { let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); - let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); - let viewport = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document); + let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input, viewport), &point, SnapTypeConfiguration::default()); + let viewport_vec = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document); // Early return if the buffer was started and this message is being run again after the buffer (so that place_anchor updates the state with the newly merged vector) if tool_data.buffering_merged_vector { if let Some(layer) = layer { tool_data.buffering_merged_vector = false; tool_data.handle_mode = HandleMode::ColinearLocked; - tool_data.bend_from_previous_point(SnapData::new(document, input), transform, layer, preferences, shape_editor, responses); - tool_data.place_anchor(SnapData::new(document, input), transform, input.mouse.position, preferences, responses); + tool_data.bend_from_previous_point(SnapData::new(document, input, viewport), transform, layer, preferences, shape_editor, responses); + tool_data.place_anchor(SnapData::new(document, input, viewport), transform, input.mouse.position, preferences, responses); } tool_data.buffering_merged_vector = false; PenToolFsmState::DraggingHandle(tool_data.handle_mode) @@ -1863,7 +1875,7 @@ impl Fsm for PenToolFsmState { let layers = LayerNodeIdentifier::ROOT_PARENT .descendants(document.metadata()) .filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[])); - if let Some((other_layer, _, _)) = should_extend(document, viewport, crate::consts::SNAP_POINT_TOLERANCE, layers, preferences) { + if let Some((other_layer, _, _)) = should_extend(document, viewport_vec, crate::consts::SNAP_POINT_TOLERANCE, layers, preferences) { let selected_nodes = document.network_interface.selected_nodes(); let mut selected_layers = selected_nodes.selected_layers(document.metadata()); if let Some(current_layer) = selected_layers @@ -1893,7 +1905,7 @@ impl Fsm for PenToolFsmState { (PenToolFsmState::DraggingHandle(_), PenToolMessage::DragStop) => { tool_data.cleanup_target_selections(shape_editor, layer, document, responses); tool_data - .finish_placing_handle(SnapData::new(document, input), transform, preferences, responses) + .finish_placing_handle(SnapData::new(document, input, viewport), transform, preferences, responses) .unwrap_or(PenToolFsmState::PlacingAnchor) } ( @@ -1914,7 +1926,7 @@ impl Fsm for PenToolFsmState { move_anchor_with_handles: input.keyboard.key(move_anchor_with_handles), }; - let snap_data = SnapData::new(document, input); + let snap_data = SnapData::new(document, input, viewport); if tool_data.modifiers.colinear && !tool_data.toggle_colinear_debounce { tool_data.handle_mode = match tool_data.handle_mode { HandleMode::Free => { @@ -1962,7 +1974,7 @@ impl Fsm for PenToolFsmState { } let state = tool_data - .drag_handle(snap_data, transform, input.mouse.position, responses, layer, input) + .drag_handle(snap_data, transform, input.mouse.position, responses, layer, input, viewport) .unwrap_or(PenToolFsmState::Ready); if tool_data.handle_swapped { @@ -1988,7 +2000,7 @@ impl Fsm for PenToolFsmState { } .into(), ]; - tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); + tool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses); state } @@ -2012,7 +2024,7 @@ impl Fsm for PenToolFsmState { move_anchor_with_handles: input.keyboard.key(move_anchor_with_handles), }; let state = tool_data - .place_anchor(SnapData::new(document, input), transform, input.mouse.position, preferences, responses) + .place_anchor(SnapData::new(document, input, viewport), transform, input.mouse.position, preferences, responses) .unwrap_or(PenToolFsmState::Ready); // Auto-panning @@ -2034,7 +2046,7 @@ impl Fsm for PenToolFsmState { } .into(), ]; - tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); + tool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses); state } @@ -2063,13 +2075,13 @@ impl Fsm for PenToolFsmState { colinear: input.keyboard.key(colinear), move_anchor_with_handles: input.keyboard.key(move_anchor_with_handles), }; - tool_data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position); + tool_data.snap_manager.preview_draw(&SnapData::new(document, input, viewport), input.mouse.position); responses.add(OverlaysMessage::Draw); self } (PenToolFsmState::DraggingHandle(mode), PenToolMessage::PointerOutsideViewport { .. }) => { // Auto-panning - let _ = tool_data.auto_panning.shift_viewport(input, responses); + let _ = tool_data.auto_panning.shift_viewport(input, viewport, responses); PenToolFsmState::DraggingHandle(mode) } @@ -2078,7 +2090,7 @@ impl Fsm for PenToolFsmState { return self; } // Auto-panning - let _ = tool_data.auto_panning.shift_viewport(input, responses); + let _ = tool_data.auto_panning.shift_viewport(input, viewport, responses); PenToolFsmState::PlacingAnchor } @@ -2119,7 +2131,7 @@ impl Fsm for PenToolFsmState { // Confirm to end path if let Some((vector, layer)) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)).zip(layer) { let single_point_in_layer = vector.point_domain.ids().len() == 1; - tool_data.finish_placing_handle(SnapData::new(document, input), transform, preferences, responses); + tool_data.finish_placing_handle(SnapData::new(document, input, viewport), transform, preferences, responses); let latest_points = tool_data.latest_points.len() == 1; if latest_points && single_point_in_layer { @@ -2166,7 +2178,7 @@ impl Fsm for PenToolFsmState { PenToolFsmState::Ready } else { tool_data - .place_anchor(SnapData::new(document, input), transform, input.mouse.position, preferences, responses) + .place_anchor(SnapData::new(document, input, viewport), transform, input.mouse.position, preferences, responses) .unwrap_or(PenToolFsmState::Ready) } } @@ -2197,7 +2209,7 @@ impl Fsm for PenToolFsmState { if tool_data.point_index > 0 { tool_data.point_index -= 1; tool_data - .place_anchor(SnapData::new(document, input), transform, input.mouse.position, preferences, responses) + .place_anchor(SnapData::new(document, input, viewport), transform, input.mouse.position, preferences, responses) .unwrap_or(PenToolFsmState::PlacingAnchor) } else { responses.add(PenToolMessage::Abort); @@ -2206,7 +2218,7 @@ impl Fsm for PenToolFsmState { } (_, PenToolMessage::Redo) => { tool_data.point_index = (tool_data.point_index + 1).min(tool_data.latest_points.len().saturating_sub(1)); - tool_data.place_anchor(SnapData::new(document, input), transform, input.mouse.position, preferences, responses); + tool_data.place_anchor(SnapData::new(document, input, viewport), transform, input.mouse.position, preferences, responses); match tool_data.point_index { 0 => PenToolFsmState::Ready, _ => PenToolFsmState::PlacingAnchor, diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index e5ab5edb4e..3fcf2b5fb2 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -419,11 +419,11 @@ struct SelectToolData { } impl SelectToolData { - fn get_snap_candidates(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler) { + fn get_snap_candidates(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) { self.snap_candidates.clear(); for &layer in &self.layers_dragging { if (self.snap_candidates.len() as f64) < document.snapping_state.tolerance { - snapping::get_layer_snap_points(layer, &SnapData::new(document, input), &mut self.snap_candidates); + snapping::get_layer_snap_points(layer, &SnapData::new(document, input, viewport), &mut self.snap_candidates); } if let Some(bounds) = document.metadata().bounding_box_with_transform(layer, DAffine2::IDENTITY) { let quad = document.metadata().transform_to_document(layer) * Quad::from_box(bounds); @@ -463,20 +463,20 @@ impl SelectToolData { } } - pub fn intersect_lasso_no_artboards(&self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler) -> Vec { + pub fn intersect_lasso_no_artboards(&self, document: &DocumentMessageHandler, viewport: &ViewportMessageHandler) -> Vec { if self.lasso_polygon.len() < 2 { return Vec::new(); } let polygon = Subpath::from_anchors_linear(self.lasso_polygon.clone(), true); - document.intersect_polygon_no_artboards(polygon, input).collect() + document.intersect_polygon_no_artboards(polygon, viewport).collect() } - pub fn is_layer_inside_lasso_polygon(&self, layer: &LayerNodeIdentifier, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler) -> bool { + pub fn is_layer_inside_lasso_polygon(&self, layer: &LayerNodeIdentifier, document: &DocumentMessageHandler, viewport: &ViewportMessageHandler) -> bool { if self.lasso_polygon.len() < 2 { return false; } let polygon = Subpath::from_anchors_linear(self.lasso_polygon.clone(), true); - document.is_layer_fully_inside_polygon(layer, input, polygon) + document.is_layer_fully_inside_polygon(layer, viewport, polygon) } /// Duplicates the currently dragging layers. Called when Alt is pressed and the layers have not yet been duplicated. @@ -598,12 +598,18 @@ impl Fsm for SelectToolFsmState { type ToolOptions = (); fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionMessageContext, _tool_options: &(), responses: &mut VecDeque) -> Self { - let ToolActionMessageContext { document, input, font_cache, .. } = tool_action_data; + let ToolActionMessageContext { + document, + input, + viewport, + font_cache, + .. + } = tool_action_data; let ToolMessage::Select(event) = event else { return self }; match (self, event) { (_, SelectToolMessage::Overlays { context: mut overlay_context }) => { - tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); + tool_data.snap_manager.draw_overlays(SnapData::new(document, input, viewport), &mut overlay_context); let selected_layers_count = document.network_interface.selected_nodes().selected_unlocked_layers(&document.network_interface).count(); tool_data.selected_layers_changed = selected_layers_count != tool_data.selected_layers_count; @@ -661,7 +667,7 @@ impl Fsm for SelectToolFsmState { if !matches!(self, Self::Drawing { .. }) && !input.keyboard.get(Key::MouseMiddle as usize) { // Get the layer the user is hovering over // Artboards are included since they're needed for quick measurement, but will be filtered out for selection later on - let click = document.click_list_with_artboards(input).last(); + let click = document.click_list_with_artboards(input, viewport).last(); let not_selected_click = click.filter(|&hovered_layer| !document.network_interface.selected_nodes().selected_layers_contains(hovered_layer, document.metadata())); if let Some(layer) = not_selected_click { if overlay_context.visibility_settings.hover_outline() && !document.network_interface.is_artboard(&layer.to_node(), &[]) { @@ -896,7 +902,7 @@ impl Fsm for SelectToolFsmState { _ => unreachable!(), }; - let viewport_diagonal = input.viewport_bounds.size().length(); + let viewport_diagonal = viewport.size().into_dvec2().length(); let color = if !hover { color @@ -918,7 +924,7 @@ impl Fsm for SelectToolFsmState { let extension = tool_data.drag_current - tool_data.drag_start; let origin = compass_center - extension; - let viewport_diagonal = input.viewport_bounds.size().length(); + let viewport_diagonal = viewport.size().into_dvec2().length(); let edge = DVec2::from_angle(snapped_angle).normalize_or(DVec2::X) * viewport_diagonal; let perp = edge.perp(); @@ -949,13 +955,13 @@ impl Fsm for SelectToolFsmState { // Draw outline visualizations on the layers to be selected let intersected_layers = match selection_shape { - SelectionShapeType::Box => document.intersect_quad_no_artboards(quad, input).collect(), - SelectionShapeType::Lasso => tool_data.intersect_lasso_no_artboards(document, input), + SelectionShapeType::Box => document.intersect_quad_no_artboards(quad, viewport).collect(), + SelectionShapeType::Lasso => tool_data.intersect_lasso_no_artboards(document, viewport), }; let layers_to_outline = intersected_layers.into_iter().filter(|layer| match current_selection_mode { SelectionMode::Enclosed => match selection_shape { SelectionShapeType::Box => document.is_layer_fully_inside(layer, quad), - SelectionShapeType::Lasso => tool_data.is_layer_inside_lasso_polygon(layer, document, input), + SelectionShapeType::Lasso => tool_data.is_layer_inside_lasso_polygon(layer, document, viewport), }, SelectionMode::Touched => match tool_data.nested_selection_behavior { NestedSelectionBehavior::Deepest => !layer.has_children(document.metadata()), @@ -1008,7 +1014,7 @@ impl Fsm for SelectToolFsmState { self } (_, SelectToolMessage::EditLayerExec) => { - if let Some(intersect) = document.click(input) { + if let Some(intersect) = document.click(input, viewport) { match tool_data.nested_selection_behavior { NestedSelectionBehavior::Shallowest => edit_layer_shallowest_manipulation(document, intersect, responses), NestedSelectionBehavior::Deepest => edit_layer_deepest_manipulation(intersect, &document.network_interface, responses), @@ -1031,7 +1037,7 @@ impl Fsm for SelectToolFsmState { tool_data.selection_mode = None; let mut selected: Vec<_> = document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface).collect(); - let intersection_list = document.click_list(input).collect::>(); + let intersection_list = document.click_list(input, viewport).collect::>(); let intersection = document.find_deepest(&intersection_list); let position = tool_data.pivot_gizmo().position(document); @@ -1064,10 +1070,10 @@ impl Fsm for SelectToolFsmState { } // Dragging one (or two, forming a corner) of the transform cage bounding box edges else if resize { - tool_data.get_snap_candidates(document, input); + tool_data.get_snap_candidates(document, input, viewport); SelectToolFsmState::ResizingBounds } else if skew { - tool_data.get_snap_candidates(document, input); + tool_data.get_snap_candidates(document, input, viewport); SelectToolFsmState::SkewingBounds { skew: Key::Control } } // Dragging the selected layers around to transform them @@ -1081,7 +1087,7 @@ impl Fsm for SelectToolFsmState { } tool_data.layers_dragging = selected; - tool_data.get_snap_candidates(document, input); + tool_data.get_snap_candidates(document, input, viewport); let (axis, using_compass) = { let axis_state = compass_rose_state.axis_type().filter(|_| can_grab_compass_rose); (axis_state.unwrap_or_default(), axis_state.is_some()) @@ -1124,7 +1130,7 @@ impl Fsm for SelectToolFsmState { NestedSelectionBehavior::Shallowest if !input.keyboard.key(select_deepest) => drag_shallowest_manipulation(responses, selected, tool_data, document, false, extend), _ => drag_deepest_manipulation(responses, selected, tool_data, document, false), } - tool_data.get_snap_candidates(document, input); + tool_data.get_snap_candidates(document, input, viewport); responses.add(DocumentMessage::StartTransaction); @@ -1177,7 +1183,7 @@ impl Fsm for SelectToolFsmState { let layers_exist = tool_data.layers_dragging.iter().all(|&layer| document.metadata().click_targets(layer).is_some()); let ignore = tool_data.non_duplicated_layers.as_ref().filter(|_| !layers_exist).unwrap_or(&tool_data.layers_dragging); - let snap_data = SnapData::ignore(document, input, ignore); + let snap_data = SnapData::ignore(document, input, viewport, ignore); let (start, current) = (tool_data.drag_start, tool_data.drag_current); let e0 = tool_data .bounding_box_manager @@ -1208,7 +1214,7 @@ impl Fsm for SelectToolFsmState { SelectToolMessage::PointerOutsideViewport { modifier_keys: modifier_keys.clone() }.into(), SelectToolMessage::PointerMove { modifier_keys }.into(), ]; - tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); + tool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses); SelectToolFsmState::Dragging { axis, @@ -1228,6 +1234,7 @@ impl Fsm for SelectToolFsmState { &mut tool_data.snap_manager, &mut tool_data.snap_candidates, input, + viewport, input.keyboard.key(modifier_keys.center), input.keyboard.key(modifier_keys.axis_align), ToolType::Select, @@ -1236,7 +1243,7 @@ impl Fsm for SelectToolFsmState { SelectToolMessage::PointerOutsideViewport { modifier_keys: modifier_keys.clone() }.into(), SelectToolMessage::PointerMove { modifier_keys }.into(), ]; - tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); + tool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses); } SelectToolFsmState::ResizingBounds } @@ -1283,7 +1290,7 @@ impl Fsm for SelectToolFsmState { SelectToolMessage::PointerOutsideViewport { modifier_keys: modifier_keys.clone() }.into(), SelectToolMessage::PointerMove { modifier_keys }.into(), ]; - tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); + tool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses); SelectToolFsmState::DraggingPivot } @@ -1304,7 +1311,7 @@ impl Fsm for SelectToolFsmState { SelectToolMessage::PointerOutsideViewport { modifier_keys: modifier_keys.clone() }.into(), SelectToolMessage::PointerMove { modifier_keys }.into(), ]; - tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); + tool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses); SelectToolFsmState::Drawing { selection_shape, has_drawn: true } } @@ -1347,7 +1354,7 @@ impl Fsm for SelectToolFsmState { SelectToolMessage::PointerOutsideViewport { .. }, ) => { // Auto-panning - if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) { + if let Some(shift) = tool_data.auto_panning.shift_viewport(input, viewport, responses) { tool_data.drag_current += shift; tool_data.drag_start += shift; } @@ -1362,7 +1369,7 @@ impl Fsm for SelectToolFsmState { } (SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. }, SelectToolMessage::PointerOutsideViewport { .. }) => { // Auto-panning - if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) { + if let Some(shift) = tool_data.auto_panning.shift_viewport(input, viewport, responses) { if let Some(bounds) = &mut tool_data.bounding_box_manager { bounds.center_of_transformation += shift; bounds.original_bound_transform.translation += shift; @@ -1373,13 +1380,13 @@ impl Fsm for SelectToolFsmState { } (SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerOutsideViewport { .. }) => { // Auto-panning - let _ = tool_data.auto_panning.shift_viewport(input, responses); + let _ = tool_data.auto_panning.shift_viewport(input, viewport, responses); self } (SelectToolFsmState::Drawing { .. }, SelectToolMessage::PointerOutsideViewport { .. }) => { // Auto-panning - if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) { + if let Some(shift) = tool_data.auto_panning.shift_viewport(input, viewport, responses) { tool_data.drag_start += shift; } @@ -1403,7 +1410,7 @@ impl Fsm for SelectToolFsmState { if !has_dragged && input.keyboard.key(remove_from_selection) && tool_data.layer_selected_on_start.is_none() { // When you click on the layer with remove from selection key (shift) pressed, we deselect all nodes that are children. let quad = tool_data.selection_quad(); - let intersection = document.intersect_quad_no_artboards(quad, input); + let intersection = document.intersect_quad_no_artboards(quad, viewport); if let Some(path) = intersection.last() { let replacement_selected_layers: Vec<_> = document @@ -1433,7 +1440,7 @@ impl Fsm for SelectToolFsmState { } else if tool_data.select_single_layer.take().is_some() { // Previously, we may have had many layers selected. If the user clicks without dragging, we should just select the one layer that has been clicked. if !has_dragged { - let selected = document.click_list(input).collect::>(); + let selected = document.click_list(input, viewport).collect::>(); let intersection = document.find_deepest(&selected); if let Some(intersection) = intersection { tool_data.layer_selected_on_start = Some(intersection); @@ -1449,7 +1456,7 @@ impl Fsm for SelectToolFsmState { } } - tool_data.get_snap_candidates(document, input); + tool_data.get_snap_candidates(document, input, viewport); } } } @@ -1510,13 +1517,13 @@ impl Fsm for SelectToolFsmState { }; let intersection: Vec = match selection_shape { - SelectionShapeType::Box => document.intersect_quad_no_artboards(quad, input).collect(), - SelectionShapeType::Lasso => tool_data.intersect_lasso_no_artboards(document, input), + SelectionShapeType::Box => document.intersect_quad_no_artboards(quad, viewport).collect(), + SelectionShapeType::Lasso => tool_data.intersect_lasso_no_artboards(document, viewport), }; let new_selected: HashSet<_> = if selection_mode == SelectionMode::Enclosed { let is_inside = |layer: &LayerNodeIdentifier| match selection_shape { SelectionShapeType::Box => document.is_layer_fully_inside(layer, quad), - SelectionShapeType::Lasso => tool_data.is_layer_inside_lasso_polygon(layer, document, input), + SelectionShapeType::Lasso => tool_data.is_layer_inside_lasso_polygon(layer, document, viewport), }; intersection.into_iter().filter(is_inside).collect() } else { diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index 76a123cb3a..235c830c74 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -501,11 +501,11 @@ pub struct ShapeToolData { } impl ShapeToolData { - fn get_snap_candidates(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler) { + fn get_snap_candidates(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) { self.snap_candidates.clear(); for &layer in &self.layers_dragging { if (self.snap_candidates.len() as f64) < document.snapping_state.tolerance { - snapping::get_layer_snap_points(layer, &SnapData::new(document, input), &mut self.snap_candidates); + snapping::get_layer_snap_points(layer, &SnapData::new(document, input, viewport), &mut self.snap_candidates); } if let Some(bounds) = document.metadata().bounding_box_with_transform(layer, DAffine2::IDENTITY) { let quad = document.metadata().transform_to_document(layer) * Quad::from_box(bounds); @@ -558,6 +558,7 @@ impl Fsm for ShapeToolFsmState { input, preferences, shape_editor, + viewport, .. }: &mut ToolActionMessageContext, tool_options: &Self::ToolOptions, @@ -596,7 +597,7 @@ impl Fsm for ShapeToolFsmState { let hovering_over_gizmo = tool_data.gizmo_manager.hovering_over_gizmo(); if !matches!(self, ShapeToolFsmState::ModifyingGizmo) && !modifying_transform_cage && !hovering_over_gizmo { - tool_data.data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); + tool_data.data.snap_manager.draw_overlays(SnapData::new(document, input, viewport), &mut overlay_context); } if modifying_transform_cage && !matches!(self, ShapeToolFsmState::ModifyingGizmo) { @@ -783,7 +784,7 @@ impl Fsm for ShapeToolFsmState { match (resize, rotate, skew) { (true, false, false) => { - tool_data.get_snap_candidates(document, input); + tool_data.get_snap_candidates(document, input, viewport); update_cursor_and_pointer(tool_data, responses); return ShapeToolFsmState::ResizingBounds; @@ -795,7 +796,7 @@ impl Fsm for ShapeToolFsmState { return ShapeToolFsmState::RotatingBounds; } (false, false, true) => { - tool_data.get_snap_candidates(document, input); + tool_data.get_snap_candidates(document, input, viewport); update_cursor_and_pointer(tool_data, responses); return ShapeToolFsmState::SkewingBounds { skew: Key::Control }; @@ -806,11 +807,14 @@ impl Fsm for ShapeToolFsmState { match tool_data.current_shape { ShapeType::Polygon | ShapeType::Star | ShapeType::Circle | ShapeType::Arc | ShapeType::Spiral | ShapeType::Grid | ShapeType::Rectangle | ShapeType::Ellipse => { - tool_data.data.start(document, input) + tool_data.data.start(document, input, viewport); } ShapeType::Line => { let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); - let snapped = tool_data.data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); + let snapped = tool_data + .data + .snap_manager + .free_snap(&SnapData::new(document, input, viewport), &point, SnapTypeConfiguration::default()); tool_data.data.drag_start = snapped.snapped_point_document; } } @@ -830,7 +834,7 @@ impl Fsm for ShapeToolFsmState { }; let nodes = vec![(NodeId(0), node)]; - let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, document.new_layer_bounding_artboard(input), responses); + let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, document.new_layer_bounding_artboard(input, viewport), responses); let defered_responses = &mut VecDeque::new(); @@ -868,20 +872,20 @@ impl Fsm for ShapeToolFsmState { }; match tool_data.current_shape { - ShapeType::Polygon => Polygon::update_shape(document, input, layer, tool_data, modifier, responses), - ShapeType::Star => Star::update_shape(document, input, layer, tool_data, modifier, responses), - ShapeType::Circle => Circle::update_shape(document, input, layer, tool_data, modifier, responses), - ShapeType::Arc => Arc::update_shape(document, input, layer, tool_data, modifier, responses), - ShapeType::Spiral => Spiral::update_shape(document, input, layer, tool_data, responses), + ShapeType::Polygon => Polygon::update_shape(document, input, viewport, layer, tool_data, modifier, responses), + ShapeType::Star => Star::update_shape(document, input, viewport, layer, tool_data, modifier, responses), + ShapeType::Circle => Circle::update_shape(document, input, viewport, layer, tool_data, modifier, responses), + ShapeType::Arc => Arc::update_shape(document, input, viewport, layer, tool_data, modifier, responses), + ShapeType::Spiral => Spiral::update_shape(document, input, viewport, layer, tool_data, responses), ShapeType::Grid => Grid::update_shape(document, input, layer, tool_options.grid_type, tool_data, modifier, responses), - ShapeType::Rectangle => Rectangle::update_shape(document, input, layer, tool_data, modifier, responses), - ShapeType::Ellipse => Ellipse::update_shape(document, input, layer, tool_data, modifier, responses), - ShapeType::Line => Line::update_shape(document, input, layer, tool_data, modifier, responses), + ShapeType::Rectangle => Rectangle::update_shape(document, input, viewport, layer, tool_data, modifier, responses), + ShapeType::Ellipse => Ellipse::update_shape(document, input, viewport, layer, tool_data, modifier, responses), + ShapeType::Line => Line::update_shape(document, input, viewport, layer, tool_data, modifier, responses), } // Auto-panning let messages = [ShapeToolMessage::PointerOutsideViewport { modifier }.into(), ShapeToolMessage::PointerMove { modifier }.into()]; - tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); + tool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses); self } @@ -890,10 +894,10 @@ impl Fsm for ShapeToolFsmState { return ShapeToolFsmState::Ready(tool_data.current_shape); }; - Line::update_shape(document, input, layer, tool_data, modifier, responses); + Line::update_shape(document, input, viewport, layer, tool_data, modifier, responses); // Auto-panning let messages = [ShapeToolMessage::PointerOutsideViewport { modifier }.into(), ShapeToolMessage::PointerMove { modifier }.into()]; - tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); + tool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses); self } @@ -915,11 +919,12 @@ impl Fsm for ShapeToolFsmState { &mut tool_data.data.snap_manager, &mut tool_data.snap_candidates, input, + viewport, input.keyboard.key(modifier[0]), input.keyboard.key(modifier[1]), ToolType::Shape, ); - tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); + tool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses); } responses.add(OverlaysMessage::Draw); @@ -974,14 +979,14 @@ impl Fsm for ShapeToolFsmState { responses.add(FrontendMessage::UpdateMouseCursor { cursor }); } - tool_data.data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position); + tool_data.data.snap_manager.preview_draw(&SnapData::new(document, input, viewport), input.mouse.position); responses.add(OverlaysMessage::Draw); self } (ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::SkewingBounds { .. }, ShapeToolMessage::PointerOutsideViewport { .. }) => { // Auto-panning - if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) + if let Some(shift) = tool_data.auto_panning.shift_viewport(input, viewport, responses) && let Some(bounds) = &mut tool_data.bounding_box_manager { bounds.center_of_transformation += shift; @@ -993,7 +998,7 @@ impl Fsm for ShapeToolFsmState { (ShapeToolFsmState::Ready(_), ShapeToolMessage::PointerOutsideViewport { .. }) => self, (_, ShapeToolMessage::PointerOutsideViewport { .. }) => { // Auto-panning - let _ = tool_data.auto_panning.shift_viewport(input, responses); + let _ = tool_data.auto_panning.shift_viewport(input, viewport, responses); self } ( diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index 0de3ac6ec9..04ff4decc8 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -267,12 +267,12 @@ impl SplineToolData { } /// Get the snapped point while ignoring current layer - fn snapped_point(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler) -> SnappedPoint { + fn snapped_point(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) -> SnappedPoint { let metadata = document.metadata(); let transform = self.current_layer.map_or(metadata.document_to_viewport, |layer| metadata.transform_to_viewport(layer)); let point = SnapCandidatePoint::handle(transform.inverse().transform_point2(input.mouse.position)); let ignore = if let Some(layer) = self.current_layer { vec![layer] } else { vec![] }; - let snap_data = SnapData::ignore(document, input, &ignore); + let snap_data = SnapData::ignore(document, input, viewport, &ignore); self.snap_manager.free_snap(&snap_data, &point, SnapTypeConfiguration::default()) } } @@ -295,6 +295,7 @@ impl Fsm for SplineToolFsmState { input, shape_editor, preferences, + viewport, .. } = tool_action_data; @@ -303,7 +304,7 @@ impl Fsm for SplineToolFsmState { (_, SplineToolMessage::CanvasTransformed) => self, (_, SplineToolMessage::Overlays { context: mut overlay_context }) => { path_endpoint_overlays(document, shape_editor, &mut overlay_context, preferences); - tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); + tool_data.snap_manager.draw_overlays(SnapData::new(document, input, viewport), &mut overlay_context); self } (SplineToolFsmState::MergingEndpoints, SplineToolMessage::MergeEndpoints) => { @@ -342,15 +343,15 @@ impl Fsm for SplineToolFsmState { tool_data.weight = tool_options.line_weight; let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); - let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); - let viewport = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document); + let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input, viewport), &point, SnapTypeConfiguration::default()); + let viewport_vec = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document); let layers = LayerNodeIdentifier::ROOT_PARENT .descendants(document.metadata()) .filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[])); // Extend an endpoint of the selected path - if let Some((layer, point, position)) = should_extend(document, viewport, SNAP_POINT_TOLERANCE, layers, preferences) { + if let Some((layer, point, position)) = should_extend(document, viewport_vec, SNAP_POINT_TOLERANCE, layers, preferences) { if find_spline(document, layer).is_some() { // If the point is the part of Spline then we extend it. tool_data.current_layer = Some(layer); @@ -386,7 +387,7 @@ impl Fsm for SplineToolFsmState { responses.add(DocumentMessage::DeselectAllLayers); - let parent = document.new_layer_bounding_artboard(input); + let parent = document.new_layer_bounding_artboard(input, viewport); let path_node_type = resolve_document_node_type("Path").expect("Path node does not exist"); let path_node = path_node_type.default_node_template(); @@ -410,7 +411,7 @@ impl Fsm for SplineToolFsmState { if tool_data.current_layer.is_none() { return SplineToolFsmState::Ready; }; - tool_data.next_point = tool_data.snapped_point(document, input).snapped_point_document; + tool_data.next_point = tool_data.snapped_point(document, input, viewport).snapped_point_document; if tool_data.points.last().is_none_or(|last_pos| last_pos.1.distance(tool_data.next_point) > DRAG_THRESHOLD) { let preview_point = tool_data.preview_point; extend_spline(tool_data, false, responses); @@ -433,7 +434,7 @@ impl Fsm for SplineToolFsmState { tool_data.next_point = point; tool_data.snap_manager.clear_indicator(); } else { - let snapped_point = tool_data.snapped_point(document, input); + let snapped_point = tool_data.snapped_point(document, input, viewport); tool_data.next_point = snapped_point.snapped_point_document; tool_data.snap_manager.update_indicator(snapped_point); } @@ -442,12 +443,12 @@ impl Fsm for SplineToolFsmState { // Auto-panning let messages = [SplineToolMessage::PointerOutsideViewport.into(), SplineToolMessage::PointerMove.into()]; - tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); + tool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses); SplineToolFsmState::Drawing } (_, SplineToolMessage::PointerMove) => { - tool_data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position); + tool_data.snap_manager.preview_draw(&SnapData::new(document, input, viewport), input.mouse.position); responses.add(OverlaysMessage::Draw); self } @@ -456,7 +457,7 @@ impl Fsm for SplineToolFsmState { return self; } // Auto-panning - let _ = tool_data.auto_panning.shift_viewport(input, responses); + let _ = tool_data.auto_panning.shift_viewport(input, viewport, responses); SplineToolFsmState::Drawing } diff --git a/editor/src/messages/tool/tool_messages/text_tool.rs b/editor/src/messages/tool/tool_messages/text_tool.rs index 90fbf6b831..867d5b3626 100644 --- a/editor/src/messages/tool/tool_messages/text_tool.rs +++ b/editor/src/messages/tool/tool_messages/text_tool.rs @@ -499,6 +499,7 @@ impl Fsm for TextToolFsmState { global_tool_data, input, font_cache, + viewport, .. } = transition_data; let fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()) @@ -509,9 +510,8 @@ impl Fsm for TextToolFsmState { let ToolMessage::Text(event) = event else { return self }; match (self, event) { (TextToolFsmState::Editing, TextToolMessage::Overlays { context: mut overlay_context }) => { - responses.add(FrontendMessage::DisplayEditableTextboxTransform { - transform: document.metadata().transform_to_viewport(tool_data.layer).to_cols_array(), - }); + let transform = document.metadata().transform_to_viewport(tool_data.layer).to_cols_array(); + responses.add(FrontendMessage::DisplayEditableTextboxTransform { transform }); if let Some(editing_text) = tool_data.editing_text.as_mut() { let far = graphene_std::text::bounding_box(&tool_data.new_text, &editing_text.font, font_cache, editing_text.typesetting, false); if far.x != 0. && far.y != 0. { @@ -529,7 +529,7 @@ impl Fsm for TextToolFsmState { let quad = Quad::from_box(tool_data.cached_resize_bounds); // Draw a bounding box on the layers to be selected - for layer in document.intersect_quad_no_artboards(quad, input) { + for layer in document.intersect_quad_no_artboards(quad, viewport) { overlay_context.quad( Quad::from_box(document.metadata().bounding_box_viewport(layer).unwrap_or([DVec2::ZERO; 2])), None, @@ -572,7 +572,7 @@ impl Fsm for TextToolFsmState { tool_data.bounding_box_manager.take(); } - tool_data.resize.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); + tool_data.resize.snap_manager.draw_overlays(SnapData::new(document, input, viewport), &mut overlay_context); self } @@ -585,7 +585,7 @@ impl Fsm for TextToolFsmState { state } (TextToolFsmState::Ready, TextToolMessage::DragStart) => { - tool_data.resize.start(document, input); + tool_data.resize.start(document, input, viewport); tool_data.cached_resize_bounds = [tool_data.resize.viewport_drag_start(document); 2]; tool_data.drag_start = input.mouse.position; tool_data.drag_current = input.mouse.position; @@ -659,7 +659,7 @@ impl Fsm for TextToolFsmState { TextToolFsmState::Ready } (TextToolFsmState::Placing, TextToolMessage::PointerMove { center, lock_ratio }) => { - tool_data.cached_resize_bounds = tool_data.resize.calculate_points_ignore_layer(document, input, center, lock_ratio, false); + tool_data.cached_resize_bounds = tool_data.resize.calculate_points_ignore_layer(document, input, viewport, center, lock_ratio, false); responses.add(OverlaysMessage::Draw); @@ -668,7 +668,7 @@ impl Fsm for TextToolFsmState { TextToolMessage::PointerOutsideViewport { center, lock_ratio }.into(), TextToolMessage::PointerMove { center, lock_ratio }.into(), ]; - tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); + tool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses); TextToolFsmState::Placing } @@ -691,7 +691,7 @@ impl Fsm for TextToolFsmState { TextToolMessage::PointerOutsideViewport { center, lock_ratio }.into(), TextToolMessage::PointerMove { center, lock_ratio }.into(), ]; - tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); + tool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses); } TextToolFsmState::Dragging @@ -713,7 +713,7 @@ impl Fsm for TextToolFsmState { let snap = Some(SizeSnapData { manager: &mut tool_data.resize.snap_manager, points: &mut tool_data.snap_candidates, - snap_data: SnapData::ignore(document, input, &selected), + snap_data: SnapData::ignore(document, input, viewport, &selected), }); let (position, size) = movement.new_size(input.mouse.position, bounds.original_bound_transform, center_position, constrain, snap); @@ -752,26 +752,26 @@ impl Fsm for TextToolFsmState { TextToolMessage::PointerOutsideViewport { center, lock_ratio }.into(), TextToolMessage::PointerMove { center, lock_ratio }.into(), ]; - tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses); + tool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses); } } TextToolFsmState::ResizingBounds } (_, TextToolMessage::PointerMove { .. }) => { - tool_data.resize.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position); + tool_data.resize.snap_manager.preview_draw(&SnapData::new(document, input, viewport), input.mouse.position); responses.add(OverlaysMessage::Draw); self } (TextToolFsmState::Placing, TextToolMessage::PointerOutsideViewport { .. }) => { // Auto-panning setup - let _ = tool_data.auto_panning.shift_viewport(input, responses); + let _ = tool_data.auto_panning.shift_viewport(input, viewport, responses); TextToolFsmState::Placing } (TextToolFsmState::ResizingBounds | TextToolFsmState::Dragging, TextToolMessage::PointerOutsideViewport { .. }) => { // Auto-panning - if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) { + if let Some(shift) = tool_data.auto_panning.shift_viewport(input, viewport, responses) { if let Some(bounds) = &mut tool_data.bounding_box_manager { bounds.center_of_transformation += shift; bounds.original_bound_transform.translation += shift; diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index e971c3f316..a39c2b4e9f 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -28,6 +28,7 @@ pub struct TransformLayerMessageContext<'a> { pub input: &'a InputPreprocessorMessageHandler, pub tool_data: &'a ToolData, pub shape_editor: &'a mut ShapeState, + pub viewport: &'a ViewportMessageHandler, } #[derive(Debug, Clone, Default, ExtractField)] @@ -77,6 +78,7 @@ impl MessageHandler> for input, tool_data, shape_editor, + viewport, } = context; let using_path_tool = tool_data.active_tool_type == ToolType::Path; @@ -184,7 +186,7 @@ impl MessageHandler> for } } - let viewport_box = input.viewport_bounds.size(); + let viewport_box = viewport.size().into_dvec2(); let axis_constraint = self.transform_operation.axis_constraint(); let format_rounded = |value: f64, precision: usize| { diff --git a/editor/src/messages/tool/utility_types.rs b/editor/src/messages/tool/utility_types.rs index b26e14d620..8b1e9c1898 100644 --- a/editor/src/messages/tool/utility_types.rs +++ b/editor/src/messages/tool/utility_types.rs @@ -28,6 +28,7 @@ pub struct ToolActionMessageContext<'a> { pub shape_editor: &'a mut ShapeState, pub node_graph: &'a NodeGraphExecutor, pub preferences: &'a PreferencesMessageHandler, + pub viewport: &'a ViewportMessageHandler, } pub trait ToolCommon: for<'a, 'b> MessageHandler> + LayoutHolder + ToolTransition + ToolMetadata {} diff --git a/editor/src/messages/viewport/mod.rs b/editor/src/messages/viewport/mod.rs new file mode 100644 index 0000000000..394d4aea15 --- /dev/null +++ b/editor/src/messages/viewport/mod.rs @@ -0,0 +1,7 @@ +mod viewport_message; +mod viewport_message_handler; + +#[doc(inline)] +pub use viewport_message::{ViewportMessage, ViewportMessageDiscriminant}; +#[doc(inline)] +pub use viewport_message_handler::*; diff --git a/editor/src/messages/viewport/viewport_message.rs b/editor/src/messages/viewport/viewport_message.rs new file mode 100644 index 0000000000..06c5af74c2 --- /dev/null +++ b/editor/src/messages/viewport/viewport_message.rs @@ -0,0 +1,8 @@ +use crate::messages::prelude::*; + +#[impl_message(Message, Viewport)] +#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] +pub enum ViewportMessage { + Update { x: f64, y: f64, width: f64, height: f64, scale: f64 }, + RepropagateUpdate, +} diff --git a/editor/src/messages/viewport/viewport_message_handler.rs b/editor/src/messages/viewport/viewport_message_handler.rs new file mode 100644 index 0000000000..d562917715 --- /dev/null +++ b/editor/src/messages/viewport/viewport_message_handler.rs @@ -0,0 +1,602 @@ +use std::ops::{Add, Div, Mul, Sub}; + +use crate::messages::prelude::*; +use crate::messages::tool::tool_messages::tool_prelude::DVec2; + +#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, specta::Type, ExtractField)] +pub struct ViewportMessageHandler { + bounds: Bounds, + // Ratio of logical pixels to physical pixels + scale: f64, +} +impl Default for ViewportMessageHandler { + fn default() -> Self { + Self { + bounds: Bounds { + offset: Point { x: 0.0, y: 0.0 }, + size: Point { x: 0.0, y: 0.0 }, + }, + scale: 1.0, + } + } +} + +#[message_handler_data] +impl MessageHandler for ViewportMessageHandler { + fn process_message(&mut self, message: ViewportMessage, responses: &mut VecDeque, _: ()) { + match message { + ViewportMessage::Update { x, y, width, height, scale } => { + assert_ne!(scale, 0.0, "Viewport scale cannot be zero"); + self.scale = scale; + + self.bounds = Bounds { + offset: Point { x, y }, + size: Point { x: width, y: height }, + }; + } + ViewportMessage::RepropagateUpdate => {} + } + + let physical_bounds = self.bounds().to_physical(); + responses.add(FrontendMessage::UpdateViewportPhysicalBounds { + x: physical_bounds.x(), + y: physical_bounds.y(), + width: physical_bounds.width(), + height: physical_bounds.height(), + }); + + responses.add(NavigationMessage::CanvasPan { delta: DVec2::ZERO }); + responses.add(NodeGraphMessage::SetGridAlignedEdges); + + responses.add(DeferMessage::AfterGraphRun { + messages: vec![ + DeferMessage::AfterGraphRun { + messages: vec![DeferMessage::TriggerNavigationReady.into()], + } + .into(), + ], + }); + } + + advertise_actions!(ViewportMessageDiscriminant;); +} + +impl ViewportMessageHandler { + pub fn scale(&self) -> f64 { + self.scale + } + + pub fn bounds(&self) -> LogicalBounds { + self.bounds.into_scaled(self.scale) + } + + pub fn offset(&self) -> LogicalPoint { + self.bounds.offset.into_scaled(self.scale) + } + + pub fn size(&self) -> LogicalPoint { + self.bounds.size().into_scaled(self.scale) + } + + #[expect(private_bounds)] + pub fn logical>(&self, point: T) -> LogicalPoint { + point.into().convert_to_logical(self.scale) + } + + #[expect(private_bounds)] + pub fn physical>(&self, point: T) -> PhysicalPoint { + point.into().convert_to_physical(self.scale) + } + + pub fn center_in_viewport_space(&self) -> LogicalPoint { + let size = self.size(); + LogicalPoint { + inner: Point { x: size.x() / 2.0, y: size.y() / 2.0 }, + scale: size.scale, + } + } + + pub fn center_in_window_space(&self) -> LogicalPoint { + let size = self.size(); + let offset = self.offset(); + LogicalPoint { + inner: Point { + x: (size.x() / 2.0) + offset.x(), + y: (size.y() / 2.0) + offset.y(), + }, + scale: size.scale, + } + } + + pub(crate) fn is_in_bounds(&self, point: LogicalPoint) -> bool { + point.x() >= self.bounds.x() && point.y() >= self.bounds.y() && point.x() <= self.bounds.x() + self.bounds.width() && point.y() <= self.bounds.y() + self.bounds.height() + } +} + +pub trait ToLogical + ?Sized> { + fn to_logical(self) -> L; +} +pub trait ToPhysical + ?Sized> { + fn to_physical(self) -> P; +} + +trait IntoScaled: Sized { + fn into_scaled(self, scale: f64) -> T; +} +trait FromWithScale: Sized { + fn from_with_scale(value: T, scale: f64) -> Self; +} +impl IntoScaled for T +where + U: FromWithScale, +{ + fn into_scaled(self, scale: f64) -> U { + U::from_with_scale(self, scale) + } +} + +trait AsPoint { + fn as_point(&self) -> Point; +} + +trait Scaled { + fn scale(&self) -> f64; +} + +pub trait Position { + fn x(&self) -> f64; + fn y(&self) -> f64; +} + +#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, specta::Type)] +struct Point { + x: f64, + y: f64, +} +impl Point { + fn convert_to_logical(&self, scale: f64) -> LogicalPoint { + Point { x: self.x(), y: self.y() }.into_scaled(scale) + } + fn convert_to_physical(&self, scale: f64) -> PhysicalPoint { + Point { + x: self.x() / scale, + y: self.y() / scale, + } + .into_scaled(scale) + } +} +impl Position for Point { + fn x(&self) -> f64 { + self.x + } + fn y(&self) -> f64 { + self.y + } +} + +#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, specta::Type)] +pub struct LogicalPoint { + inner: Point, + scale: f64, +} +impl AsPoint for LogicalPoint { + fn as_point(&self) -> Point { + self.inner + } +} +impl Scaled for LogicalPoint { + fn scale(&self) -> f64 { + self.scale + } +} +impl Position for LogicalPoint { + fn x(&self) -> f64 { + self.inner.x() + } + fn y(&self) -> f64 { + self.inner.y() + } +} +impl ToPhysical for LogicalPoint { + fn to_physical(self) -> PhysicalPoint { + PhysicalPoint { inner: self.inner, scale: self.scale } + } +} +impl FromWithScale for LogicalPoint { + fn from_with_scale(value: Point, scale: f64) -> Self { + Self { inner: value, scale } + } +} + +#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, specta::Type)] +pub struct PhysicalPoint { + inner: Point, + scale: f64, +} +impl AsPoint for PhysicalPoint { + fn as_point(&self) -> Point { + self.inner + } +} +impl Scaled for PhysicalPoint { + fn scale(&self) -> f64 { + self.scale + } +} +impl Position for PhysicalPoint { + fn x(&self) -> f64 { + self.inner.x() * self.scale + } + fn y(&self) -> f64 { + self.inner.y() * self.scale + } +} +impl ToLogical for PhysicalPoint { + fn to_logical(self) -> LogicalPoint { + LogicalPoint { inner: self.inner, scale: self.scale } + } +} +impl FromWithScale for PhysicalPoint { + fn from_with_scale(value: Point, scale: f64) -> Self { + Self { inner: value, scale } + } +} + +pub trait Rect: Position { + fn offset(&self) -> P; + fn size(&self) -> P; + fn width(&self) -> f64; + fn height(&self) -> f64; +} + +#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, specta::Type, ExtractField)] +struct Bounds { + offset: Point, + size: Point, +} +impl Position for Bounds { + fn x(&self) -> f64 { + self.offset.x() + } + fn y(&self) -> f64 { + self.offset.y() + } +} +impl Rect for Bounds { + fn offset(&self) -> Point { + self.offset + } + fn size(&self) -> Point { + self.size + } + fn width(&self) -> f64 { + self.size.x() + } + fn height(&self) -> f64 { + self.size.y() + } +} + +#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, specta::Type)] +pub struct LogicalBounds { + offset: Point, + size: Point, + scale: f64, +} +impl Scaled for LogicalBounds { + fn scale(&self) -> f64 { + self.scale + } +} +impl Position for LogicalBounds { + fn x(&self) -> f64 { + self.offset.x() + } + fn y(&self) -> f64 { + self.offset.y() + } +} +impl Rect for LogicalBounds { + fn offset(&self) -> LogicalPoint { + self.offset.into_scaled(self.scale) + } + fn size(&self) -> LogicalPoint { + self.size.into_scaled(self.scale) + } + fn width(&self) -> f64 { + self.size.x() + } + fn height(&self) -> f64 { + self.size.y() + } +} +impl ToPhysical for LogicalBounds { + fn to_physical(self) -> PhysicalBounds { + PhysicalBounds { + offset: self.offset, + size: self.size, + scale: self.scale, + } + } +} +impl FromWithScale for LogicalBounds { + fn from_with_scale(value: Bounds, scale: f64) -> Self { + Self { + offset: value.offset(), + size: value.size(), + scale, + } + } +} + +#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, specta::Type)] +pub struct PhysicalBounds { + offset: Point, + size: Point, + scale: f64, +} +impl Scaled for PhysicalBounds { + fn scale(&self) -> f64 { + self.scale + } +} +impl Position for PhysicalBounds { + fn x(&self) -> f64 { + self.offset.x() * self.scale + } + fn y(&self) -> f64 { + self.offset.y() * self.scale + } +} +impl Rect for PhysicalBounds { + fn offset(&self) -> PhysicalPoint { + self.offset.into_scaled(self.scale) + } + fn size(&self) -> PhysicalPoint { + self.size.into_scaled(self.scale) + } + fn width(&self) -> f64 { + self.size.x() * self.scale + } + fn height(&self) -> f64 { + self.size.y() * self.scale + } +} +impl ToLogical for PhysicalBounds { + fn to_logical(self) -> LogicalBounds { + LogicalBounds { + offset: self.offset, + size: self.size, + scale: self.scale, + } + } +} +impl FromWithScale for PhysicalBounds { + fn from_with_scale(value: Bounds, scale: f64) -> Self { + Self { + offset: value.offset(), + size: value.size(), + scale, + } + } +} + +impl Mul for Point { + type Output = Point; + fn mul(self, rhs: f64) -> Self::Output { + assert_ne!(rhs, 0.0, "Cannot multiply point by zero"); + Point { x: self.x * rhs, y: self.y * rhs } + } +} +impl Div for Point { + type Output = Point; + fn div(self, rhs: f64) -> Self::Output { + assert_ne!(rhs, 0.0, "Cannot divide point by zero"); + Point { x: self.x / rhs, y: self.y / rhs } + } +} +impl Add for Point { + type Output = Point; + fn add(self, rhs: f64) -> Self::Output { + Point { x: self.x + rhs, y: self.y + rhs } + } +} +impl Sub for Point { + type Output = Point; + fn sub(self, rhs: f64) -> Self::Output { + Point { x: self.x - rhs, y: self.y - rhs } + } +} +impl Mul for Point { + type Output = Point; + fn mul(self, rhs: Point) -> Self::Output { + assert_ne!(rhs.x, 0.0, "Cannot multiply point by zero"); + assert_ne!(rhs.y, 0.0, "Cannot multiply point by zero"); + Point { x: self.x * rhs.x, y: self.y * rhs.y } + } +} +impl Div for Point { + type Output = Point; + fn div(self, rhs: Point) -> Self::Output { + assert_ne!(rhs.x, 0.0, "Cannot multiply point by zero"); + assert_ne!(rhs.y, 0.0, "Cannot multiply point by zero"); + Point { x: self.x / rhs.x, y: self.y / rhs.y } + } +} +impl Add for Point { + type Output = Point; + fn add(self, rhs: Point) -> Self::Output { + Point { x: self.x + rhs.x, y: self.y + rhs.y } + } +} +impl Sub for Point { + type Output = Point; + fn sub(self, rhs: Point) -> Self::Output { + Point { x: self.x - rhs.x, y: self.y - rhs.y } + } +} + +impl Mul for Bounds { + type Output = Bounds; + fn mul(self, rhs: f64) -> Self::Output { + assert_ne!(rhs, 0.0, "Cannot multiply bounds by zero"); + Bounds { + offset: self.offset * rhs, + size: self.size * rhs, + } + } +} +impl Div for Bounds { + type Output = Bounds; + fn div(self, rhs: f64) -> Self::Output { + assert_ne!(rhs, 0.0, "Cannot divide bounds by zero"); + Bounds { + offset: self.offset / rhs, + size: self.size / rhs, + } + } +} + +impl Mul for LogicalPoint { + type Output = LogicalPoint; + fn mul(self, rhs: LogicalPoint) -> Self::Output { + assert_scale(&self, &rhs); + (self.as_point() * rhs.as_point()).into_scaled(self.scale()) + } +} +impl Div for LogicalPoint { + type Output = LogicalPoint; + fn div(self, rhs: LogicalPoint) -> Self::Output { + assert_scale(&self, &rhs); + (self.as_point() / rhs.as_point()).into_scaled(self.scale()) + } +} +impl Add for LogicalPoint { + type Output = LogicalPoint; + fn add(self, rhs: LogicalPoint) -> Self::Output { + assert_scale(&self, &rhs); + (self.as_point() + rhs.as_point()).into_scaled(self.scale()) + } +} +impl Sub for LogicalPoint { + type Output = LogicalPoint; + fn sub(self, rhs: LogicalPoint) -> Self::Output { + assert_scale(&self, &rhs); + (self.as_point() - rhs.as_point()).into_scaled(self.scale()) + } +} +impl Mul for PhysicalPoint { + type Output = PhysicalPoint; + fn mul(self, rhs: PhysicalPoint) -> Self::Output { + assert_scale(&self, &rhs); + (self.as_point() * rhs.as_point()).into_scaled(self.scale()) + } +} +impl Div for PhysicalPoint { + type Output = PhysicalPoint; + fn div(self, rhs: PhysicalPoint) -> Self::Output { + assert_scale(&self, &rhs); + (self.as_point() / rhs.as_point()).into_scaled(self.scale()) + } +} +impl Add for PhysicalPoint { + type Output = PhysicalPoint; + fn add(self, rhs: PhysicalPoint) -> Self::Output { + assert_scale(&self, &rhs); + (self.as_point() + rhs.as_point()).into_scaled(self.scale()) + } +} +impl Sub for PhysicalPoint { + type Output = PhysicalPoint; + fn sub(self, rhs: PhysicalPoint) -> Self::Output { + assert_scale(&self, &rhs); + (self.as_point() - rhs.as_point()).into_scaled(self.scale()) + } +} +fn assert_scale(a: &T, b: &T) { + assert_eq!(a.scale(), b.scale(), "Cannot multiply with diffent scale"); +} + +impl From<(f64, f64)> for Point { + fn from((x, y): (f64, f64)) -> Self { + Self { x, y } + } +} +impl From<(f64, f64, f64, f64)> for Bounds { + fn from((x, y, width, height): (f64, f64, f64, f64)) -> Self { + Self { + offset: Point { x, y }, + size: Point { x: width, y: height }, + } + } +} + +impl From for (f64, f64) { + fn from(point: LogicalPoint) -> Self { + (point.x(), point.y()) + } +} +impl From for (f64, f64) { + fn from(point: PhysicalPoint) -> Self { + (point.x(), point.y()) + } +} +impl From for (f64, f64, f64, f64) { + fn from(bounds: LogicalBounds) -> Self { + (bounds.x(), bounds.y(), bounds.width(), bounds.height()) + } +} +impl From for (f64, f64, f64, f64) { + fn from(bounds: PhysicalBounds) -> Self { + (bounds.x(), bounds.y(), bounds.width(), bounds.height()) + } +} + +impl From for Point { + fn from(vec: glam::DVec2) -> Self { + Self { x: vec.x, y: vec.y } + } +} +impl From for glam::DVec2 { + fn from(val: LogicalPoint) -> Self { + glam::DVec2::new(val.x(), val.y()) + } +} +impl From for glam::DVec2 { + fn from(val: PhysicalPoint) -> Self { + glam::DVec2::new(val.x(), val.y()) + } +} + +impl From<[glam::DVec2; 2]> for Bounds { + fn from(bounds: [glam::DVec2; 2]) -> Self { + Self { + offset: bounds[0].into(), + size: Point { + x: bounds[1].x - bounds[0].x, + y: bounds[1].y - bounds[0].y, + }, + } + } +} +impl From for [glam::DVec2; 2] { + fn from(bounds: LogicalBounds) -> Self { + [glam::DVec2::new(bounds.x(), bounds.y()), glam::DVec2::new(bounds.x() + bounds.width(), bounds.y() + bounds.height())] + } +} +impl From for [glam::DVec2; 2] { + fn from(bounds: PhysicalBounds) -> Self { + [glam::DVec2::new(bounds.x(), bounds.y()), glam::DVec2::new(bounds.x() + bounds.width(), bounds.y() + bounds.height())] + } +} + +impl LogicalPoint { + pub fn into_dvec2(self) -> DVec2 { + DVec2::new(self.x(), self.y()) + } +} +impl PhysicalPoint { + pub fn into_dvec2(self) -> DVec2 { + DVec2::new(self.x(), self.y()) + } +} diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index 640e776d0e..7889e4e540 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -1,6 +1,6 @@ use crate::messages::frontend::utility_types::{ExportBounds, FileType}; use crate::messages::prelude::*; -use glam::{DAffine2, DVec2, UVec2}; +use glam::{DAffine2, UVec2}; use graph_craft::document::value::{RenderOutput, TaggedValue}; use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput}; use graph_craft::proto::GraphErrors; @@ -136,14 +136,17 @@ impl NodeGraphExecutor { document: &mut DocumentMessageHandler, document_id: DocumentId, viewport_resolution: UVec2, + viewport_scale: f64, time: TimingInformation, ) -> Result { + let viewport = Footprint { + transform: document.metadata().document_to_viewport, + resolution: viewport_resolution, + ..Default::default() + }; let render_config = RenderConfig { - viewport: Footprint { - transform: document.metadata().document_to_viewport, - resolution: viewport_resolution, - ..Default::default() - }, + viewport, + scale: viewport_scale, time, #[cfg(any(feature = "resvg", feature = "vello"))] export_format: graphene_std::application_io::ExportFormat::Raster, @@ -163,23 +166,31 @@ impl NodeGraphExecutor { } /// Evaluates a node graph, computing the entire graph + #[allow(clippy::too_many_arguments)] pub fn submit_node_graph_evaluation( &mut self, document: &mut DocumentMessageHandler, document_id: DocumentId, viewport_resolution: UVec2, + viewport_scale: f64, time: TimingInformation, node_to_inspect: Option, ignore_hash: bool, ) -> Result { self.update_node_graph(document, node_to_inspect, ignore_hash)?; - self.submit_current_node_graph_evaluation(document, document_id, viewport_resolution, time) + self.submit_current_node_graph_evaluation(document, document_id, viewport_resolution, viewport_scale, time) } /// Evaluates a node graph for export pub fn submit_document_export(&mut self, document: &mut DocumentMessageHandler, document_id: DocumentId, mut export_config: ExportConfig) -> Result<(), String> { let network = document.network_interface.document_network().clone(); + let export_format = if export_config.file_type == FileType::Svg { + graphene_std::application_io::ExportFormat::Svg + } else { + graphene_std::application_io::ExportFormat::Raster + }; + // Calculate the bounding box of the region to be exported let bounds = match export_config.bounds { ExportBounds::AllArtwork => document.network_interface.document_bounds_document_space(!export_config.transparent_background), @@ -187,28 +198,23 @@ impl NodeGraphExecutor { ExportBounds::Artboard(id) => document.metadata().bounding_box_document(id), } .ok_or_else(|| "No bounding box".to_string())?; - let size = bounds[1] - bounds[0]; + let resolution = (bounds[1] - bounds[0]).as_uvec2(); let transform = DAffine2::from_translation(bounds[0]).inverse(); - let export_format = if export_config.file_type == FileType::Svg { - graphene_std::application_io::ExportFormat::Svg - } else { - graphene_std::application_io::ExportFormat::Raster - }; - let render_config = RenderConfig { viewport: Footprint { - transform: DAffine2::from_scale(DVec2::splat(export_config.scale_factor)) * transform, - resolution: (size * export_config.scale_factor).as_uvec2(), + resolution, + transform, ..Default::default() }, + scale: export_config.scale_factor, time: Default::default(), export_format, render_mode: document.render_mode, hide_artboards: export_config.transparent_background, for_export: true, }; - export_config.size = size; + export_config.size = resolution.as_dvec2(); // Execute the node graph self.runtime_io diff --git a/editor/src/test_utils.rs b/editor/src/test_utils.rs index b6ce5de5f8..4a9e579a9a 100644 --- a/editor/src/test_utils.rs +++ b/editor/src/test_utils.rs @@ -1,7 +1,6 @@ use crate::application::Editor; use crate::application::set_uuid_seed; use crate::messages::input_mapper::utility_types::input_keyboard::ModifierKeys; -use crate::messages::input_mapper::utility_types::input_mouse::ViewportBounds; use crate::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, MouseKeys, ScrollDelta, ViewportPosition}; use crate::messages::portfolio::utility_types::Platform; use crate::messages::prelude::*; @@ -10,7 +9,7 @@ use crate::messages::tool::utility_types::ToolType; use crate::node_graph_executor::Instrumented; use crate::node_graph_executor::NodeRuntime; use crate::test_utils::test_prelude::LayerNodeIdentifier; -use glam::DVec2; +use glam::{DVec2, UVec2}; use graph_craft::document::DocumentNode; use graphene_std::InputAccessor; use graphene_std::raster::color::Color; @@ -48,8 +47,7 @@ impl EditorTestUtils { Err(e) => return Err(format!("update_node_graph_instrumented failed\n\n{e}")), }; - let viewport_resolution = glam::UVec2::ONE; - if let Err(e) = exector.submit_current_node_graph_evaluation(document, DocumentId(0), viewport_resolution, Default::default()) { + if let Err(e) = exector.submit_current_node_graph_evaluation(document, DocumentId(0), UVec2::ONE, 1.0, Default::default()) { return Err(format!("submit_current_node_graph_evaluation failed\n\n{e}")); } runtime.run().await; @@ -296,8 +294,12 @@ impl EditorTestUtils { /// Necessary for doing snapping since snaps outside of the viewport are discarded pub async fn set_viewport_size(&mut self, top_left: DVec2, bottom_right: DVec2) { - self.handle_message(InputPreprocessorMessage::BoundsOfViewports { - bounds_of_viewports: vec![ViewportBounds { top_left, bottom_right }], + self.handle_message(ViewportMessage::Update { + x: top_left.x, + y: top_left.y, + width: bottom_right.x - top_left.x, + height: bottom_right.y - top_left.y, + scale: 1.0, }) .await; } diff --git a/frontend/src/components/panels/Document.svelte b/frontend/src/components/panels/Document.svelte index c259c9ba74..e6e5282d53 100644 --- a/frontend/src/components/panels/Document.svelte +++ b/frontend/src/components/panels/Document.svelte @@ -20,7 +20,7 @@ import type { DocumentState } from "@graphite/state-providers/document"; import { textInputCleanup } from "@graphite/utility-functions/keyboard-entry"; import { extractPixelData, rasterizeSVGCanvas } from "@graphite/utility-functions/rasterization"; - import { updateBoundsOfViewports } from "@graphite/utility-functions/viewports"; + import { updateBoundsOfViewports as updateViewport } from "@graphite/utility-functions/viewports"; import EyedropperPreview, { ZOOM_WINDOW_DIMENSIONS } from "@graphite/components/floating-menus/EyedropperPreview.svelte"; import LayoutCol from "@graphite/components/layout/LayoutCol.svelte"; @@ -74,21 +74,21 @@ let cursorEyedropperPreviewColorSecondary = ""; // Canvas dimensions - let canvasSvgWidth: number | undefined = undefined; - let canvasSvgHeight: number | undefined = undefined; + let canvasWidth: number | undefined = undefined; + let canvasHeight: number | undefined = undefined; let devicePixelRatio: number | undefined; // Dimension is rounded up to the nearest even number because resizing is centered, and dividing an odd number by 2 for centering causes antialiasing - $: canvasWidthRoundedToEven = canvasSvgWidth && (canvasSvgWidth % 2 === 1 ? canvasSvgWidth + 1 : canvasSvgWidth); - $: canvasHeightRoundedToEven = canvasSvgHeight && (canvasSvgHeight % 2 === 1 ? canvasSvgHeight + 1 : canvasSvgHeight); + $: canvasWidthRoundedToEven = canvasWidth && (canvasWidth % 2 === 1 ? canvasWidth + 1 : canvasWidth); + $: canvasHeightRoundedToEven = canvasHeight && (canvasHeight % 2 === 1 ? canvasHeight + 1 : canvasHeight); // Used to set the canvas element size on the page. // The value above in pixels, or if undefined, we fall back to 100% as a non-pixel-perfect backup that's hopefully short-lived $: canvasWidthCSS = canvasWidthRoundedToEven ? `${canvasWidthRoundedToEven}px` : "100%"; $: canvasHeightCSS = canvasHeightRoundedToEven ? `${canvasHeightRoundedToEven}px` : "100%"; - $: canvasWidthScaled = canvasSvgWidth && devicePixelRatio && Math.floor(canvasSvgWidth * devicePixelRatio); - $: canvasHeightScaled = canvasSvgHeight && devicePixelRatio && Math.floor(canvasSvgHeight * devicePixelRatio); + $: canvasWidthScaled = canvasWidth && devicePixelRatio && Math.floor(canvasWidth * devicePixelRatio); + $: canvasHeightScaled = canvasHeight && devicePixelRatio && Math.floor(canvasHeight * devicePixelRatio); // Used to set the canvas rendering dimensions. $: canvasWidthScaledRoundedToEven = canvasWidthScaled && (canvasWidthScaled % 2 === 1 ? canvasWidthScaled + 1 : canvasWidthScaled); @@ -226,14 +226,14 @@ } cursorEyedropper = true; - if (canvasSvgWidth === undefined || canvasSvgHeight === undefined) return undefined; + if (canvasWidth === undefined || canvasHeight === undefined) return undefined; cursorLeft = mousePosition.x; cursorTop = mousePosition.y; // This works nearly perfectly, but sometimes at odd DPI scale factors like 1.25, the anti-aliasing color can yield slightly incorrect colors (potential room for future improvement) const dpiFactor = window.devicePixelRatio; - const [width, height] = [canvasSvgWidth, canvasSvgHeight]; + const [width, height] = [canvasWidth, canvasHeight]; const outsideArtboardsColor = getComputedStyle(window.document.documentElement).getPropertyValue("--color-2-mildblack"); const outsideArtboards = ``; @@ -381,6 +381,22 @@ showTextInput = false; } + function updateViewportInfo() { + if (!viewport) return; + // Resize the canvas + canvasWidth = Math.ceil(parseFloat(getComputedStyle(viewport).width)); + canvasHeight = Math.ceil(parseFloat(getComputedStyle(viewport).height)); + + devicePixelRatio = window.devicePixelRatio || 1.0; + + // Resize the rulers + rulerHorizontal?.resize(); + rulerVertical?.resize(); + + // Send the new bounds of the viewports to the backend + if (viewport.parentElement) updateViewport(editor); + } + onMount(() => { // Not compatible with Safari: // @@ -393,8 +409,7 @@ mediaQueryList.addEventListener("change", updatePixelRatio); removeUpdatePixelRatio = () => mediaQueryList.removeEventListener("change", updatePixelRatio); - devicePixelRatio = window.devicePixelRatio; - editor.handle.setDevicePixelRatio(devicePixelRatio); + updateViewportInfo(); }; updatePixelRatio(); @@ -462,18 +477,7 @@ window.dispatchEvent(new Event("resize")); const viewportResizeObserver = new ResizeObserver(() => { - if (!viewport) return; - - // Resize the canvas - canvasSvgWidth = Math.ceil(parseFloat(getComputedStyle(viewport).width)); - canvasSvgHeight = Math.ceil(parseFloat(getComputedStyle(viewport).height)); - - // Resize the rulers - rulerHorizontal?.resize(); - rulerVertical?.resize(); - - // Send the new bounds of the viewports to the backend - if (viewport.parentElement) updateBoundsOfViewports(editor); + updateViewportInfo(); }); if (viewport) viewportResizeObserver.observe(viewport); }); diff --git a/frontend/src/utility-functions/viewports.ts b/frontend/src/utility-functions/viewports.ts index 134ece0b31..8fe6a32311 100644 --- a/frontend/src/utility-functions/viewports.ts +++ b/frontend/src/utility-functions/viewports.ts @@ -2,13 +2,11 @@ import { type Editor } from "@graphite/editor"; export function updateBoundsOfViewports(editor: Editor) { const viewports = Array.from(window.document.querySelectorAll("[data-viewport-container]")); - const boundsOfViewports = viewports.map((canvas) => { - const bounds = canvas.getBoundingClientRect(); - return [bounds.left, bounds.top, bounds.right, bounds.bottom]; - }); - const flattened = boundsOfViewports.flat(); - const data = Float64Array.from(flattened); + if (viewports.length <= 0) return; - if (boundsOfViewports.length > 0) editor.handle.boundsOfViewports(data); + const bounds = viewports[0].getBoundingClientRect(); + const scale = window.devicePixelRatio || 1; + + editor.handle.updateViewport(bounds.x, bounds.y, bounds.width, bounds.height, scale); } diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index b46b27f6ac..66ef5e82bc 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -8,7 +8,7 @@ use crate::helpers::translate_key; use crate::{EDITOR_HANDLE, EDITOR_HAS_CRASHED, Error, MESSAGE_BUFFER}; use editor::consts::FILE_EXTENSION; use editor::messages::input_mapper::utility_types::input_keyboard::ModifierKeys; -use editor::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, ScrollDelta, ViewportBounds}; +use editor::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, ScrollDelta}; use editor::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use editor::messages::portfolio::document::utility_types::network_interface::ImportOrExport; use editor::messages::portfolio::utility_types::Platform; @@ -516,13 +516,10 @@ impl EditorHandle { self.dispatch(message); } - /// Send new bounds when document panel viewports get resized or moved within the editor - /// [left, top, right, bottom]... - #[wasm_bindgen(js_name = boundsOfViewports)] - pub fn bounds_of_viewports(&self, bounds_of_viewports: &[f64]) { - let chunked: Vec<_> = bounds_of_viewports.chunks(4).map(ViewportBounds::from_slice).collect(); - - let message = InputPreprocessorMessage::BoundsOfViewports { bounds_of_viewports: chunked }; + /// Send new viewport info to the backend + #[wasm_bindgen(js_name = updateViewport)] + pub fn update_viewport(&self, x: f64, y: f64, width: f64, height: f64, scale: f64) { + let message = ViewportMessage::Update { x, y, width, height, scale }; self.dispatch(message); } @@ -533,13 +530,6 @@ impl EditorHandle { self.dispatch(message); } - /// Inform the overlays system of the current device pixel ratio - #[wasm_bindgen(js_name = setDevicePixelRatio)] - pub fn set_device_pixel_ratio(&self, ratio: f64) { - let message = PortfolioMessage::SetDevicePixelRatio { ratio }; - self.dispatch(message); - } - /// Mouse movement within the screenspace bounds of the viewport #[wasm_bindgen(js_name = onMouseMove)] pub fn on_mouse_move(&self, x: f64, y: f64, mouse_keys: u8, modifiers: u8) { diff --git a/node-graph/gapplication-io/src/lib.rs b/node-graph/gapplication-io/src/lib.rs index ed0fdee36c..6f7814b2c0 100644 --- a/node-graph/gapplication-io/src/lib.rs +++ b/node-graph/gapplication-io/src/lib.rs @@ -234,6 +234,7 @@ pub struct TimingInformation { #[derive(Debug, Default, Clone, Copy, PartialEq, DynAny, serde::Serialize, serde::Deserialize)] pub struct RenderConfig { pub viewport: Footprint, + pub scale: f64, pub export_format: ExportFormat, pub time: TimingInformation, #[serde(alias = "view_mode")]