Split out viewport handling into its own message handler (#3331)

* extract viewport handeling

* fix web overlays

* some cleanup

* remove some physical conversions

* fix resize snapping

* fixup

* apply some review feedback

* make viewport api more ergonomic

* fix

* fix web overlay canvas clear size

* clear workaround for canvas

* rename trigger message

---------

Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
Timon
2025-11-08 08:59:38 +00:00
committed by GitHub
co-authored by Dennis Kobert
parent 3490111b96
commit 881ec0b193
60 changed files with 1326 additions and 607 deletions
+15 -4
View File
@@ -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));
@@ -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<DialogMessage, DialogMessageContext<'_>> for DialogMessageHandler {
fn process_message(&mut self, message: DialogMessage, responses: &mut VecDeque<Message>, 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 => {
@@ -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;
@@ -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<NewDocumentDialogMessage, NewDocumentDialogMessageContext<'a>> for NewDocumentDialogMessageHandler {
fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque<Message>, context: NewDocumentDialogMessageContext<'a>) {
impl<'a> MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHandler {
fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque<Message>, _: ()) {
match message {
NewDocumentDialogMessage::Name { name } => self.name = name,
NewDocumentDialogMessage::Infinite { infinite } => self.infinite = infinite,
@@ -35,12 +30,9 @@ impl<'a> MessageHandler<NewDocumentDialogMessage, NewDocumentDialogMessageContex
});
responses.add(NavigationMessage::CanvasPan { delta: self.dimensions.as_dvec2() });
responses.add(NodeGraphMessage::RunDocumentGraph);
// If we already have bounds, we won't receive a viewport bounds update so we just fabricate one ourselves
if *context.viewport_bounds != ViewportBounds::default() {
responses.add(InputPreprocessorMessage::BoundsOfViewports {
bounds_of_viewports: vec![context.viewport_bounds.clone()],
});
}
responses.add(ViewportMessage::RepropagateUpdate);
responses.add(DeferMessage::AfterNavigationReady {
messages: vec![DocumentMessage::ZoomCanvasToFitAll.into(), DocumentMessage::DeselectAllLayers.into()],
});
@@ -350,6 +350,12 @@ pub enum FrontendMessage {
UpdateViewportHolePunch {
active: bool,
},
UpdateViewportPhysicalBounds {
x: f64,
y: f64,
width: f64,
height: f64,
},
#[cfg(not(target_family = "wasm"))]
RenderOverlays {
#[serde(skip, default = "OverlayContext::default")]
@@ -3,41 +3,13 @@ use crate::messages::prelude::*;
use bitflags::bitflags;
use glam::DVec2;
use std::collections::VecDeque;
use std::hash::{Hash, Hasher};
// Origin is top left
pub type DocumentPosition = DVec2;
pub type ViewportPosition = DVec2;
pub type EditorPosition = DVec2;
#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct ViewportBounds {
pub top_left: DVec2,
pub bottom_right: DVec2,
}
impl ViewportBounds {
pub fn from_slice(slice: &[f64]) -> 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,
}
@@ -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<ViewportBounds> },
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 },
@@ -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<InputPreprocessorMessage, InputPreprocessorMessageContext> for InputPreprocessorMessageHandler {
fn process_message(&mut self, message: InputPreprocessorMessage, responses: &mut VecDeque<Message>, context: InputPreprocessorMessageContext) {
let InputPreprocessorMessageContext { keyboard_platform } = context;
impl<'a> MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageContext<'a>> for InputPreprocessorMessageHandler {
fn process_message(&mut self, message: InputPreprocessorMessage, responses: &mut VecDeque<Message>, 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<InputPreprocessorMessage, InputPreprocessorMessageContext> 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<InputPreprocessorMessage, InputPreprocessorMessageContext> 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<InputPreprocessorMessage, InputPreprocessorMessageContext> 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<InputPreprocessorMessage, InputPreprocessorMessageContext> 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<InputPreprocessorMessage, InputPreprocessorMessageContext> 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);
+2
View File
@@ -33,6 +33,8 @@ pub enum Message {
Preferences(PreferencesMessage),
#[child]
Tool(ToolMessage),
#[child]
Viewport(ViewportMessage),
// Messages
Batched {
+1
View File
@@ -16,3 +16,4 @@ pub mod portfolio;
pub mod preferences;
pub mod prelude;
pub mod tool;
pub mod viewport;
@@ -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<DocumentMessage, DocumentMessageContext<'_>> 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<DocumentMessage, DocumentMessageContext<'_>> 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<DocumentMessage, DocumentMessageContext<'_>> 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<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
navigation_handler: &self.navigation_handler,
preferences,
layers_panel_open,
viewport,
},
);
}
@@ -380,8 +375,8 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> 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<DocumentMessage, DocumentMessageContext<'_>> 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<DocumentMessage, DocumentMessageContext<'_>> 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<DocumentMessage, DocumentMessageContext<'_>> 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<DocumentMessage, DocumentMessageContext<'_>> 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<DocumentMessage, DocumentMessageContext<'_>> 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<DocumentMessage, DocumentMessageContext<'_>> 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<DocumentMessage, DocumentMessageContext<'_>> 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<DocumentMessage, DocumentMessageContext<'_>> 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<DocumentMessage, DocumentMessageContext<'_>> 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<Item = LayerNodeIdentifier> + 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<Item = LayerNodeIdentifier> + 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<Item = LayerNodeIdentifier> + 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<Item = LayerNodeIdentifier> + 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<PointId>, ipp: &InputPreprocessorMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + 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<PointId>, viewport: &ViewportMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + 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<PointId>, ipp: &InputPreprocessorMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + 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<PointId>, viewport: &ViewportMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + 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<PointId>) -> 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<PointId>) -> 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<Item = LayerNodeIdentifier> + 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<Item = LayerNodeIdentifier> + 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<Item = LayerNodeIdentifier> + use<'a> {
self.click_xray(ipp)
pub fn click_list<'a>(&'a self, ipp: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + 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<Item = LayerNodeIdentifier> + use<'a> {
self.click_xray(ipp)
pub fn click_list_with_artboards<'a>(&'a self, ipp: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + 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<Item = LayerNodeIdentifier> + use<'a> {
self.click_xray(ipp)
pub fn click_list_no_parents<'a>(&'a self, ipp: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + 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<LayerNodeIdentifier> {
self.click_list(ipp).last()
pub fn click(&self, ipp: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) -> Option<LayerNodeIdentifier> {
self.click_list(ipp, viewport).last()
}
pub fn click_based_on_position(&self, mouse_snapped_positon: DVec2) -> Option<LayerNodeIdentifier> {
@@ -1943,8 +1939,8 @@ impl DocumentMessageHandler {
structure_section.as_slice().into()
}
pub fn undo_with_history(&mut self, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
let Some(previous_network) = self.undo(ipp, responses) else { return };
pub fn undo_with_history(&mut self, viewport: &ViewportMessageHandler, responses: &mut VecDeque<Message>) {
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<Message>) -> Option<NodeNetworkInterface> {
pub fn undo(&mut self, viewport: &ViewportMessageHandler, responses: &mut VecDeque<Message>) -> Option<NodeNetworkInterface> {
// 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<Message>) {
pub fn redo_with_history(&mut self, viewport: &ViewportMessageHandler, responses: &mut VecDeque<Message>) {
// 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<Message>) -> Option<NodeNetworkInterface> {
pub fn redo(&mut self, viewport: &ViewportMessageHandler, responses: &mut VecDeque<Message>) -> Option<NodeNetworkInterface> {
// 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);
@@ -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<NavigationMessage, NavigationMessageContext<'_>> 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<NavigationMessage, NavigationMessageContext<'_>> 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<NavigationMessage, NavigationMessageContext<'_>> 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<NavigationMessage, NavigationMessageContext<'_>> 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<NavigationMessage, NavigationMessageContext<'_>> 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<NavigationMessage, NavigationMessageContext<'_>> 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<NavigationMessage, NavigationMessageContext<'_>> 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<NavigationMessage, NavigationMessageContext<'_>> 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<NavigationMessage, NavigationMessageContext<'_>> 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<NavigationMessage, NavigationMessageContext<'_>> 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<NavigationMessage, NavigationMessageContext<'_>> 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<NavigationMessage, NavigationMessageContext<'_>> 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.;
@@ -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<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
navigation_handler,
preferences,
layers_panel_open,
viewport,
} = context;
match message {
@@ -783,12 +786,12 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> 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<NodeGraphMessage, NodeGraphMessageContext<'a>> 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<NodeGraphMessage, NodeGraphMessageContext<'a>> 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<NodeGraphMessage, NodeGraphMessageContext<'a>> 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<NodeGraphMessage, NodeGraphMessageContext<'a>> 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<NodeGraphMessage, NodeGraphMessageContext<'a>> 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);
}
@@ -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);
@@ -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<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMessageHandler {
fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque<Message>, 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<OverlaysMessage, OverlaysMessageContext<'_>> 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<OverlaysMessage, OverlaysMessageContext<'_>> 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<OverlaysMessage, OverlaysMessageContext<'_>> 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);
@@ -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));
@@ -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 {
@@ -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<Mutex<OverlayContextInternal>>,
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<Mutex<_>>
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<f64>, 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<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {
@@ -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<f64>, 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<bool>) {
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.,
},
};
@@ -112,9 +112,6 @@ pub enum PortfolioMessage {
SetActivePanel {
panel: PanelType,
},
SetDevicePixelRatio {
ratio: f64,
},
SelectDocument {
document_id: DocumentId,
},
@@ -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<f64>,
pub reset_node_definitions_on_open: bool,
pub data_panel_open: bool,
#[derivative(Default(value = "true"))]
@@ -76,6 +76,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
message_logging_verbosity,
reset_node_definitions_on_open,
timing_information,
viewport,
} = context;
match message {
@@ -124,7 +125,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> 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<PortfolioMessage, PortfolioMessageContext<'_>> 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<PortfolioMessage, PortfolioMessageContext<'_>> 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<PortfolioMessage, PortfolioMessageContext<'_>> 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<PortfolioMessage, PortfolioMessageContext<'_>> 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<PortfolioMessage, PortfolioMessageContext<'_>> 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) => {
+1
View File
@@ -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};
@@ -34,9 +34,9 @@ impl AutoPanning {
}
}
pub fn setup_by_mouse_position(&mut self, input: &InputPreprocessorMessageHandler, messages: &[Message], responses: &mut VecDeque<Message>) {
pub fn setup_by_mouse_position(&mut self, input: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler, messages: &[Message], responses: &mut VecDeque<Message>) {
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<Message>) -> Option<DVec2> {
pub fn shift_viewport(&self, input: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler, responses: &mut VecDeque<Message>) -> Option<DVec2> {
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),
@@ -142,7 +142,7 @@ impl PointRadiusHandle {
}
}
pub fn overlays(&self, selected_star_layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, overlay_context: &mut OverlayContext) {
pub fn overlays(&self, selected_star_layer: Option<LayerNodeIdentifier>, 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) {
@@ -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<Message> {
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<Message> {
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];
@@ -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
@@ -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<Message>,
) {
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;
};
@@ -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<Message>,
) {
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;
};
@@ -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;
};
@@ -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) {
@@ -58,13 +58,13 @@ impl ShapeGizmoHandler for PolygonGizmoHandler {
&self,
document: &DocumentMessageHandler,
selected_polygon_layer: Option<LayerNodeIdentifier>,
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);
@@ -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;
};
@@ -36,13 +36,20 @@ impl Spiral {
])
}
pub fn update_shape(document: &DocumentMessageHandler, ipp: &InputPreprocessorMessageHandler, layer: LayerNodeIdentifier, shape_tool_data: &mut ShapeToolData, responses: &mut VecDeque<Message>) {
pub fn update_shape(
document: &DocumentMessageHandler,
ipp: &InputPreprocessorMessageHandler,
viewport: &ViewportMessageHandler,
layer: LayerNodeIdentifier,
shape_tool_data: &mut ShapeToolData,
responses: &mut VecDeque<Message>,
) {
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);
@@ -58,13 +58,13 @@ impl ShapeGizmoHandler for StarGizmoHandler {
&self,
document: &DocumentMessageHandler,
selected_star_layer: Option<LayerNodeIdentifier>,
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);
@@ -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<LayerNodeIdentifier>>,
pub alignment_candidates: Option<&'a Vec<LayerNodeIdentifier>>,
}
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);
@@ -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
@@ -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);
}
}
@@ -262,6 +262,7 @@ pub fn resize_bounds(
snap_manager: &mut SnapManager,
snap_candidates: &mut Vec<SnapCandidatePoint>,
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);
@@ -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<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
persistent_data,
node_graph,
preferences,
viewport,
} = context;
let font_cache = &persistent_data.font_cache;
@@ -54,6 +56,7 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
input,
tool_data: &self.tool_state.tool_data,
shape_editor: &mut self.shape_editor,
viewport,
},
),
@@ -123,6 +126,7 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> 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<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
shape_editor: &mut self.shape_editor,
node_graph,
preferences,
viewport,
};
// Set initial hints and cursor
@@ -336,6 +341,7 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
shape_editor: &mut self.shape_editor,
node_graph,
preferences,
viewport,
};
if matches!(tool_message, ToolMessage::UpdateHints) {
if graph_view_overlay_open {
@@ -145,12 +145,12 @@ impl ArtboardToolData {
}
}
fn hovered_artboard(document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler) -> Option<LayerNodeIdentifier> {
document.click_xray(input).find(|&layer| document.network_interface.is_artboard(&layer.to_node(), &[]))
fn hovered_artboard(document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) -> Option<LayerNodeIdentifier> {
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<Message>) -> bool {
if let Some(intersection) = Self::hovered_artboard(document, input) {
fn select_artboard(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler, responses: &mut VecDeque<Message>) -> 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<Message>, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, from_center: bool, constrain_square: bool) {
fn resize_artboard(
&mut self,
responses: &mut VecDeque<Message>,
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<Message>) -> 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;
@@ -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<Message>) -> 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);
@@ -94,7 +94,11 @@ impl Fsm for FillToolFsmState {
responses: &mut VecDeque<Message>,
) -> 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
@@ -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();
@@ -255,7 +255,11 @@ impl Fsm for GradientToolFsmState {
responses: &mut VecDeque<Message>,
) -> 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));
}
@@ -701,6 +701,7 @@ impl PathToolData {
shape_editor: &mut ShapeState,
document: &DocumentMessageHandler,
input: &InputPreprocessorMessageHandler,
viewport: &ViewportMessageHandler,
responses: &mut VecDeque<Message>,
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<Message>,
) {
// 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<Message>,
) -> 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<LayerNodeIdentifier> = 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<LayerNodeIdentifier> {
let drill_through_layers = document.click_list_no_parents(input).collect::<Vec<LayerNodeIdentifier>>();
let drill_through_layers = document.click_list_no_parents(input, viewport).collect::<Vec<LayerNodeIdentifier>>();
if drill_through_layers.is_empty() {
tool_data.reset_drill_through_cycle();
None
@@ -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<DVec2> {
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<Message>,
layer: Option<LayerNodeIdentifier>,
input: &InputPreprocessorMessageHandler,
viewport: &ViewportMessageHandler,
) -> Option<PenToolFsmState> {
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<Message>,
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::<Vec<_>>().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::<Vec<_>>().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,
@@ -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<LayerNodeIdentifier> {
pub fn intersect_lasso_no_artboards(&self, document: &DocumentMessageHandler, viewport: &ViewportMessageHandler) -> Vec<LayerNodeIdentifier> {
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<Message>) -> 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::<Vec<_>>();
let intersection_list = document.click_list(input, viewport).collect::<Vec<_>>();
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::<Vec<_>>();
let selected = document.click_list(input, viewport).collect::<Vec<_>>();
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<LayerNodeIdentifier> = 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 {
@@ -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
}
(
@@ -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
}
@@ -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;
@@ -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<TransformLayerMessage, TransformLayerMessageContext<'_>> 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<TransformLayerMessage, TransformLayerMessageContext<'_>> 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| {
@@ -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<ToolMessage, &'b mut ToolActionMessageContext<'a>> + LayoutHolder + ToolTransition + ToolMetadata {}
+7
View File
@@ -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::*;
@@ -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,
}
@@ -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<ViewportMessage, ()> for ViewportMessageHandler {
fn process_message(&mut self, message: ViewportMessage, responses: &mut VecDeque<Message>, _: ()) {
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<T: Into<Point>>(&self, point: T) -> LogicalPoint {
point.into().convert_to_logical(self.scale)
}
#[expect(private_bounds)]
pub fn physical<T: Into<Point>>(&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<L: ToPhysical<Self> + ?Sized> {
fn to_logical(self) -> L;
}
pub trait ToPhysical<P: ToLogical<Self> + ?Sized> {
fn to_physical(self) -> P;
}
trait IntoScaled<T: Scaled>: Sized {
fn into_scaled(self, scale: f64) -> T;
}
trait FromWithScale<T>: Sized {
fn from_with_scale(value: T, scale: f64) -> Self;
}
impl<T, U: Scaled> IntoScaled<U> for T
where
U: FromWithScale<T>,
{
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<PhysicalPoint> for LogicalPoint {
fn to_physical(self) -> PhysicalPoint {
PhysicalPoint { inner: self.inner, scale: self.scale }
}
}
impl FromWithScale<Point> 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<LogicalPoint> for PhysicalPoint {
fn to_logical(self) -> LogicalPoint {
LogicalPoint { inner: self.inner, scale: self.scale }
}
}
impl FromWithScale<Point> for PhysicalPoint {
fn from_with_scale(value: Point, scale: f64) -> Self {
Self { inner: value, scale }
}
}
pub trait Rect<P: Position>: 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<Point> 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<LogicalPoint> 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<PhysicalBounds> for LogicalBounds {
fn to_physical(self) -> PhysicalBounds {
PhysicalBounds {
offset: self.offset,
size: self.size,
scale: self.scale,
}
}
}
impl FromWithScale<Bounds> 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<PhysicalPoint> 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<LogicalBounds> for PhysicalBounds {
fn to_logical(self) -> LogicalBounds {
LogicalBounds {
offset: self.offset,
size: self.size,
scale: self.scale,
}
}
}
impl FromWithScale<Bounds> for PhysicalBounds {
fn from_with_scale(value: Bounds, scale: f64) -> Self {
Self {
offset: value.offset(),
size: value.size(),
scale,
}
}
}
impl Mul<f64> 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<f64> 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<f64> for Point {
type Output = Point;
fn add(self, rhs: f64) -> Self::Output {
Point { x: self.x + rhs, y: self.y + rhs }
}
}
impl Sub<f64> for Point {
type Output = Point;
fn sub(self, rhs: f64) -> Self::Output {
Point { x: self.x - rhs, y: self.y - rhs }
}
}
impl Mul<Point> 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<Point> 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<Point> 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<Point> 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<f64> 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<f64> 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<LogicalPoint> 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<LogicalPoint> 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<LogicalPoint> 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<LogicalPoint> 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<PhysicalPoint> 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<PhysicalPoint> 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<PhysicalPoint> 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<PhysicalPoint> 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<T: Scaled>(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<LogicalPoint> for (f64, f64) {
fn from(point: LogicalPoint) -> Self {
(point.x(), point.y())
}
}
impl From<PhysicalPoint> for (f64, f64) {
fn from(point: PhysicalPoint) -> Self {
(point.x(), point.y())
}
}
impl From<LogicalBounds> for (f64, f64, f64, f64) {
fn from(bounds: LogicalBounds) -> Self {
(bounds.x(), bounds.y(), bounds.width(), bounds.height())
}
}
impl From<PhysicalBounds> for (f64, f64, f64, f64) {
fn from(bounds: PhysicalBounds) -> Self {
(bounds.x(), bounds.y(), bounds.width(), bounds.height())
}
}
impl From<glam::DVec2> for Point {
fn from(vec: glam::DVec2) -> Self {
Self { x: vec.x, y: vec.y }
}
}
impl From<LogicalPoint> for glam::DVec2 {
fn from(val: LogicalPoint) -> Self {
glam::DVec2::new(val.x(), val.y())
}
}
impl From<PhysicalPoint> 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<LogicalBounds> 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<PhysicalBounds> 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())
}
}
+23 -17
View File
@@ -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<Message, String> {
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<NodeId>,
ignore_hash: bool,
) -> Result<Message, String> {
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
+8 -6
View File
@@ -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;
}