mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-30 06:18:12 +08:00
Add 'Zoom with Scroll' input navigation scheme to preferences (#1021)
* Add use_scroll_as_zoom field to preference handler
* Add {Create,Delete}Mapping variants to message
* Revert "Add {Create,Delete}Mapping variants to message"
This reverts commit 0ba74754c9fb0c78d0b590c96e1d4fe2cfdd13e7.
* Revert "Add use_scroll_as_zoom field to preference handler"
This reverts commit d30f7c9edfa6d6e156939ca07f4db81f288975fd.
* Add basic scroll_as_zoom mapping
* Create overengineered mapping patch abstraction
* Add (for now passthrough) input layout manager
* Actually handle ModifyLayout messages (untested)
* Add backend preferences <-> layout manager comms
* Add scroll-as-zoom to actual preferences UI
* Rename LayoutManager -> KeyMapping
* Add Input section to preferences and title case
* Add scrollAsZoom frontend handling code (untested)
* Handle frontend <-> preferences comms
* (broken) Move scrollAsZoom persistence into state
* Fix scrollAsZoom having no effect on node graph
* Remove debugging helpers
* Fix confusion between horizontal and vertical
* Rename feature
* Move new message handler into folder
---------
Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
committed by
Keavon Chambers
co-authored by
Keavon Chambers
parent
c2234ce3fe
commit
7a52e50a94
+10
-10
@@ -18,8 +18,8 @@ struct DispatcherMessageHandlers {
|
|||||||
debug_message_handler: DebugMessageHandler,
|
debug_message_handler: DebugMessageHandler,
|
||||||
dialog_message_handler: DialogMessageHandler,
|
dialog_message_handler: DialogMessageHandler,
|
||||||
globals_message_handler: GlobalsMessageHandler,
|
globals_message_handler: GlobalsMessageHandler,
|
||||||
input_mapper_message_handler: InputMapperMessageHandler,
|
|
||||||
input_preprocessor_message_handler: InputPreprocessorMessageHandler,
|
input_preprocessor_message_handler: InputPreprocessorMessageHandler,
|
||||||
|
key_mapping_message_handler: KeyMappingMessageHandler,
|
||||||
layout_message_handler: LayoutMessageHandler,
|
layout_message_handler: LayoutMessageHandler,
|
||||||
portfolio_message_handler: PortfolioMessageHandler,
|
portfolio_message_handler: PortfolioMessageHandler,
|
||||||
preferences_message_handler: PreferencesMessageHandler,
|
preferences_message_handler: PreferencesMessageHandler,
|
||||||
@@ -133,20 +133,20 @@ impl Dispatcher {
|
|||||||
Globals(message) => {
|
Globals(message) => {
|
||||||
self.message_handlers.globals_message_handler.process_message(message, &mut queue, ());
|
self.message_handlers.globals_message_handler.process_message(message, &mut queue, ());
|
||||||
}
|
}
|
||||||
InputMapper(message) => {
|
|
||||||
let actions = self.collect_actions();
|
|
||||||
|
|
||||||
self.message_handlers
|
|
||||||
.input_mapper_message_handler
|
|
||||||
.process_message(message, &mut queue, (&self.message_handlers.input_preprocessor_message_handler, actions));
|
|
||||||
}
|
|
||||||
InputPreprocessor(message) => {
|
InputPreprocessor(message) => {
|
||||||
let keyboard_platform = GLOBAL_PLATFORM.get().copied().unwrap_or_default().as_keyboard_platform_layout();
|
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, keyboard_platform);
|
self.message_handlers.input_preprocessor_message_handler.process_message(message, &mut queue, keyboard_platform);
|
||||||
}
|
}
|
||||||
|
KeyMapping(message) => {
|
||||||
|
let actions = self.collect_actions();
|
||||||
|
|
||||||
|
self.message_handlers
|
||||||
|
.key_mapping_message_handler
|
||||||
|
.process_message(message, &mut queue, (&self.message_handlers.input_preprocessor_message_handler, actions));
|
||||||
|
}
|
||||||
Layout(message) => {
|
Layout(message) => {
|
||||||
let action_input_mapping = &|action_to_find: &MessageDiscriminant| self.message_handlers.input_mapper_message_handler.action_input_mapping(action_to_find);
|
let action_input_mapping = &|action_to_find: &MessageDiscriminant| self.message_handlers.key_mapping_message_handler.action_input_mapping(action_to_find);
|
||||||
|
|
||||||
self.message_handlers.layout_message_handler.process_message(message, &mut queue, action_input_mapping);
|
self.message_handlers.layout_message_handler.process_message(message, &mut queue, action_input_mapping);
|
||||||
}
|
}
|
||||||
@@ -195,7 +195,7 @@ impl Dispatcher {
|
|||||||
let mut list = Vec::new();
|
let mut list = Vec::new();
|
||||||
list.extend(self.message_handlers.dialog_message_handler.actions());
|
list.extend(self.message_handlers.dialog_message_handler.actions());
|
||||||
list.extend(self.message_handlers.input_preprocessor_message_handler.actions());
|
list.extend(self.message_handlers.input_preprocessor_message_handler.actions());
|
||||||
list.extend(self.message_handlers.input_mapper_message_handler.actions());
|
list.extend(self.message_handlers.key_mapping_message_handler.actions());
|
||||||
list.extend(self.message_handlers.debug_message_handler.actions());
|
list.extend(self.message_handlers.debug_message_handler.actions());
|
||||||
if self.message_handlers.portfolio_message_handler.active_document().is_some() {
|
if self.message_handlers.portfolio_message_handler.active_document().is_some() {
|
||||||
list.extend(self.message_handlers.tool_message_handler.actions());
|
list.extend(self.message_handlers.tool_message_handler.actions());
|
||||||
|
|||||||
+31
-1
@@ -1,7 +1,7 @@
|
|||||||
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||||
use crate::messages::layout::utility_types::misc::LayoutTarget;
|
use crate::messages::layout::utility_types::misc::LayoutTarget;
|
||||||
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
|
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
|
||||||
use crate::messages::layout::utility_types::widgets::input_widgets::{NumberInput, TextInput};
|
use crate::messages::layout::utility_types::widgets::input_widgets::{CheckboxInput, NumberInput, TextInput};
|
||||||
use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType, TextLabel};
|
use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType, TextLabel};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
@@ -33,6 +33,35 @@ impl PreferencesDialogMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn properties(&self, preferences: &PreferencesMessageHandler) -> Layout {
|
fn properties(&self, preferences: &PreferencesMessageHandler) -> Layout {
|
||||||
|
let zoom_with_scroll = vec![
|
||||||
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||||
|
value: "Input".into(),
|
||||||
|
min_width: 60,
|
||||||
|
italic: true,
|
||||||
|
..Default::default()
|
||||||
|
})),
|
||||||
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||||
|
value: "Zoom with Scroll".into(),
|
||||||
|
table_align: true,
|
||||||
|
..Default::default()
|
||||||
|
})),
|
||||||
|
WidgetHolder::new(Widget::Separator(Separator {
|
||||||
|
separator_type: SeparatorType::Unrelated,
|
||||||
|
direction: SeparatorDirection::Horizontal,
|
||||||
|
})),
|
||||||
|
WidgetHolder::new(Widget::CheckboxInput(CheckboxInput {
|
||||||
|
checked: preferences.zoom_with_scroll,
|
||||||
|
tooltip: "Use the scroll wheel for zooming instead of vertically panning (not recommended for trackpads)".into(),
|
||||||
|
on_update: WidgetCallback::new(|checkbox_input: &CheckboxInput| {
|
||||||
|
PreferencesMessage::ModifyLayout {
|
||||||
|
zoom_with_scroll: checkbox_input.checked,
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
|
||||||
let imaginate_server_hostname = vec![
|
let imaginate_server_hostname = vec![
|
||||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||||
value: "Imaginate".into(),
|
value: "Imaginate".into(),
|
||||||
@@ -107,6 +136,7 @@ impl PreferencesDialogMessageHandler {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
}))],
|
}))],
|
||||||
},
|
},
|
||||||
|
LayoutGroup::Row { widgets: zoom_with_scroll },
|
||||||
LayoutGroup::Row { widgets: imaginate_server_hostname },
|
LayoutGroup::Row { widgets: imaginate_server_hostname },
|
||||||
LayoutGroup::Row { widgets: imaginate_refresh_frequency },
|
LayoutGroup::Row { widgets: imaginate_refresh_frequency },
|
||||||
LayoutGroup::Row { widgets: button_widgets },
|
LayoutGroup::Row { widgets: button_widgets },
|
||||||
|
|||||||
@@ -266,4 +266,8 @@ pub enum FrontendMessage {
|
|||||||
layout_target: LayoutTarget,
|
layout_target: LayoutTarget,
|
||||||
diff: Vec<WidgetDiff>,
|
diff: Vec<WidgetDiff>,
|
||||||
},
|
},
|
||||||
|
UpdateZoomWithScroll {
|
||||||
|
#[serde(rename = "zoomWithScroll")]
|
||||||
|
zoom_with_scroll: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::consts::{BIG_NUDGE_AMOUNT, NUDGE_AMOUNT};
|
use crate::consts::{BIG_NUDGE_AMOUNT, NUDGE_AMOUNT};
|
||||||
|
use crate::messages::input_mapper::key_mapping::MappingVariant;
|
||||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeyStates};
|
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeyStates};
|
||||||
use crate::messages::input_mapper::utility_types::macros::*;
|
use crate::messages::input_mapper::utility_types::macros::*;
|
||||||
use crate::messages::input_mapper::utility_types::misc::MappingEntry;
|
use crate::messages::input_mapper::utility_types::misc::MappingEntry;
|
||||||
@@ -8,6 +9,15 @@ use crate::messages::prelude::*;
|
|||||||
|
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
|
|
||||||
|
impl From<MappingVariant> for Mapping {
|
||||||
|
fn from(value: MappingVariant) -> Self {
|
||||||
|
match value {
|
||||||
|
MappingVariant::Default => default_mapping(),
|
||||||
|
MappingVariant::ZoomWithScroll => zoom_with_scroll(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn default_mapping() -> Mapping {
|
pub fn default_mapping() -> Mapping {
|
||||||
use InputMapperMessage::*;
|
use InputMapperMessage::*;
|
||||||
use Key::*;
|
use Key::*;
|
||||||
@@ -337,3 +347,40 @@ pub fn default_mapping() -> Mapping {
|
|||||||
pointer_move,
|
pointer_move,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Defaults except that scrolling without modifiers is bound to zooming instead of vertical panning
|
||||||
|
pub fn zoom_with_scroll() -> Mapping {
|
||||||
|
// TODO(multisn8): for other keymaps this patterns might be useful
|
||||||
|
use InputMapperMessage::*;
|
||||||
|
|
||||||
|
let mut mapping = default_mapping();
|
||||||
|
|
||||||
|
let remove = [
|
||||||
|
entry!(WheelScroll; modifiers=[Control], action_dispatch=NavigationMessage::WheelCanvasZoom),
|
||||||
|
entry!(WheelScroll; modifiers=[Shift], action_dispatch=NavigationMessage::WheelCanvasTranslate { use_y_as_x: true }),
|
||||||
|
entry!(WheelScroll; action_dispatch=NavigationMessage::WheelCanvasTranslate { use_y_as_x: false }),
|
||||||
|
];
|
||||||
|
let add = [
|
||||||
|
entry!(WheelScroll; modifiers=[Control], action_dispatch=NavigationMessage::WheelCanvasTranslate { use_y_as_x: true }),
|
||||||
|
entry!(WheelScroll; modifiers=[Shift], action_dispatch=NavigationMessage::WheelCanvasTranslate { use_y_as_x: false }),
|
||||||
|
entry!(WheelScroll; action_dispatch=NavigationMessage::WheelCanvasZoom),
|
||||||
|
];
|
||||||
|
|
||||||
|
apply_mapping_patch(&mut mapping, remove, add);
|
||||||
|
|
||||||
|
mapping
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_mapping_patch<'a, const N: usize, const M: usize, const X: usize, const Y: usize>(
|
||||||
|
mapping: &mut Mapping,
|
||||||
|
remove: impl IntoIterator<Item = &'a [&'a [MappingEntry; N]; M]>,
|
||||||
|
add: impl IntoIterator<Item = &'a [&'a [MappingEntry; X]; Y]>,
|
||||||
|
) {
|
||||||
|
for entry in remove.into_iter().flat_map(|inner| inner.iter()).flat_map(|inner| inner.iter()) {
|
||||||
|
mapping.remove(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
for entry in add.into_iter().flat_map(|inner| inner.iter()).flat_map(|inner| inner.iter()) {
|
||||||
|
mapping.add(entry.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use crate::messages::prelude::*;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[remain::sorted]
|
#[remain::sorted]
|
||||||
#[impl_message(Message, InputMapper)]
|
#[impl_message(Message, KeyMappingMessage, Lookup)]
|
||||||
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
|
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
pub enum InputMapperMessage {
|
pub enum InputMapperMessage {
|
||||||
// Sub-messages
|
// Sub-messages
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ impl MessageHandler<InputMapperMessage, (&InputPreprocessorMessageHandler, Actio
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl InputMapperMessageHandler {
|
impl InputMapperMessageHandler {
|
||||||
|
pub fn set_mapping(&mut self, mapping: Mapping) {
|
||||||
|
self.mapping = mapping;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn hints(&self, actions: ActionList) -> String {
|
pub fn hints(&self, actions: ActionList) -> String {
|
||||||
let mut output = String::new();
|
let mut output = String::new();
|
||||||
let mut actions = actions
|
let mut actions = actions
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[remain::sorted]
|
||||||
|
#[impl_message(Message, KeyMapping)]
|
||||||
|
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
|
pub enum KeyMappingMessage {
|
||||||
|
#[child]
|
||||||
|
Lookup(InputMapperMessage),
|
||||||
|
#[child]
|
||||||
|
ModifyMapping(MappingVariant),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[remain::sorted]
|
||||||
|
#[impl_message(Message, KeyMappingMessage, ModifyMapping)]
|
||||||
|
#[derive(PartialEq, Eq, Clone, Debug, Default, Hash, Serialize, Deserialize)]
|
||||||
|
pub enum MappingVariant {
|
||||||
|
#[remain::unsorted]
|
||||||
|
#[default]
|
||||||
|
Default,
|
||||||
|
|
||||||
|
ZoomWithScroll,
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup;
|
||||||
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct KeyMappingMessageHandler {
|
||||||
|
mapping_handler: InputMapperMessageHandler,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageHandler<KeyMappingMessage, (&InputPreprocessorMessageHandler, ActionList)> for KeyMappingMessageHandler {
|
||||||
|
fn process_message(&mut self, message: KeyMappingMessage, responses: &mut VecDeque<Message>, data: (&InputPreprocessorMessageHandler, ActionList)) {
|
||||||
|
match message {
|
||||||
|
KeyMappingMessage::Lookup(input) => self.mapping_handler.process_message(input, responses, data),
|
||||||
|
KeyMappingMessage::ModifyMapping(new_layout) => self.mapping_handler.set_mapping(new_layout.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
advertise_actions!();
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyMappingMessageHandler {
|
||||||
|
pub fn action_input_mapping(&self, action_to_find: &MessageDiscriminant) -> Vec<KeysGroup> {
|
||||||
|
self.mapping_handler.action_input_mapping(action_to_find)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
mod key_mapping_message;
|
||||||
|
mod key_mapping_message_handler;
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use key_mapping_message::{KeyMappingMessage, KeyMappingMessageDiscriminant, MappingVariant, MappingVariantDiscriminant};
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use key_mapping_message_handler::KeyMappingMessageHandler;
|
||||||
@@ -2,6 +2,7 @@ mod input_mapper_message;
|
|||||||
mod input_mapper_message_handler;
|
mod input_mapper_message_handler;
|
||||||
|
|
||||||
pub mod default_mapping;
|
pub mod default_mapping;
|
||||||
|
pub mod key_mapping;
|
||||||
pub mod utility_types;
|
pub mod utility_types;
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use super::input_keyboard::{all_required_modifiers_pressed, KeysGroup, LayoutKeysGroup};
|
use super::input_keyboard::{all_required_modifiers_pressed, KeysGroup, LayoutKeysGroup};
|
||||||
use crate::messages::input_mapper::default_mapping::default_mapping;
|
use crate::messages::input_mapper::key_mapping::MappingVariant;
|
||||||
use crate::messages::input_mapper::utility_types::input_keyboard::{KeyStates, NUMBER_OF_KEYS};
|
use crate::messages::input_mapper::utility_types::input_keyboard::{KeyStates, NUMBER_OF_KEYS};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
@@ -14,22 +14,46 @@ pub struct Mapping {
|
|||||||
pub pointer_move: KeyMappingEntries,
|
pub pointer_move: KeyMappingEntries,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mapping {
|
impl Default for Mapping {
|
||||||
pub fn match_input_message(&self, message: InputMapperMessage, keyboard_state: &KeyStates, actions: ActionList) -> Option<Message> {
|
fn default() -> Self {
|
||||||
let list = match message {
|
MappingVariant::Default.into()
|
||||||
InputMapperMessage::KeyDown(key) => &self.key_down[key as usize],
|
|
||||||
InputMapperMessage::KeyUp(key) => &self.key_up[key as usize],
|
|
||||||
InputMapperMessage::DoubleClick => &self.double_click,
|
|
||||||
InputMapperMessage::WheelScroll => &self.wheel_scroll,
|
|
||||||
InputMapperMessage::PointerMove => &self.pointer_move,
|
|
||||||
};
|
|
||||||
list.match_mapping(keyboard_state, actions)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Mapping {
|
impl Mapping {
|
||||||
fn default() -> Self {
|
pub fn match_input_message(&self, message: InputMapperMessage, keyboard_state: &KeyStates, actions: ActionList) -> Option<Message> {
|
||||||
default_mapping()
|
let list = self.associated_entries(&message);
|
||||||
|
list.match_mapping(keyboard_state, actions)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(&mut self, target_entry: &MappingEntry) {
|
||||||
|
let list = self.associated_entries_mut(&target_entry.input);
|
||||||
|
list.remove(target_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(&mut self, new_entry: MappingEntry) {
|
||||||
|
let list = self.associated_entries_mut(&new_entry.input);
|
||||||
|
list.push(new_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn associated_entries(&self, message: &InputMapperMessage) -> &KeyMappingEntries {
|
||||||
|
match message {
|
||||||
|
InputMapperMessage::KeyDown(key) => &self.key_down[*key as usize],
|
||||||
|
InputMapperMessage::KeyUp(key) => &self.key_up[*key as usize],
|
||||||
|
InputMapperMessage::DoubleClick => &self.double_click,
|
||||||
|
InputMapperMessage::WheelScroll => &self.wheel_scroll,
|
||||||
|
InputMapperMessage::PointerMove => &self.pointer_move,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn associated_entries_mut(&mut self, message: &InputMapperMessage) -> &mut KeyMappingEntries {
|
||||||
|
match message {
|
||||||
|
InputMapperMessage::KeyDown(key) => &mut self.key_down[*key as usize],
|
||||||
|
InputMapperMessage::KeyUp(key) => &mut self.key_up[*key as usize],
|
||||||
|
InputMapperMessage::DoubleClick => &mut self.double_click,
|
||||||
|
InputMapperMessage::WheelScroll => &mut self.wheel_scroll,
|
||||||
|
InputMapperMessage::PointerMove => &mut self.pointer_move,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +76,11 @@ impl KeyMappingEntries {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn push(&mut self, entry: MappingEntry) {
|
pub fn push(&mut self, entry: MappingEntry) {
|
||||||
self.0.push(entry)
|
self.0.push(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(&mut self, target_entry: &MappingEntry) {
|
||||||
|
self.0.retain(|entry| entry != target_entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
|
|||||||
@@ -26,10 +26,10 @@ pub enum Message {
|
|||||||
#[child]
|
#[child]
|
||||||
Globals(GlobalsMessage),
|
Globals(GlobalsMessage),
|
||||||
#[child]
|
#[child]
|
||||||
InputMapper(InputMapperMessage),
|
|
||||||
#[child]
|
|
||||||
InputPreprocessor(InputPreprocessorMessage),
|
InputPreprocessor(InputPreprocessorMessage),
|
||||||
#[child]
|
#[child]
|
||||||
|
KeyMapping(KeyMappingMessage),
|
||||||
|
#[child]
|
||||||
Layout(LayoutMessage),
|
Layout(LayoutMessage),
|
||||||
#[child]
|
#[child]
|
||||||
Portfolio(PortfolioMessage),
|
Portfolio(PortfolioMessage),
|
||||||
|
|||||||
@@ -10,4 +10,5 @@ pub enum PreferencesMessage {
|
|||||||
|
|
||||||
ImaginateRefreshFrequency { seconds: f64 },
|
ImaginateRefreshFrequency { seconds: f64 },
|
||||||
ImaginateServerHostname { hostname: String },
|
ImaginateServerHostname { hostname: String },
|
||||||
|
ModifyLayout { zoom_with_scroll: bool },
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::messages::input_mapper::key_mapping::MappingVariant;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -6,6 +7,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
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 zoom_with_scroll: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PreferencesMessageHandler {
|
impl Default for PreferencesMessageHandler {
|
||||||
@@ -13,6 +15,7 @@ impl Default for PreferencesMessageHandler {
|
|||||||
Self {
|
Self {
|
||||||
imaginate_server_hostname: "http://localhost:7860/".into(),
|
imaginate_server_hostname: "http://localhost:7860/".into(),
|
||||||
imaginate_refresh_frequency: 1.,
|
imaginate_refresh_frequency: 1.,
|
||||||
|
zoom_with_scroll: matches!(MappingVariant::default(), MappingVariant::ZoomWithScroll),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,6 +56,16 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
|
|||||||
self.imaginate_server_hostname = hostname;
|
self.imaginate_server_hostname = hostname;
|
||||||
responses.push_back(PortfolioMessage::ImaginateCheckServerStatus.into());
|
responses.push_back(PortfolioMessage::ImaginateCheckServerStatus.into());
|
||||||
}
|
}
|
||||||
|
PreferencesMessage::ModifyLayout { zoom_with_scroll } => {
|
||||||
|
self.zoom_with_scroll = zoom_with_scroll;
|
||||||
|
|
||||||
|
let variant = match zoom_with_scroll {
|
||||||
|
false => MappingVariant::Default,
|
||||||
|
true => MappingVariant::ZoomWithScroll,
|
||||||
|
};
|
||||||
|
responses.push_back(KeyMappingMessage::ModifyMapping(variant).into());
|
||||||
|
responses.push_back(FrontendMessage::UpdateZoomWithScroll { zoom_with_scroll }.into());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
responses.push_back(FrontendMessage::TriggerSavePreferences { preferences: self.clone() }.into());
|
responses.push_back(FrontendMessage::TriggerSavePreferences { preferences: self.clone() }.into());
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ pub use crate::messages::dialog::preferences_dialog::{PreferencesDialogMessage,
|
|||||||
pub use crate::messages::dialog::{DialogMessage, DialogMessageDiscriminant, DialogMessageHandler};
|
pub use crate::messages::dialog::{DialogMessage, DialogMessageDiscriminant, DialogMessageHandler};
|
||||||
pub use crate::messages::frontend::{FrontendMessage, FrontendMessageDiscriminant};
|
pub use crate::messages::frontend::{FrontendMessage, FrontendMessageDiscriminant};
|
||||||
pub use crate::messages::globals::{GlobalsMessage, GlobalsMessageDiscriminant, GlobalsMessageHandler};
|
pub use crate::messages::globals::{GlobalsMessage, GlobalsMessageDiscriminant, GlobalsMessageHandler};
|
||||||
|
pub use crate::messages::input_mapper::key_mapping::{KeyMappingMessage, KeyMappingMessageDiscriminant, KeyMappingMessageHandler};
|
||||||
pub use crate::messages::input_mapper::{InputMapperMessage, InputMapperMessageDiscriminant, InputMapperMessageHandler};
|
pub use crate::messages::input_mapper::{InputMapperMessage, InputMapperMessageDiscriminant, InputMapperMessageHandler};
|
||||||
pub use crate::messages::input_preprocessor::{InputPreprocessorMessage, InputPreprocessorMessageDiscriminant, InputPreprocessorMessageHandler};
|
pub use crate::messages::input_preprocessor::{InputPreprocessorMessage, InputPreprocessorMessageDiscriminant, InputPreprocessorMessageHandler};
|
||||||
pub use crate::messages::layout::{LayoutMessage, LayoutMessageDiscriminant, LayoutMessageHandler};
|
pub use crate::messages::layout::{LayoutMessage, LayoutMessageDiscriminant, LayoutMessageHandler};
|
||||||
|
|||||||
@@ -53,6 +53,10 @@ export class UpdateOpenDocumentsList extends JsMessage {
|
|||||||
readonly openDocuments!: FrontendDocumentDetails[];
|
readonly openDocuments!: FrontendDocumentDetails[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class UpdateZoomWithScroll extends JsMessage {
|
||||||
|
readonly zoomWithScroll!: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
// Allows the auto save system to use a string for the id rather than a BigInt.
|
// Allows the auto save system to use a string for the id rather than a BigInt.
|
||||||
// IndexedDb does not allow for BigInts as primary keys.
|
// IndexedDb does not allow for BigInts as primary keys.
|
||||||
// TypeScript does not allow subclasses to change the type of class variables in subclasses.
|
// TypeScript does not allow subclasses to change the type of class variables in subclasses.
|
||||||
@@ -1432,6 +1436,7 @@ export const messageMakers: Record<string, MessageMaker> = {
|
|||||||
UpdateOpenDocumentsList,
|
UpdateOpenDocumentsList,
|
||||||
UpdatePropertyPanelOptionsLayout,
|
UpdatePropertyPanelOptionsLayout,
|
||||||
UpdatePropertyPanelSectionsLayout,
|
UpdatePropertyPanelSectionsLayout,
|
||||||
|
UpdateZoomWithScroll,
|
||||||
UpdateToolOptionsLayout,
|
UpdateToolOptionsLayout,
|
||||||
UpdateToolShelfLayout,
|
UpdateToolShelfLayout,
|
||||||
UpdateWorkingColorsLayout,
|
UpdateWorkingColorsLayout,
|
||||||
|
|||||||
@@ -513,9 +513,20 @@ export default defineComponent({
|
|||||||
scroll(e: WheelEvent) {
|
scroll(e: WheelEvent) {
|
||||||
const scrollX = e.deltaX;
|
const scrollX = e.deltaX;
|
||||||
const scrollY = e.deltaY;
|
const scrollY = e.deltaY;
|
||||||
|
const zoomWithScroll = this.nodeGraph.state.zoomWithScroll;
|
||||||
|
|
||||||
|
let zoom;
|
||||||
|
let horizontalPan;
|
||||||
|
if (zoomWithScroll) {
|
||||||
|
zoom = !(e.ctrlKey || e.shiftKey);
|
||||||
|
horizontalPan = e.ctrlKey;
|
||||||
|
} else {
|
||||||
|
zoom = e.ctrlKey;
|
||||||
|
horizontalPan = !(e.ctrlKey || e.shiftKey);
|
||||||
|
}
|
||||||
|
|
||||||
// Zoom
|
// Zoom
|
||||||
if (e.ctrlKey) {
|
if (zoom) {
|
||||||
let zoomFactor = 1 + Math.abs(scrollY) * WHEEL_RATE;
|
let zoomFactor = 1 + Math.abs(scrollY) * WHEEL_RATE;
|
||||||
if (scrollY > 0) zoomFactor = 1 / zoomFactor;
|
if (scrollY > 0) zoomFactor = 1 / zoomFactor;
|
||||||
|
|
||||||
@@ -541,11 +552,11 @@ export default defineComponent({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
// Pan
|
// Pan
|
||||||
else if (!e.shiftKey) {
|
else if (horizontalPan) {
|
||||||
|
this.transform.x -= scrollY / this.transform.scale;
|
||||||
|
} else {
|
||||||
this.transform.x -= scrollX / this.transform.scale;
|
this.transform.x -= scrollX / this.transform.scale;
|
||||||
this.transform.y -= scrollY / this.transform.scale;
|
this.transform.y -= scrollY / this.transform.scale;
|
||||||
} else {
|
|
||||||
this.transform.x -= scrollY / this.transform.scale;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
keydown(e: KeyboardEvent): void {
|
keydown(e: KeyboardEvent): void {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
UpdateNodeGraph,
|
UpdateNodeGraph,
|
||||||
UpdateNodeTypes,
|
UpdateNodeTypes,
|
||||||
UpdateNodeGraphBarLayout,
|
UpdateNodeGraphBarLayout,
|
||||||
|
UpdateZoomWithScroll,
|
||||||
defaultWidgetLayout,
|
defaultWidgetLayout,
|
||||||
patchWidgetLayout,
|
patchWidgetLayout,
|
||||||
} from "@/wasm-communication/messages";
|
} from "@/wasm-communication/messages";
|
||||||
@@ -19,6 +20,7 @@ export function createNodeGraphState(editor: Editor) {
|
|||||||
links: [] as FrontendNodeLink[],
|
links: [] as FrontendNodeLink[],
|
||||||
nodeTypes: [] as FrontendNodeType[],
|
nodeTypes: [] as FrontendNodeType[],
|
||||||
nodeGraphBarLayout: defaultWidgetLayout(),
|
nodeGraphBarLayout: defaultWidgetLayout(),
|
||||||
|
zoomWithScroll: false as boolean,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set up message subscriptions on creation
|
// Set up message subscriptions on creation
|
||||||
@@ -32,6 +34,9 @@ export function createNodeGraphState(editor: Editor) {
|
|||||||
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphBarLayout, (updateNodeGraphBarLayout) => {
|
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphBarLayout, (updateNodeGraphBarLayout) => {
|
||||||
patchWidgetLayout(state.nodeGraphBarLayout, updateNodeGraphBarLayout);
|
patchWidgetLayout(state.nodeGraphBarLayout, updateNodeGraphBarLayout);
|
||||||
});
|
});
|
||||||
|
editor.subscriptions.subscribeJsMessage(UpdateZoomWithScroll, (updateZoomWithScroll) => {
|
||||||
|
state.zoomWithScroll = updateZoomWithScroll.zoomWithScroll;
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
state: readonly(state) as typeof state,
|
state: readonly(state) as typeof state,
|
||||||
|
|||||||
@@ -53,6 +53,10 @@ export class UpdateOpenDocumentsList extends JsMessage {
|
|||||||
readonly openDocuments!: FrontendDocumentDetails[];
|
readonly openDocuments!: FrontendDocumentDetails[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class UpdateZoomWithScroll extends JsMessage {
|
||||||
|
readonly zoomWithScroll!: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
// Allows the auto save system to use a string for the id rather than a BigInt.
|
// Allows the auto save system to use a string for the id rather than a BigInt.
|
||||||
// IndexedDb does not allow for BigInts as primary keys.
|
// IndexedDb does not allow for BigInts as primary keys.
|
||||||
// TypeScript does not allow subclasses to change the type of class variables in subclasses.
|
// TypeScript does not allow subclasses to change the type of class variables in subclasses.
|
||||||
@@ -1422,6 +1426,7 @@ export const messageMakers: Record<string, MessageMaker> = {
|
|||||||
UpdateOpenDocumentsList,
|
UpdateOpenDocumentsList,
|
||||||
UpdatePropertyPanelOptionsLayout,
|
UpdatePropertyPanelOptionsLayout,
|
||||||
UpdatePropertyPanelSectionsLayout,
|
UpdatePropertyPanelSectionsLayout,
|
||||||
|
UpdateZoomWithScroll,
|
||||||
UpdateToolOptionsLayout,
|
UpdateToolOptionsLayout,
|
||||||
UpdateToolShelfLayout,
|
UpdateToolShelfLayout,
|
||||||
UpdateWorkingColorsLayout,
|
UpdateWorkingColorsLayout,
|
||||||
|
|||||||
Reference in New Issue
Block a user