Add an editor preference for touched/enclosed/directional based selection (#2156)
* implemented left selection logic * added logic for right ward selection * removed the logs code * corrected capitalization error * corrected capitalization error * added radio buttons for selection_mode * fixed multiple selection of checkboxes * adapted to the RadioEntryData * State management bug * integrated message system to selection_mode * updated * updated * added selection mode to transition arms * removed from portfolio message and added preference in ToolMessageData * removed dead code of selection_mode from frontend logic * removed dead code for zoomWithScroll * Cleanup * Rename, simplify, use dashed box, and highlight only outlines of layers that'll get selected * More code review --------- Co-authored-by: Pratik Agrawal <patrik@Pratiks-MacBook-Air.local> Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
93880abc4c
commit
96c57605b7
|
|
@ -221,20 +221,21 @@ impl Dispatcher {
|
||||||
}
|
}
|
||||||
Message::Tool(message) => {
|
Message::Tool(message) => {
|
||||||
let document_id = self.message_handlers.portfolio_message_handler.active_document_id().unwrap();
|
let document_id = self.message_handlers.portfolio_message_handler.active_document_id().unwrap();
|
||||||
if let Some(document) = self.message_handlers.portfolio_message_handler.documents.get_mut(&document_id) {
|
let Some(document) = self.message_handlers.portfolio_message_handler.documents.get_mut(&document_id) else {
|
||||||
let data = ToolMessageData {
|
|
||||||
document_id,
|
|
||||||
document,
|
|
||||||
input: &self.message_handlers.input_preprocessor_message_handler,
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.message_handlers.tool_message_handler.process_message(message, &mut queue, data);
|
|
||||||
} else {
|
|
||||||
warn!("Called ToolMessage without an active document.\nGot {message:?}");
|
warn!("Called ToolMessage without an active document.\nGot {message:?}");
|
||||||
}
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let data = ToolMessageData {
|
||||||
|
document_id,
|
||||||
|
document,
|
||||||
|
input: &self.message_handlers.input_preprocessor_message_handler,
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.message_handlers.tool_message_handler.process_message(message, &mut queue, data);
|
||||||
}
|
}
|
||||||
Message::Workspace(message) => {
|
Message::Workspace(message) => {
|
||||||
self.message_handlers.workspace_message_handler.process_message(message, &mut queue, ());
|
self.message_handlers.workspace_message_handler.process_message(message, &mut queue, ());
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
|
use crate::messages::preferences::SelectionMode;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
pub struct PreferencesDialogMessageData<'a> {
|
pub struct PreferencesDialogMessageData<'a> {
|
||||||
|
|
@ -31,6 +32,39 @@ impl PreferencesDialogMessageHandler {
|
||||||
const TITLE: &'static str = "Editor Preferences";
|
const TITLE: &'static str = "Editor Preferences";
|
||||||
|
|
||||||
fn layout(&self, preferences: &PreferencesMessageHandler) -> Layout {
|
fn layout(&self, preferences: &PreferencesMessageHandler) -> Layout {
|
||||||
|
let selection_section = vec![TextLabel::new("Selection").italic(true).widget_holder()];
|
||||||
|
let selection_mode = RadioInput::new(vec![
|
||||||
|
RadioEntryData::new(SelectionMode::Touched.to_string())
|
||||||
|
.label(SelectionMode::Touched.to_string())
|
||||||
|
.tooltip(SelectionMode::Touched.tooltip_description())
|
||||||
|
.on_update(move |_| {
|
||||||
|
PreferencesMessage::SelectionMode {
|
||||||
|
selection_mode: SelectionMode::Touched,
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
RadioEntryData::new(SelectionMode::Enclosed.to_string())
|
||||||
|
.label(SelectionMode::Enclosed.to_string())
|
||||||
|
.tooltip(SelectionMode::Enclosed.tooltip_description())
|
||||||
|
.on_update(move |_| {
|
||||||
|
PreferencesMessage::SelectionMode {
|
||||||
|
selection_mode: SelectionMode::Enclosed,
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
RadioEntryData::new(SelectionMode::Directional.to_string())
|
||||||
|
.label(SelectionMode::Directional.to_string())
|
||||||
|
.tooltip(SelectionMode::Directional.tooltip_description())
|
||||||
|
.on_update(move |_| {
|
||||||
|
PreferencesMessage::SelectionMode {
|
||||||
|
selection_mode: SelectionMode::Directional,
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
.selected_index(Some(preferences.selection_mode as u32))
|
||||||
|
.widget_holder();
|
||||||
|
|
||||||
let zoom_with_scroll_tooltip = "Use the scroll wheel for zooming instead of vertically panning (not recommended for trackpads)";
|
let zoom_with_scroll_tooltip = "Use the scroll wheel for zooming instead of vertically panning (not recommended for trackpads)";
|
||||||
let input_section = vec![TextLabel::new("Input").italic(true).widget_holder()];
|
let input_section = vec![TextLabel::new("Input").italic(true).widget_holder()];
|
||||||
let zoom_with_scroll = vec![
|
let zoom_with_scroll = vec![
|
||||||
|
|
@ -43,9 +77,9 @@ impl PreferencesDialogMessageHandler {
|
||||||
.into()
|
.into()
|
||||||
})
|
})
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
|
||||||
TextLabel::new("Zoom with Scroll").table_align(true).tooltip(zoom_with_scroll_tooltip).widget_holder(),
|
TextLabel::new("Zoom with Scroll").table_align(true).tooltip(zoom_with_scroll_tooltip).widget_holder(),
|
||||||
];
|
];
|
||||||
|
|
||||||
let vello_tooltip = "Use the experimental Vello renderer (your browser must support WebGPU)";
|
let vello_tooltip = "Use the experimental Vello renderer (your browser must support WebGPU)";
|
||||||
let renderer_section = vec![TextLabel::new("Experimental").italic(true).widget_holder()];
|
let renderer_section = vec![TextLabel::new("Experimental").italic(true).widget_holder()];
|
||||||
let use_vello = vec![
|
let use_vello = vec![
|
||||||
|
|
@ -54,7 +88,6 @@ impl PreferencesDialogMessageHandler {
|
||||||
.disabled(!preferences.supports_wgpu())
|
.disabled(!preferences.supports_wgpu())
|
||||||
.on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::UseVello { use_vello: checkbox_input.checked }.into())
|
.on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::UseVello { use_vello: checkbox_input.checked }.into())
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
|
||||||
TextLabel::new("Vello Renderer")
|
TextLabel::new("Vello Renderer")
|
||||||
.table_align(true)
|
.table_align(true)
|
||||||
.tooltip(vello_tooltip)
|
.tooltip(vello_tooltip)
|
||||||
|
|
@ -62,11 +95,19 @@ impl PreferencesDialogMessageHandler {
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let vector_mesh_tooltip = "Allow tools to produce vector meshes, where more than two segments can connect to an anchor point.\n\nCurrently this does not properly handle line joins and fills.";
|
||||||
|
let vector_meshes = vec![
|
||||||
|
CheckboxInput::new(preferences.vector_meshes)
|
||||||
|
.tooltip(vector_mesh_tooltip)
|
||||||
|
.on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::VectorMeshes { enabled: checkbox_input.checked }.into())
|
||||||
|
.widget_holder(),
|
||||||
|
TextLabel::new("Vector Meshes").table_align(true).tooltip(vector_mesh_tooltip).widget_holder(),
|
||||||
|
];
|
||||||
|
|
||||||
// TODO: Reenable when Imaginate is restored
|
// TODO: Reenable when Imaginate is restored
|
||||||
// let imaginate_server_hostname = vec![
|
// let imaginate_server_hostname = vec![
|
||||||
// TextLabel::new("Imaginate").min_width(60).italic(true).widget_holder(),
|
// TextLabel::new("Imaginate").min_width(60).italic(true).widget_holder(),
|
||||||
// TextLabel::new("Server Hostname").table_align(true).widget_holder(),
|
// TextLabel::new("Server Hostname").table_align(true).widget_holder(),
|
||||||
// Separator::new(SeparatorType::Unrelated).widget_holder(),
|
|
||||||
// TextInput::new(&preferences.imaginate_server_hostname)
|
// TextInput::new(&preferences.imaginate_server_hostname)
|
||||||
// .min_width(200)
|
// .min_width(200)
|
||||||
// .on_update(|text_input: &TextInput| PreferencesMessage::ImaginateServerHostname { hostname: text_input.value.clone() }.into())
|
// .on_update(|text_input: &TextInput| PreferencesMessage::ImaginateServerHostname { hostname: text_input.value.clone() }.into())
|
||||||
|
|
@ -75,7 +116,6 @@ impl PreferencesDialogMessageHandler {
|
||||||
// let imaginate_refresh_frequency = vec![
|
// let imaginate_refresh_frequency = vec![
|
||||||
// TextLabel::new("").min_width(60).widget_holder(),
|
// TextLabel::new("").min_width(60).widget_holder(),
|
||||||
// TextLabel::new("Refresh Frequency").table_align(true).widget_holder(),
|
// TextLabel::new("Refresh Frequency").table_align(true).widget_holder(),
|
||||||
// Separator::new(SeparatorType::Unrelated).widget_holder(),
|
|
||||||
// NumberInput::new(Some(preferences.imaginate_refresh_frequency))
|
// NumberInput::new(Some(preferences.imaginate_refresh_frequency))
|
||||||
// .unit(" seconds")
|
// .unit(" seconds")
|
||||||
// .min(0.)
|
// .min(0.)
|
||||||
|
|
@ -85,17 +125,9 @@ impl PreferencesDialogMessageHandler {
|
||||||
// .widget_holder(),
|
// .widget_holder(),
|
||||||
// ];
|
// ];
|
||||||
|
|
||||||
let vector_mesh_tooltip = "Allow tools to produce vector meshes, where more than two segments can connect to an anchor point.\n\nCurrently this does not properly handle line joins and fills.";
|
|
||||||
let vector_meshes = vec![
|
|
||||||
CheckboxInput::new(preferences.vector_meshes)
|
|
||||||
.tooltip(vector_mesh_tooltip)
|
|
||||||
.on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::VectorMeshes { enabled: checkbox_input.checked }.into())
|
|
||||||
.widget_holder(),
|
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
|
||||||
TextLabel::new("Vector Meshes").table_align(true).tooltip(vector_mesh_tooltip).widget_holder(),
|
|
||||||
];
|
|
||||||
|
|
||||||
Layout::WidgetLayout(WidgetLayout::new(vec![
|
Layout::WidgetLayout(WidgetLayout::new(vec![
|
||||||
|
LayoutGroup::Row { widgets: selection_section },
|
||||||
|
LayoutGroup::Row { widgets: vec![selection_mode] },
|
||||||
LayoutGroup::Row { widgets: input_section },
|
LayoutGroup::Row { widgets: input_section },
|
||||||
LayoutGroup::Row { widgets: zoom_with_scroll },
|
LayoutGroup::Row { widgets: zoom_with_scroll },
|
||||||
LayoutGroup::Row { widgets: renderer_section },
|
LayoutGroup::Row { widgets: renderer_section },
|
||||||
|
|
|
||||||
|
|
@ -1407,6 +1407,29 @@ impl DocumentMessageHandler {
|
||||||
self.intersect_quad(viewport_quad, ipp).filter(|layer| !self.network_interface.is_artboard(&layer.to_node(), &[]))
|
self.intersect_quad(viewport_quad, ipp).filter(|layer| !self.network_interface.is_artboard(&layer.to_node(), &[]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_layer_fully_inside(&self, layer: &LayerNodeIdentifier, quad: graphene_core::renderer::Quad) -> bool {
|
||||||
|
// Get the bounding box of the layer in document space
|
||||||
|
let Some(bounding_box) = self.metadata().bounding_box_viewport(*layer) else { return false };
|
||||||
|
|
||||||
|
// Check if the bounding box is fully within the selection quad
|
||||||
|
let [top_left, bottom_right] = bounding_box;
|
||||||
|
|
||||||
|
let quad_bbox = quad.bounding_box();
|
||||||
|
|
||||||
|
let quad_left = quad_bbox[0].x;
|
||||||
|
let quad_right = quad_bbox[1].x;
|
||||||
|
let quad_top = quad_bbox[0].y.max(quad_bbox[1].y); // Correct top
|
||||||
|
let quad_bottom = quad_bbox[0].y.min(quad_bbox[1].y); // Correct bottom
|
||||||
|
|
||||||
|
// Extract layer's bounding box coordinates
|
||||||
|
let layer_left = top_left.x;
|
||||||
|
let layer_right = bottom_right.x;
|
||||||
|
let layer_top = bottom_right.y;
|
||||||
|
let layer_bottom = top_left.y;
|
||||||
|
|
||||||
|
layer_left >= quad_left && layer_right <= quad_right && layer_top <= quad_top && layer_bottom >= quad_bottom
|
||||||
|
}
|
||||||
|
|
||||||
/// Find all of the layers that were clicked on from a viewport space location
|
/// 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> + '_ {
|
pub fn click_xray(&self, ipp: &InputPreprocessorMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
|
||||||
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(ipp.viewport_bounds.center(), &self.document_ptz);
|
||||||
|
|
|
||||||
|
|
@ -460,7 +460,7 @@ impl DoubleEndedIterator for DescendantsIter<'_> {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
pub struct NodeRelations {
|
pub struct NodeRelations {
|
||||||
parent: Option<LayerNodeIdentifier>,
|
pub parent: Option<LayerNodeIdentifier>,
|
||||||
previous_sibling: Option<LayerNodeIdentifier>,
|
previous_sibling: Option<LayerNodeIdentifier>,
|
||||||
next_sibling: Option<LayerNodeIdentifier>,
|
next_sibling: Option<LayerNodeIdentifier>,
|
||||||
first_child: Option<LayerNodeIdentifier>,
|
first_child: Option<LayerNodeIdentifier>,
|
||||||
|
|
|
||||||
|
|
@ -46,16 +46,16 @@ pub enum PortfolioMessage {
|
||||||
document_id: DocumentId,
|
document_id: DocumentId,
|
||||||
},
|
},
|
||||||
DestroyAllDocuments,
|
DestroyAllDocuments,
|
||||||
|
EditorPreferences,
|
||||||
FontLoaded {
|
FontLoaded {
|
||||||
font_family: String,
|
font_family: String,
|
||||||
font_style: String,
|
font_style: String,
|
||||||
preview_url: String,
|
preview_url: String,
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
},
|
},
|
||||||
ImaginateCheckServerStatus,
|
// ImaginateCheckServerStatus,
|
||||||
ImaginatePollServerStatus,
|
// ImaginatePollServerStatus,
|
||||||
EditorPreferences,
|
// ImaginateServerHostname,
|
||||||
ImaginateServerHostname,
|
|
||||||
Import,
|
Import,
|
||||||
LoadDocumentResources {
|
LoadDocumentResources {
|
||||||
document_id: DocumentId,
|
document_id: DocumentId,
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
||||||
use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
|
use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
|
||||||
use crate::messages::portfolio::document::DocumentMessageData;
|
use crate::messages::portfolio::document::DocumentMessageData;
|
||||||
|
use crate::messages::preferences::SelectionMode;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType};
|
use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType};
|
||||||
use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor};
|
use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor};
|
||||||
|
|
@ -38,6 +39,7 @@ pub struct PortfolioMessageHandler {
|
||||||
copy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize],
|
copy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize],
|
||||||
pub persistent_data: PersistentData,
|
pub persistent_data: PersistentData,
|
||||||
pub executor: NodeGraphExecutor,
|
pub executor: NodeGraphExecutor,
|
||||||
|
pub selection_mode: SelectionMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMessageHandler {
|
impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMessageHandler {
|
||||||
|
|
@ -295,35 +297,35 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
||||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PortfolioMessage::ImaginateCheckServerStatus => {
|
// PortfolioMessage::ImaginateCheckServerStatus => {
|
||||||
let server_status = self.persistent_data.imaginate.server_status().clone();
|
// let server_status = self.persistent_data.imaginate.server_status().clone();
|
||||||
self.persistent_data.imaginate.poll_server_check();
|
// self.persistent_data.imaginate.poll_server_check();
|
||||||
#[cfg(target_arch = "wasm32")]
|
// #[cfg(target_arch = "wasm32")]
|
||||||
if let Some(fut) = self.persistent_data.imaginate.initiate_server_check() {
|
// if let Some(fut) = self.persistent_data.imaginate.initiate_server_check() {
|
||||||
wasm_bindgen_futures::spawn_local(async move {
|
// wasm_bindgen_futures::spawn_local(async move {
|
||||||
let () = fut.await;
|
// let () = fut.await;
|
||||||
use wasm_bindgen::prelude::*;
|
// use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
#[wasm_bindgen(module = "/../frontend/src/editor.ts")]
|
// #[wasm_bindgen(module = "/../frontend/src/editor.ts")]
|
||||||
extern "C" {
|
// extern "C" {
|
||||||
#[wasm_bindgen(js_name = injectImaginatePollServerStatus)]
|
// #[wasm_bindgen(js_name = injectImaginatePollServerStatus)]
|
||||||
fn inject();
|
// fn inject();
|
||||||
}
|
// }
|
||||||
inject();
|
// inject();
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
if &server_status != self.persistent_data.imaginate.server_status() {
|
// if &server_status != self.persistent_data.imaginate.server_status() {
|
||||||
responses.add(PropertiesPanelMessage::Refresh);
|
// responses.add(PropertiesPanelMessage::Refresh);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
PortfolioMessage::ImaginatePollServerStatus => {
|
// PortfolioMessage::ImaginatePollServerStatus => {
|
||||||
self.persistent_data.imaginate.poll_server_check();
|
// self.persistent_data.imaginate.poll_server_check();
|
||||||
responses.add(PropertiesPanelMessage::Refresh);
|
// responses.add(PropertiesPanelMessage::Refresh);
|
||||||
}
|
// }
|
||||||
PortfolioMessage::EditorPreferences => self.executor.update_editor_preferences(preferences.editor_preferences()),
|
PortfolioMessage::EditorPreferences => self.executor.update_editor_preferences(preferences.editor_preferences()),
|
||||||
PortfolioMessage::ImaginateServerHostname => {
|
// PortfolioMessage::ImaginateServerHostname => {
|
||||||
self.persistent_data.imaginate.set_host_name(&preferences.imaginate_server_hostname);
|
// self.persistent_data.imaginate.set_host_name(&preferences.imaginate_server_hostname);
|
||||||
}
|
// }
|
||||||
PortfolioMessage::Import => {
|
PortfolioMessage::Import => {
|
||||||
// This portfolio message wraps the frontend message so it can be listed as an action, which isn't possible for frontend messages
|
// This portfolio message wraps the frontend message so it can be listed as an action, which isn't possible for frontend messages
|
||||||
responses.add(FrontendMessage::TriggerImport);
|
responses.add(FrontendMessage::TriggerImport);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use graphene_std::{imaginate::ImaginatePersistentData, text::FontCache};
|
use graphene_std::imaginate::ImaginatePersistentData;
|
||||||
|
use graphene_std::text::FontCache;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct PersistentData {
|
pub struct PersistentData {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
mod preferences_message;
|
mod preferences_message;
|
||||||
mod preferences_message_handler;
|
mod preferences_message_handler;
|
||||||
|
pub mod utility_types;
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use preferences_message::{PreferencesMessage, PreferencesMessageDiscriminant};
|
pub use preferences_message::{PreferencesMessage, PreferencesMessageDiscriminant};
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use preferences_message_handler::PreferencesMessageHandler;
|
pub use preferences_message_handler::PreferencesMessageHandler;
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use utility_types::SelectionMode;
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,18 @@
|
||||||
|
use crate::messages::preferences::SelectionMode;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
#[impl_message(Message, Preferences)]
|
#[impl_message(Message, Preferences)]
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum PreferencesMessage {
|
pub enum PreferencesMessage {
|
||||||
|
// Management messages
|
||||||
Load { preferences: String },
|
Load { preferences: String },
|
||||||
ResetToDefaults,
|
ResetToDefaults,
|
||||||
|
|
||||||
ImaginateRefreshFrequency { seconds: f64 },
|
// Per-preference messages
|
||||||
UseVello { use_vello: bool },
|
UseVello { use_vello: bool },
|
||||||
ImaginateServerHostname { hostname: String },
|
SelectionMode { selection_mode: SelectionMode },
|
||||||
VectorMeshes { enabled: bool },
|
VectorMeshes { enabled: bool },
|
||||||
ModifyLayout { zoom_with_scroll: bool },
|
ModifyLayout { zoom_with_scroll: bool },
|
||||||
|
// ImaginateRefreshFrequency { seconds: f64 },
|
||||||
|
// ImaginateServerHostname { hostname: String },
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,24 @@
|
||||||
use crate::messages::input_mapper::key_mapping::MappingVariant;
|
use crate::messages::input_mapper::key_mapping::MappingVariant;
|
||||||
|
use crate::messages::preferences::SelectionMode;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
use graph_craft::wasm_application_io::EditorPreferences;
|
use graph_craft::wasm_application_io::EditorPreferences;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
pub struct PreferencesMessageHandler {
|
pub struct PreferencesMessageHandler {
|
||||||
pub imaginate_server_hostname: String,
|
pub imaginate_server_hostname: String,
|
||||||
pub imaginate_refresh_frequency: f64,
|
pub imaginate_refresh_frequency: f64,
|
||||||
|
pub selection_mode: SelectionMode,
|
||||||
pub zoom_with_scroll: bool,
|
pub zoom_with_scroll: bool,
|
||||||
pub use_vello: bool,
|
pub use_vello: bool,
|
||||||
pub vector_meshes: bool,
|
pub vector_meshes: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PreferencesMessageHandler {
|
impl PreferencesMessageHandler {
|
||||||
|
pub fn get_selection_mode(&self) -> SelectionMode {
|
||||||
|
self.selection_mode
|
||||||
|
}
|
||||||
|
|
||||||
pub fn editor_preferences(&self) -> EditorPreferences {
|
pub fn editor_preferences(&self) -> EditorPreferences {
|
||||||
EditorPreferences {
|
EditorPreferences {
|
||||||
imaginate_hostname: self.imaginate_server_hostname.clone(),
|
imaginate_hostname: self.imaginate_server_hostname.clone(),
|
||||||
|
|
@ -33,6 +40,7 @@ impl Default for PreferencesMessageHandler {
|
||||||
Self {
|
Self {
|
||||||
imaginate_server_hostname: host_name,
|
imaginate_server_hostname: host_name,
|
||||||
imaginate_refresh_frequency: 1.,
|
imaginate_refresh_frequency: 1.,
|
||||||
|
selection_mode: SelectionMode::Touched,
|
||||||
zoom_with_scroll: matches!(MappingVariant::default(), MappingVariant::ZoomWithScroll),
|
zoom_with_scroll: matches!(MappingVariant::default(), MappingVariant::ZoomWithScroll),
|
||||||
use_vello,
|
use_vello,
|
||||||
vector_meshes: false,
|
vector_meshes: false,
|
||||||
|
|
@ -43,6 +51,7 @@ impl Default for PreferencesMessageHandler {
|
||||||
impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
|
impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
|
||||||
fn process_message(&mut self, message: PreferencesMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
fn process_message(&mut self, message: PreferencesMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
||||||
match message {
|
match message {
|
||||||
|
// Management messages
|
||||||
PreferencesMessage::Load { preferences } => {
|
PreferencesMessage::Load { preferences } => {
|
||||||
if let Ok(deserialized_preferences) = serde_json::from_str::<PreferencesMessageHandler>(&preferences) {
|
if let Ok(deserialized_preferences) = serde_json::from_str::<PreferencesMessageHandler>(&preferences) {
|
||||||
*self = deserialized_preferences;
|
*self = deserialized_preferences;
|
||||||
|
|
@ -65,31 +74,12 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
|
||||||
*self = Self::default()
|
*self = Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
PreferencesMessage::ImaginateRefreshFrequency { seconds } => {
|
// Per-preference messages
|
||||||
self.imaginate_refresh_frequency = seconds;
|
|
||||||
responses.add(PortfolioMessage::ImaginateCheckServerStatus);
|
|
||||||
responses.add(PortfolioMessage::EditorPreferences);
|
|
||||||
}
|
|
||||||
PreferencesMessage::UseVello { use_vello } => {
|
PreferencesMessage::UseVello { use_vello } => {
|
||||||
self.use_vello = use_vello;
|
self.use_vello = use_vello;
|
||||||
responses.add(PortfolioMessage::UpdateVelloPreference);
|
responses.add(PortfolioMessage::UpdateVelloPreference);
|
||||||
responses.add(PortfolioMessage::EditorPreferences);
|
responses.add(PortfolioMessage::EditorPreferences);
|
||||||
}
|
}
|
||||||
PreferencesMessage::ImaginateServerHostname { hostname } => {
|
|
||||||
let initial = hostname.clone();
|
|
||||||
let has_protocol = hostname.starts_with("http://") || hostname.starts_with("https://");
|
|
||||||
let hostname = if has_protocol { hostname } else { "http://".to_string() + &hostname };
|
|
||||||
let hostname = if hostname.ends_with('/') { hostname } else { hostname + "/" };
|
|
||||||
|
|
||||||
if hostname != initial {
|
|
||||||
refresh_dialog(responses);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.imaginate_server_hostname = hostname;
|
|
||||||
responses.add(PortfolioMessage::ImaginateServerHostname);
|
|
||||||
responses.add(PortfolioMessage::ImaginateCheckServerStatus);
|
|
||||||
responses.add(PortfolioMessage::EditorPreferences);
|
|
||||||
}
|
|
||||||
PreferencesMessage::VectorMeshes { enabled } => {
|
PreferencesMessage::VectorMeshes { enabled } => {
|
||||||
self.vector_meshes = enabled;
|
self.vector_meshes = enabled;
|
||||||
}
|
}
|
||||||
|
|
@ -102,7 +92,31 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
|
||||||
};
|
};
|
||||||
responses.add(KeyMappingMessage::ModifyMapping(variant));
|
responses.add(KeyMappingMessage::ModifyMapping(variant));
|
||||||
}
|
}
|
||||||
|
PreferencesMessage::SelectionMode { selection_mode } => {
|
||||||
|
self.selection_mode = selection_mode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// TODO: Reenable when Imaginate is restored (and move back up one line since the auto-formatter doesn't like it in that block)
|
||||||
|
// PreferencesMessage::ImaginateRefreshFrequency { seconds } => {
|
||||||
|
// self.imaginate_refresh_frequency = seconds;
|
||||||
|
// responses.add(PortfolioMessage::ImaginateCheckServerStatus);
|
||||||
|
// responses.add(PortfolioMessage::EditorPreferences);
|
||||||
|
// }
|
||||||
|
// PreferencesMessage::ImaginateServerHostname { hostname } => {
|
||||||
|
// let initial = hostname.clone();
|
||||||
|
// let has_protocol = hostname.starts_with("http://") || hostname.starts_with("https://");
|
||||||
|
// let hostname = if has_protocol { hostname } else { "http://".to_string() + &hostname };
|
||||||
|
// let hostname = if hostname.ends_with('/') { hostname } else { hostname + "/" };
|
||||||
|
|
||||||
|
// if hostname != initial {
|
||||||
|
// refresh_dialog(responses);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// self.imaginate_server_hostname = hostname;
|
||||||
|
// responses.add(PortfolioMessage::ImaginateServerHostname);
|
||||||
|
// responses.add(PortfolioMessage::ImaginateCheckServerStatus);
|
||||||
|
// responses.add(PortfolioMessage::EditorPreferences);
|
||||||
|
//}
|
||||||
|
|
||||||
responses.add(FrontendMessage::TriggerSavePreferences { preferences: self.clone() });
|
responses.add(FrontendMessage::TriggerSavePreferences { preferences: self.clone() });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type, Hash)]
|
||||||
|
pub enum SelectionMode {
|
||||||
|
#[default]
|
||||||
|
Touched = 0,
|
||||||
|
Enclosed = 1,
|
||||||
|
Directional = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for SelectionMode {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
SelectionMode::Touched => write!(f, "Touched"),
|
||||||
|
SelectionMode::Enclosed => write!(f, "Enclosed"),
|
||||||
|
SelectionMode::Directional => write!(f, "Directional"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SelectionMode {
|
||||||
|
pub fn tooltip_description(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
SelectionMode::Touched => "Select all layers at least partially covered by the dragged selection area",
|
||||||
|
SelectionMode::Enclosed => "Select only layers fully enclosed by the dragged selection area",
|
||||||
|
SelectionMode::Directional => r#""Touched" for leftward drags, "Enclosed" for rightward drags"#,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,11 +21,11 @@ pub struct SizeSnapData<'a> {
|
||||||
/// Contains the edges that are being dragged along with the original bounds.
|
/// Contains the edges that are being dragged along with the original bounds.
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct SelectedEdges {
|
pub struct SelectedEdges {
|
||||||
bounds: [DVec2; 2],
|
pub bounds: [DVec2; 2],
|
||||||
top: bool,
|
pub top: bool,
|
||||||
bottom: bool,
|
pub bottom: bool,
|
||||||
left: bool,
|
pub left: bool,
|
||||||
right: bool,
|
pub right: bool,
|
||||||
// Aspect ratio in the form of width/height, so x:1 = width:height
|
// Aspect ratio in the form of width/height, so x:1 = width:height
|
||||||
aspect_ratio: f64,
|
aspect_ratio: f64,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use super::utility_types::ToolType;
|
use super::utility_types::ToolType;
|
||||||
|
use crate::messages::preferences::SelectionMode;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
use graphene_core::raster::color::Color;
|
use graphene_core::raster::color::Color;
|
||||||
|
|
@ -98,4 +99,7 @@ pub enum ToolMessage {
|
||||||
Undo,
|
Undo,
|
||||||
UpdateCursor,
|
UpdateCursor,
|
||||||
UpdateHints,
|
UpdateHints,
|
||||||
|
UpdateSelectionMode {
|
||||||
|
selection_mode: SelectionMode,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
|
||||||
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis};
|
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis};
|
||||||
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, NodeNetworkInterface, NodeTemplate};
|
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, NodeNetworkInterface, NodeTemplate};
|
||||||
use crate::messages::portfolio::document::utility_types::transformation::Selected;
|
use crate::messages::portfolio::document::utility_types::transformation::Selected;
|
||||||
|
use crate::messages::preferences::SelectionMode;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils::{get_text, is_layer_fed_by_node_of_name};
|
use crate::messages::tool::common_functionality::graph_modification_utils::{get_text, is_layer_fed_by_node_of_name};
|
||||||
use crate::messages::tool::common_functionality::pivot::Pivot;
|
use crate::messages::tool::common_functionality::pivot::Pivot;
|
||||||
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapManager};
|
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapManager};
|
||||||
|
|
@ -250,12 +251,13 @@ impl ToolTransition for SelectTool {
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
enum SelectToolFsmState {
|
enum SelectToolFsmState {
|
||||||
Ready { selection: NestedSelectionBehavior },
|
Ready { selection: NestedSelectionBehavior },
|
||||||
DrawingBox { selection: NestedSelectionBehavior },
|
DrawingBox,
|
||||||
Dragging,
|
Dragging,
|
||||||
ResizingBounds,
|
ResizingBounds,
|
||||||
RotatingBounds,
|
RotatingBounds,
|
||||||
DraggingPivot,
|
DraggingPivot,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SelectToolFsmState {
|
impl Default for SelectToolFsmState {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let selection = NestedSelectionBehavior::Deepest;
|
let selection = NestedSelectionBehavior::Deepest;
|
||||||
|
|
@ -297,12 +299,22 @@ impl SelectToolData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selection_quad(&self) -> Quad {
|
pub fn selection_quad(&self) -> Quad {
|
||||||
let bbox = self.selection_box();
|
let bbox = self.selection_box();
|
||||||
Quad::from_box(bbox)
|
Quad::from_box(bbox)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selection_box(&self) -> [DVec2; 2] {
|
pub fn calculate_direction(&self) -> SelectionMode {
|
||||||
|
let bbox: [DVec2; 2] = self.selection_box();
|
||||||
|
if bbox[1].x < bbox[0].x {
|
||||||
|
SelectionMode::Touched
|
||||||
|
} else {
|
||||||
|
// This also covers the case where they're equal: the area is zero, so we use `Enclosed` to ensure the selection ends up empty, as nothing will be enclosed by an empty area
|
||||||
|
SelectionMode::Enclosed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn selection_box(&self) -> [DVec2; 2] {
|
||||||
if self.drag_current == self.drag_start {
|
if self.drag_current == self.drag_start {
|
||||||
let tolerance = DVec2::splat(SELECTION_TOLERANCE);
|
let tolerance = DVec2::splat(SELECTION_TOLERANCE);
|
||||||
[self.drag_start - tolerance, self.drag_start + tolerance]
|
[self.drag_start - tolerance, self.drag_start + tolerance]
|
||||||
|
|
@ -475,21 +487,41 @@ impl Fsm for SelectToolFsmState {
|
||||||
tool_data.pivot.update_pivot(document, &mut overlay_context);
|
tool_data.pivot.update_pivot(document, &mut overlay_context);
|
||||||
|
|
||||||
// Check if the tool is in box selection mode
|
// Check if the tool is in box selection mode
|
||||||
if matches!(self, Self::DrawingBox { .. }) {
|
if matches!(self, Self::DrawingBox) {
|
||||||
// Get the updated selection box bounds
|
// Get the updated selection box bounds
|
||||||
let quad = Quad::from_box([tool_data.drag_start, tool_data.drag_current]);
|
let quad = Quad::from_box([tool_data.drag_start, tool_data.drag_current]);
|
||||||
|
|
||||||
|
let mut selection_direction = tool_action_data.preferences.get_selection_mode();
|
||||||
|
if selection_direction == SelectionMode::Directional {
|
||||||
|
selection_direction = tool_data.calculate_direction();
|
||||||
|
}
|
||||||
|
|
||||||
// Draw outline visualizations on the layers to be selected
|
// Draw outline visualizations on the layers to be selected
|
||||||
for layer in document.intersect_quad_no_artboards(quad, input) {
|
let mut draw_layer_outline = |layer| overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer));
|
||||||
overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer));
|
let intersection = document.intersect_quad_no_artboards(quad, input);
|
||||||
|
if selection_direction == SelectionMode::Enclosed {
|
||||||
|
for layer in intersection.filter(|layer| document.is_layer_fully_inside(layer, quad)) {
|
||||||
|
draw_layer_outline(layer);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for layer in intersection {
|
||||||
|
draw_layer_outline(layer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the selection box
|
// Update the selection box
|
||||||
let fill_color = graphene_std::Color::from_rgb_str(crate::consts::COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap())
|
let mut fill_color = graphene_std::Color::from_rgb_str(crate::consts::COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.with_alpha(0.05)
|
.with_alpha(0.05)
|
||||||
.rgba_hex();
|
.rgba_hex();
|
||||||
overlay_context.quad(quad, Some(&("#".to_string() + &fill_color)));
|
fill_color.insert(0, '#');
|
||||||
|
let fill_color = Some(fill_color.as_str());
|
||||||
|
|
||||||
|
if selection_direction == SelectionMode::Enclosed {
|
||||||
|
overlay_context.dashed_quad(quad, fill_color, Some(4.), Some(4.), Some(0.5));
|
||||||
|
} else {
|
||||||
|
overlay_context.quad(quad, fill_color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Only highlight layers if the viewport is not being panned (middle mouse button is pressed)
|
// Only highlight layers if the viewport is not being panned (middle mouse button is pressed)
|
||||||
// TODO: Don't use `Key::Mmb` directly, instead take it as a variable from the input mappings list like in all other places
|
// TODO: Don't use `Key::Mmb` directly, instead take it as a variable from the input mappings list like in all other places
|
||||||
|
|
@ -675,9 +707,7 @@ impl Fsm for SelectToolFsmState {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
SelectToolFsmState::Dragging
|
SelectToolFsmState::Dragging
|
||||||
} else {
|
} else {
|
||||||
// Make a box selection, preserving previously selected layers
|
SelectToolFsmState::DrawingBox
|
||||||
let selection = tool_data.nested_selection_behavior;
|
|
||||||
SelectToolFsmState::DrawingBox { selection }
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
tool_data.non_duplicated_layers = None;
|
tool_data.non_duplicated_layers = None;
|
||||||
|
|
@ -824,7 +854,7 @@ impl Fsm for SelectToolFsmState {
|
||||||
|
|
||||||
SelectToolFsmState::DraggingPivot
|
SelectToolFsmState::DraggingPivot
|
||||||
}
|
}
|
||||||
(SelectToolFsmState::DrawingBox { .. }, SelectToolMessage::PointerMove(modifier_keys)) => {
|
(SelectToolFsmState::DrawingBox, SelectToolMessage::PointerMove(modifier_keys)) => {
|
||||||
tool_data.drag_current = input.mouse.position;
|
tool_data.drag_current = input.mouse.position;
|
||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
|
||||||
|
|
@ -835,8 +865,7 @@ impl Fsm for SelectToolFsmState {
|
||||||
];
|
];
|
||||||
tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses);
|
tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses);
|
||||||
|
|
||||||
let selection = tool_data.nested_selection_behavior;
|
SelectToolFsmState::DrawingBox
|
||||||
SelectToolFsmState::DrawingBox { selection }
|
|
||||||
}
|
}
|
||||||
(SelectToolFsmState::Ready { .. }, SelectToolMessage::PointerMove(_)) => {
|
(SelectToolFsmState::Ready { .. }, SelectToolMessage::PointerMove(_)) => {
|
||||||
let mut cursor = tool_data.bounding_box_manager.as_ref().map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true));
|
let mut cursor = tool_data.bounding_box_manager.as_ref().map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true));
|
||||||
|
|
@ -883,7 +912,7 @@ impl Fsm for SelectToolFsmState {
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
(SelectToolFsmState::DrawingBox { .. }, SelectToolMessage::PointerOutsideViewport(_)) => {
|
(SelectToolFsmState::DrawingBox, SelectToolMessage::PointerOutsideViewport(_)) => {
|
||||||
// AutoPanning
|
// AutoPanning
|
||||||
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) {
|
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) {
|
||||||
tool_data.drag_start += shift;
|
tool_data.drag_start += shift;
|
||||||
|
|
@ -1012,14 +1041,26 @@ impl Fsm for SelectToolFsmState {
|
||||||
SelectToolFsmState::Ready { selection }
|
SelectToolFsmState::Ready { selection }
|
||||||
}
|
}
|
||||||
(
|
(
|
||||||
SelectToolFsmState::DrawingBox { .. },
|
SelectToolFsmState::DrawingBox,
|
||||||
SelectToolMessage::DragStop {
|
SelectToolMessage::DragStop {
|
||||||
remove_from_selection,
|
remove_from_selection,
|
||||||
negative_box_selection,
|
negative_box_selection,
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
let quad = tool_data.selection_quad();
|
let quad = tool_data.selection_quad();
|
||||||
let new_selected: HashSet<_> = document.intersect_quad_no_artboards(quad, input).collect();
|
|
||||||
|
let mut selection_direction = tool_action_data.preferences.get_selection_mode();
|
||||||
|
if selection_direction == SelectionMode::Directional {
|
||||||
|
selection_direction = tool_data.calculate_direction();
|
||||||
|
}
|
||||||
|
|
||||||
|
let intersection = document.intersect_quad_no_artboards(quad, input);
|
||||||
|
let new_selected: HashSet<_> = if selection_direction == SelectionMode::Enclosed {
|
||||||
|
intersection.filter(|layer| document.is_layer_fully_inside(layer, quad)).collect()
|
||||||
|
} else {
|
||||||
|
intersection.collect()
|
||||||
|
};
|
||||||
|
|
||||||
let current_selected: HashSet<_> = document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata()).collect();
|
let current_selected: HashSet<_> = document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata()).collect();
|
||||||
if new_selected != current_selected {
|
if new_selected != current_selected {
|
||||||
// Negative selection when both Shift and Ctrl are pressed
|
// Negative selection when both Shift and Ctrl are pressed
|
||||||
|
|
@ -1166,7 +1207,7 @@ impl Fsm for SelectToolFsmState {
|
||||||
]);
|
]);
|
||||||
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
||||||
}
|
}
|
||||||
SelectToolFsmState::DrawingBox { .. } => {
|
SelectToolFsmState::DrawingBox => {
|
||||||
let hint_data = HintData(vec![
|
let hint_data = HintData(vec![
|
||||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
||||||
HintGroup(vec![HintInfo::keys([Key::Control, Key::Shift], "Remove from Selection").add_mac_keys([Key::Command, Key::Shift])]),
|
HintGroup(vec![HintInfo::keys([Key::Control, Key::Shift], "Remove from Selection").add_mac_keys([Key::Command, Key::Shift])]),
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use crate::messages::input_mapper::utility_types::macros::action_keys;
|
||||||
use crate::messages::input_mapper::utility_types::misc::ActionKeys;
|
use crate::messages::input_mapper::utility_types::misc::ActionKeys;
|
||||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayProvider;
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayProvider;
|
||||||
|
use crate::messages::preferences::PreferencesMessageHandler;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use crate::node_graph_executor::NodeGraphExecutor;
|
use crate::node_graph_executor::NodeGraphExecutor;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ export function createEditor(): Editor {
|
||||||
// TODO: Then, delete the `(window as any).editorHandle = handle;` line above.
|
// TODO: Then, delete the `(window as any).editorHandle = handle;` line above.
|
||||||
// This function is called by an FFI binding within the Rust code directly, rather than using the FrontendMessage system.
|
// This function is called by an FFI binding within the Rust code directly, rather than using the FrontendMessage system.
|
||||||
// Then, this directly calls the `injectImaginatePollServerStatus` function on the `EditorHandle` object which is a JS binding generated by wasm-bindgen, going straight back into the Rust code.
|
// Then, this directly calls the `injectImaginatePollServerStatus` function on the `EditorHandle` object which is a JS binding generated by wasm-bindgen, going straight back into the Rust code.
|
||||||
export function injectImaginatePollServerStatus() {
|
// export function injectImaginatePollServerStatus() {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(window as any).editorHandle?.injectImaginatePollServerStatus();
|
// (window as any).editorHandle?.injectImaginatePollServerStatus();
|
||||||
}
|
// }
|
||||||
|
|
|
||||||
|
|
@ -725,10 +725,10 @@ impl EditorHandle {
|
||||||
self.dispatch(message);
|
self.dispatch(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = injectImaginatePollServerStatus)]
|
// #[wasm_bindgen(js_name = injectImaginatePollServerStatus)]
|
||||||
pub fn inject_imaginate_poll_server_status(&self) {
|
// pub fn inject_imaginate_poll_server_status(&self) {
|
||||||
self.dispatch(PortfolioMessage::ImaginatePollServerStatus);
|
// self.dispatch(PortfolioMessage::ImaginatePollServerStatus);
|
||||||
}
|
// }
|
||||||
|
|
||||||
// TODO: Eventually remove this document upgrade code
|
// TODO: Eventually remove this document upgrade code
|
||||||
#[wasm_bindgen(js_name = triggerUpgradeDocumentToVectorManipulationFormat)]
|
#[wasm_bindgen(js_name = triggerUpgradeDocumentToVectorManipulationFormat)]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue