From b30488bbb767666ceaf252db81b99df7b215f0f0 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Fri, 1 Sep 2023 14:57:03 -0700 Subject: [PATCH] Add support for handling MMB/RMB double click inputs (#1407) * Add support for handling MMB/RMB double click inputs * Add todo comment * Enforce types --------- Co-authored-by: 0hypercube <0hypercube@gmail.com> --- .../messages/input_mapper/default_mapping.rs | 11 +++-- .../input_mapper/input_mapper_message.rs | 6 ++- .../input_mapper_message_handler.rs | 2 +- .../input_mapper/utility_types/input_mouse.rs | 11 +++++ .../input_mapper/utility_types/macros.rs | 4 +- .../input_mapper/utility_types/misc.rs | 12 ++++-- .../input_preprocessor_message_handler.rs | 11 ++++- frontend/src/io-managers/input.ts | 40 ++++++++++++------- .../src/utility-functions/keyboard-entry.ts | 2 +- frontend/wasm/src/editor_api.rs | 1 + 10 files changed, 70 insertions(+), 30 deletions(-) diff --git a/editor/src/messages/input_mapper/default_mapping.rs b/editor/src/messages/input_mapper/default_mapping.rs index 9a2ff8c7..895f1ee4 100644 --- a/editor/src/messages/input_mapper/default_mapping.rs +++ b/editor/src/messages/input_mapper/default_mapping.rs @@ -1,6 +1,7 @@ use crate::consts::{BIG_NUDGE_AMOUNT, BRUSH_SIZE_CHANGE_KEYBOARD, 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_mouse::MouseButton; 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::{KeyMappingEntries, Mapping}; @@ -64,7 +65,7 @@ pub fn default_mapping() -> Mapping { entry!(KeyDown(Lmb); action_dispatch=SelectToolMessage::DragStart { add_to_selection: Shift, select_deepest: Accel }), entry!(KeyUp(Lmb); action_dispatch=SelectToolMessage::DragStop { remove_from_selection: Shift }), entry!(KeyDown(Enter); action_dispatch=SelectToolMessage::Enter), - entry!(DoubleClick; action_dispatch=SelectToolMessage::EditLayer), + entry!(DoubleClick(MouseButton::Left); action_dispatch=SelectToolMessage::EditLayer), entry!(KeyDown(Rmb); action_dispatch=SelectToolMessage::Abort), entry!(KeyDown(Escape); action_dispatch=SelectToolMessage::Abort), // @@ -127,7 +128,7 @@ pub fn default_mapping() -> Mapping { entry!(KeyDown(Lmb); action_dispatch=GradientToolMessage::PointerDown), entry!(PointerMove; refresh_keys=[Shift], action_dispatch=GradientToolMessage::PointerMove { constrain_axis: Shift }), entry!(KeyUp(Lmb); action_dispatch=GradientToolMessage::PointerUp), - entry!(DoubleClick; action_dispatch=GradientToolMessage::InsertStop), + entry!(DoubleClick(MouseButton::Left); action_dispatch=GradientToolMessage::InsertStop), entry!(KeyDown(Delete); action_dispatch=GradientToolMessage::DeleteStop), entry!(KeyDown(Backspace); action_dispatch=GradientToolMessage::DeleteStop), // @@ -182,7 +183,7 @@ pub fn default_mapping() -> Mapping { entry!(KeyDown(Enter); action_dispatch=PathToolMessage::Enter { add_to_selection: Shift }), - entry!(DoubleClick; action_dispatch=PathToolMessage::InsertPoint), + entry!(DoubleClick(MouseButton::Left); action_dispatch=PathToolMessage::InsertPoint), entry!(KeyDown(ArrowRight); action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: 0. }), entry!(KeyDown(ArrowRight); modifiers=[Shift], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: BIG_NUDGE_AMOUNT, delta_y: 0. }), entry!(KeyDown(ArrowRight); modifiers=[ArrowUp], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }), @@ -376,7 +377,9 @@ pub fn default_mapping() -> Mapping { sort(sublist); } } - sort(&mut double_click); + for sublist in &mut double_click { + sort(sublist) + } sort(&mut wheel_scroll); sort(&mut pointer_move); diff --git a/editor/src/messages/input_mapper/input_mapper_message.rs b/editor/src/messages/input_mapper/input_mapper_message.rs index 32ec8e0e..e951b27d 100644 --- a/editor/src/messages/input_mapper/input_mapper_message.rs +++ b/editor/src/messages/input_mapper/input_mapper_message.rs @@ -1,4 +1,4 @@ -use crate::messages::input_mapper::utility_types::input_keyboard::Key; +use crate::messages::input_mapper::utility_types::{input_keyboard::Key, input_mouse::MouseButton}; use crate::messages::prelude::*; use serde::{Deserialize, Serialize}; @@ -20,9 +20,11 @@ pub enum InputMapperMessage { #[remain::unsorted] #[child] KeyUpNoRepeat(Key), + #[remain::unsorted] + #[child] + DoubleClick(MouseButton), // Messages - DoubleClick, PointerMove, WheelScroll, } diff --git a/editor/src/messages/input_mapper/input_mapper_message_handler.rs b/editor/src/messages/input_mapper/input_mapper_message_handler.rs index 0ebb4ec2..96029f65 100644 --- a/editor/src/messages/input_mapper/input_mapper_message_handler.rs +++ b/editor/src/messages/input_mapper/input_mapper_message_handler.rs @@ -51,7 +51,7 @@ impl InputMapperMessageHandler { .chain(self.mapping.key_down.iter()) .chain(self.mapping.key_up_no_repeat.iter()) .chain(self.mapping.key_down_no_repeat.iter()) - .chain(std::iter::once(&self.mapping.double_click)) + .chain(self.mapping.double_click.iter()) .chain(std::iter::once(&self.mapping.wheel_scroll)) .chain(std::iter::once(&self.mapping.pointer_move)); let all_mapping_entries = all_key_mapping_entries.flat_map(|entry| entry.0.iter()); diff --git a/editor/src/messages/input_mapper/utility_types/input_mouse.rs b/editor/src/messages/input_mapper/utility_types/input_mouse.rs index 53a80ac2..a30b635d 100644 --- a/editor/src/messages/input_mapper/utility_types/input_mouse.rs +++ b/editor/src/messages/input_mapper/utility_types/input_mouse.rs @@ -146,3 +146,14 @@ bitflags! { const MIDDLE = 0b0000_0100; } } + +#[impl_message(Message, InputMapperMessage, DoubleClick)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize, specta::Type, num_enum::TryFromPrimitive)] +#[repr(u8)] +pub enum MouseButton { + Left, + Right, + Middle, +} + +pub const NUMBER_OF_MOUSE_BUTTONS: usize = 3; diff --git a/editor/src/messages/input_mapper/utility_types/macros.rs b/editor/src/messages/input_mapper/utility_types/macros.rs index 3f2c6337..fa40e2de 100644 --- a/editor/src/messages/input_mapper/utility_types/macros.rs +++ b/editor/src/messages/input_mapper/utility_types/macros.rs @@ -77,7 +77,7 @@ macro_rules! mapping { let mut key_down = KeyMappingEntries::key_array(); let mut key_up_no_repeat = KeyMappingEntries::key_array(); let mut key_down_no_repeat = KeyMappingEntries::key_array(); - let mut double_click = KeyMappingEntries::new(); + let mut double_click = KeyMappingEntries::mouse_buttons_arrays(); let mut wheel_scroll = KeyMappingEntries::new(); let mut pointer_move = KeyMappingEntries::new(); @@ -91,7 +91,7 @@ macro_rules! mapping { InputMapperMessage::KeyUp(key) => &mut key_up[key as usize], InputMapperMessage::KeyDownNoRepeat(key) => &mut key_down_no_repeat[key as usize], InputMapperMessage::KeyUpNoRepeat(key) => &mut key_up_no_repeat[key as usize], - InputMapperMessage::DoubleClick => &mut double_click, + InputMapperMessage::DoubleClick(key) => &mut double_click[key as usize], InputMapperMessage::WheelScroll => &mut wheel_scroll, InputMapperMessage::PointerMove => &mut pointer_move, }; diff --git a/editor/src/messages/input_mapper/utility_types/misc.rs b/editor/src/messages/input_mapper/utility_types/misc.rs index 40d057be..ee799b1a 100644 --- a/editor/src/messages/input_mapper/utility_types/misc.rs +++ b/editor/src/messages/input_mapper/utility_types/misc.rs @@ -1,6 +1,7 @@ use super::input_keyboard::{all_required_modifiers_pressed, KeysGroup, LayoutKeysGroup}; 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_mouse::NUMBER_OF_MOUSE_BUTTONS; use crate::messages::prelude::*; use serde::{Deserialize, Serialize}; @@ -11,7 +12,7 @@ pub struct Mapping { pub key_down: [KeyMappingEntries; NUMBER_OF_KEYS], pub key_up_no_repeat: [KeyMappingEntries; NUMBER_OF_KEYS], pub key_down_no_repeat: [KeyMappingEntries; NUMBER_OF_KEYS], - pub double_click: KeyMappingEntries, + pub double_click: [KeyMappingEntries; NUMBER_OF_MOUSE_BUTTONS], pub wheel_scroll: KeyMappingEntries, pub pointer_move: KeyMappingEntries, } @@ -44,7 +45,7 @@ impl Mapping { InputMapperMessage::KeyUp(key) => &self.key_up[*key as usize], InputMapperMessage::KeyDownNoRepeat(key) => &self.key_down_no_repeat[*key as usize], InputMapperMessage::KeyUpNoRepeat(key) => &self.key_up_no_repeat[*key as usize], - InputMapperMessage::DoubleClick => &self.double_click, + InputMapperMessage::DoubleClick(key) => &self.double_click[*key as usize], InputMapperMessage::WheelScroll => &self.wheel_scroll, InputMapperMessage::PointerMove => &self.pointer_move, } @@ -56,7 +57,7 @@ impl Mapping { InputMapperMessage::KeyUp(key) => &mut self.key_up[*key as usize], InputMapperMessage::KeyDownNoRepeat(key) => &mut self.key_down_no_repeat[*key as usize], InputMapperMessage::KeyUpNoRepeat(key) => &mut self.key_up_no_repeat[*key as usize], - InputMapperMessage::DoubleClick => &mut self.double_click, + InputMapperMessage::DoubleClick(key) => &mut self.double_click[*key as usize], InputMapperMessage::WheelScroll => &mut self.wheel_scroll, InputMapperMessage::PointerMove => &mut self.pointer_move, } @@ -97,6 +98,11 @@ impl KeyMappingEntries { const DEFAULT: KeyMappingEntries = KeyMappingEntries::new(); [DEFAULT; NUMBER_OF_KEYS] } + + pub fn mouse_buttons_arrays() -> [Self; NUMBER_OF_MOUSE_BUTTONS] { + const DEFAULT: KeyMappingEntries = KeyMappingEntries::new(); + [DEFAULT; NUMBER_OF_MOUSE_BUTTONS] + } } #[derive(PartialEq, Clone, Debug)] diff --git a/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs b/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs index 600f9577..e6670191 100644 --- a/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs +++ b/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs @@ -1,5 +1,5 @@ use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeyStates, ModifierKeys}; -use crate::messages::input_mapper::utility_types::input_mouse::{MouseKeys, MouseState, ViewportBounds}; +use crate::messages::input_mapper::utility_types::input_mouse::{MouseButton, MouseKeys, MouseState, ViewportBounds}; use crate::messages::portfolio::utility_types::KeyboardPlatformLayout; use crate::messages::prelude::*; @@ -37,7 +37,14 @@ impl MessageHandler for InputP let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds); self.mouse.position = mouse_state.position; - responses.add(InputMapperMessage::DoubleClick); + for key in mouse_state.mouse_keys { + responses.add(InputMapperMessage::DoubleClick(match key { + MouseKeys::LEFT => MouseButton::Left, + MouseKeys::RIGHT => MouseButton::Right, + MouseKeys::MIDDLE => MouseButton::Middle, + _ => unimplemented!(), + })); + } } InputPreprocessorMessage::KeyDown { key, key_repeat, modifier_keys } => { self.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses); diff --git a/frontend/src/io-managers/input.ts b/frontend/src/io-managers/input.ts index 1ebcd95f..bf549ef2 100644 --- a/frontend/src/io-managers/input.ts +++ b/frontend/src/io-managers/input.ts @@ -28,7 +28,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli // Event listeners // eslint-disable-next-line @typescript-eslint/no-explicit-any - const listeners: { target: EventListenerTarget; eventName: EventName; action: (event: any) => void; options?: boolean | AddEventListenerOptions }[] = [ + const listeners: { target: EventListenerTarget; eventName: EventName; action: (event: any) => void; options?: AddEventListenerOptions }[] = [ { target: window, eventName: "resize", action: () => onWindowResize(window.document.body) }, { target: window, eventName: "beforeunload", action: (e: BeforeUnloadEvent) => onBeforeUnload(e) }, { target: window, eventName: "keyup", action: (e: KeyboardEvent) => onKeyUp(e) }, @@ -36,7 +36,8 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli { target: window, eventName: "pointermove", action: (e: PointerEvent) => onPointerMove(e) }, { target: window, eventName: "pointerdown", action: (e: PointerEvent) => onPointerDown(e) }, { target: window, eventName: "pointerup", action: (e: PointerEvent) => onPointerUp(e) }, - { target: window, eventName: "dblclick", action: (e: PointerEvent) => onDoubleClick(e) }, + { target: window, eventName: "mousedown", action: (e: MouseEvent) => onMouseDown(e) }, + { target: window, eventName: "mouseup", action: (e: MouseEvent) => onPotentialDoubleClick(e) }, { target: window, eventName: "wheel", action: (e: WheelEvent) => onWheelScroll(e), options: { passive: false } }, { target: window, eventName: "modifyinputfield", action: (e: CustomEvent) => onModifyInputField(e) }, { target: window, eventName: "focusout", action: () => (canvasFocused = false) }, @@ -147,6 +148,11 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli editor.instance.onMouseMove(e.clientX, e.clientY, e.buttons, modifiers); } + function onMouseDown(e: MouseEvent): void { + // Block middle mouse button auto-scroll mode (the circlar gizmo that appears and allows quick scrolling by moving the cursor above or below it) + if (e.button === 1) e.preventDefault(); + } + function onPointerDown(e: PointerEvent): void { const { target } = e; const isTargetingCanvas = target instanceof Element && target.closest("[data-viewport]"); @@ -168,27 +174,31 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli const modifiers = makeKeyboardModifiersBitfield(e); editor.instance.onMouseDown(e.clientX, e.clientY, e.buttons, modifiers); } - - // Block middle mouse button auto-scroll mode (the circlar widget that appears and allows quick scrolling by moving the cursor above or below it) - if (e.button === 1) e.preventDefault(); } function onPointerUp(e: PointerEvent): void { if (!e.buttons) viewportPointerInteractionOngoing = false; - if (!textToolInteractiveInputElement) { - const modifiers = makeKeyboardModifiersBitfield(e); - editor.instance.onMouseUp(e.clientX, e.clientY, e.buttons, modifiers); - } + if (textToolInteractiveInputElement) return; + + const modifiers = makeKeyboardModifiersBitfield(e); + editor.instance.onMouseUp(e.clientX, e.clientY, e.buttons, modifiers); } - function onDoubleClick(e: PointerEvent): void { - if (!e.buttons) viewportPointerInteractionOngoing = false; + function onPotentialDoubleClick(e: MouseEvent): void { + if (textToolInteractiveInputElement) return; + + // Allow only double-clicks + if (e.detail !== 2) return; + + // `e.buttons` is always 0 in the `mouseup` event, so we have to convert from `e.button` instead + let buttons = 1; + if (e.button === 0) buttons = 1; // LMB + if (e.button === 1) buttons = 4; // MMB + if (e.button === 2) buttons = 2; // RMB - if (!textToolInteractiveInputElement) { - const modifiers = makeKeyboardModifiersBitfield(e); - editor.instance.onDoubleClick(e.clientX, e.clientY, e.buttons, modifiers); - } + const modifiers = makeKeyboardModifiersBitfield(e); + editor.instance.onDoubleClick(e.clientX, e.clientY, buttons, modifiers); } // Mouse events diff --git a/frontend/src/utility-functions/keyboard-entry.ts b/frontend/src/utility-functions/keyboard-entry.ts index a47c763b..e33181cd 100644 --- a/frontend/src/utility-functions/keyboard-entry.ts +++ b/frontend/src/utility-functions/keyboard-entry.ts @@ -1,4 +1,4 @@ -export function makeKeyboardModifiersBitfield(e: WheelEvent | PointerEvent | KeyboardEvent): number { +export function makeKeyboardModifiersBitfield(e: WheelEvent | PointerEvent | MouseEvent | KeyboardEvent): number { return ( // Shift (all platforms) (Number(e.shiftKey) << 0) | diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index 58942ca3..06d3f715 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -421,6 +421,7 @@ impl JsEditorHandle { #[wasm_bindgen(js_name = onDoubleClick)] pub fn on_double_click(&self, x: f64, y: f64, mouse_keys: u8, modifiers: u8) { let editor_mouse_state = EditorMouseState::from_keys_and_editor_position(mouse_keys, (x, y).into()); + let modifier_keys = ModifierKeys::from_bits(modifiers).expect("Invalid modifier keys"); let message = InputPreprocessorMessage::DoubleClick { editor_mouse_state, modifier_keys };