Add modifier keys to every keyboard and mouse input (#247)
* Add modifier keys to every keyboard and mouse input * Fix bad naming convention in Typescript * Added some tests related to modifier keys
This commit is contained in:
parent
1454be24d3
commit
bb79bbd0e4
|
|
@ -228,32 +228,42 @@ function redirectKeyboardEventToBackend(e: KeyboardEvent): boolean {
|
|||
return true;
|
||||
}
|
||||
|
||||
function makeModifiersBitfield(control: boolean, shift: boolean, alt: boolean): number {
|
||||
// eslint-disable-next-line no-bitwise
|
||||
return Number(control) | (Number(shift) << 1) | (Number(alt) << 2);
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
methods: {
|
||||
async canvasMouseDown(e: MouseEvent) {
|
||||
const { on_mouse_down } = await wasm;
|
||||
on_mouse_down(e.offsetX, e.offsetY, e.buttons);
|
||||
const modifiers = makeModifiersBitfield(e.ctrlKey, e.shiftKey, e.altKey);
|
||||
on_mouse_down(e.offsetX, e.offsetY, e.buttons, modifiers);
|
||||
},
|
||||
async canvasMouseUp(e: MouseEvent) {
|
||||
const { on_mouse_up } = await wasm;
|
||||
on_mouse_up(e.offsetX, e.offsetY, e.buttons);
|
||||
const modifiers = makeModifiersBitfield(e.ctrlKey, e.shiftKey, e.altKey);
|
||||
on_mouse_up(e.offsetX, e.offsetY, e.buttons, modifiers);
|
||||
},
|
||||
async canvasMouseMove(e: MouseEvent) {
|
||||
const { on_mouse_move } = await wasm;
|
||||
on_mouse_move(e.offsetX, e.offsetY);
|
||||
const modifiers = makeModifiersBitfield(e.ctrlKey, e.shiftKey, e.altKey);
|
||||
on_mouse_move(e.offsetX, e.offsetY, modifiers);
|
||||
},
|
||||
async keyDown(e: KeyboardEvent) {
|
||||
if (redirectKeyboardEventToBackend(e)) {
|
||||
e.preventDefault();
|
||||
const { on_key_down } = await wasm;
|
||||
on_key_down(e.key);
|
||||
const modifiers = makeModifiersBitfield(e.ctrlKey, e.shiftKey, e.altKey);
|
||||
on_key_down(e.key, modifiers);
|
||||
}
|
||||
},
|
||||
async keyUp(e: KeyboardEvent) {
|
||||
if (redirectKeyboardEventToBackend(e)) {
|
||||
e.preventDefault();
|
||||
const { on_key_up } = await wasm;
|
||||
on_key_up(e.key);
|
||||
const modifiers = makeModifiersBitfield(e.ctrlKey, e.shiftKey, e.altKey);
|
||||
on_key_up(e.key, modifiers);
|
||||
}
|
||||
},
|
||||
async selectTool(toolName: string) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use crate::shims::Error;
|
||||
use crate::wrappers::{translate_key, translate_tool, Color};
|
||||
use crate::EDITOR_STATE;
|
||||
use editor_core::input::input_preprocessor::ModifierKeys;
|
||||
use editor_core::message_prelude::*;
|
||||
use editor_core::{
|
||||
input::mouse::{MouseState, ViewportPosition},
|
||||
|
|
@ -39,43 +40,48 @@ pub fn new_document() -> Result<(), JsValue> {
|
|||
// TODO: When a mouse button is down that started in the viewport, this should trigger even when the mouse is outside the viewport (or even the browser window if the browser supports it)
|
||||
/// Mouse movement within the screenspace bounds of the viewport
|
||||
#[wasm_bindgen]
|
||||
pub fn on_mouse_move(x: u32, y: u32) -> Result<(), JsValue> {
|
||||
pub fn on_mouse_move(x: u32, y: u32, modifiers: u8) -> Result<(), JsValue> {
|
||||
let mods = ModifierKeys::from_bits(modifiers).expect("invalid modifier keys");
|
||||
// TODO: Convert these screenspace viewport coordinates to canvas coordinates based on the current zoom and pan
|
||||
let ev = InputPreprocessorMessage::MouseMove(ViewportPosition { x, y });
|
||||
let ev = InputPreprocessorMessage::MouseMove(ViewportPosition { x, y }, mods);
|
||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error)
|
||||
}
|
||||
|
||||
/// A mouse button depressed within screenspace the bounds of the viewport
|
||||
#[wasm_bindgen]
|
||||
pub fn on_mouse_down(x: u32, y: u32, mouse_keys: u8) -> Result<(), JsValue> {
|
||||
pub fn on_mouse_down(x: u32, y: u32, mouse_keys: u8, modifiers: u8) -> Result<(), JsValue> {
|
||||
let pos = ViewportPosition { x, y };
|
||||
let ev = InputPreprocessorMessage::MouseDown(MouseState::from_u8_pos(mouse_keys, pos));
|
||||
let mods = ModifierKeys::from_bits(modifiers).expect("invalid modifier keys");
|
||||
let ev = InputPreprocessorMessage::MouseDown(MouseState::from_u8_pos(mouse_keys, pos), mods);
|
||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error)
|
||||
}
|
||||
|
||||
/// A mouse button released
|
||||
#[wasm_bindgen]
|
||||
pub fn on_mouse_up(x: u32, y: u32, mouse_keys: u8) -> Result<(), JsValue> {
|
||||
pub fn on_mouse_up(x: u32, y: u32, mouse_keys: u8, modifiers: u8) -> Result<(), JsValue> {
|
||||
let pos = ViewportPosition { x, y };
|
||||
let ev = InputPreprocessorMessage::MouseUp(MouseState::from_u8_pos(mouse_keys, pos));
|
||||
let mods = ModifierKeys::from_bits(modifiers).expect("invalid modifier keys");
|
||||
let ev = InputPreprocessorMessage::MouseUp(MouseState::from_u8_pos(mouse_keys, pos), mods);
|
||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error)
|
||||
}
|
||||
|
||||
/// A keyboard button depressed within screenspace the bounds of the viewport
|
||||
#[wasm_bindgen]
|
||||
pub fn on_key_down(name: String) -> Result<(), JsValue> {
|
||||
pub fn on_key_down(name: String, modifiers: u8) -> Result<(), JsValue> {
|
||||
let key = translate_key(&name);
|
||||
log::trace!("key down {:?}, name: {}", key, name);
|
||||
let ev = InputPreprocessorMessage::KeyDown(key);
|
||||
let mods = ModifierKeys::from_bits(modifiers).expect("invalid modifier keys");
|
||||
log::trace!("key down {:?}, name: {}, modifiers: {:?}", key, name, mods);
|
||||
let ev = InputPreprocessorMessage::KeyDown(key, mods);
|
||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error)
|
||||
}
|
||||
|
||||
/// A keyboard button released
|
||||
#[wasm_bindgen]
|
||||
pub fn on_key_up(name: String) -> Result<(), JsValue> {
|
||||
pub fn on_key_up(name: String, modifiers: u8) -> Result<(), JsValue> {
|
||||
let key = translate_key(&name);
|
||||
log::trace!("key up {:?}, name: {}", key, name);
|
||||
let ev = InputPreprocessorMessage::KeyUp(key);
|
||||
let mods = ModifierKeys::from_bits(modifiers).expect("invalid modifier keys");
|
||||
log::trace!("key up {:?}, name: {}, modifiers: {:?}", key, name, mods);
|
||||
let ev = InputPreprocessorMessage::KeyUp(key, mods);
|
||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
use std::usize;
|
||||
|
||||
use super::keyboard::{Key, KeyStates};
|
||||
use super::mouse::{MouseKeys, MouseState, ViewportPosition};
|
||||
use crate::message_prelude::*;
|
||||
use bitflags::bitflags;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use document_core::DocumentResponse;
|
||||
|
|
@ -8,11 +11,21 @@ pub use document_core::DocumentResponse;
|
|||
#[impl_message(Message, InputPreprocessor)]
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum InputPreprocessorMessage {
|
||||
MouseDown(MouseState),
|
||||
MouseUp(MouseState),
|
||||
MouseMove(ViewportPosition),
|
||||
KeyUp(Key),
|
||||
KeyDown(Key),
|
||||
MouseDown(MouseState, ModifierKeys),
|
||||
MouseUp(MouseState, ModifierKeys),
|
||||
MouseMove(ViewportPosition, ModifierKeys),
|
||||
KeyUp(Key, ModifierKeys),
|
||||
KeyDown(Key, ModifierKeys),
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Default)]
|
||||
#[repr(transparent)]
|
||||
pub struct ModifierKeys: u8 {
|
||||
const CONTROL = 0b0000_0001;
|
||||
const SHIFT = 0b0000_0010;
|
||||
const ALT = 0b0000_0100;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
|
|
@ -29,17 +42,26 @@ enum KeyPosition {
|
|||
impl MessageHandler<InputPreprocessorMessage, ()> for InputPreprocessor {
|
||||
fn process_action(&mut self, message: InputPreprocessorMessage, _data: (), responses: &mut VecDeque<Message>) {
|
||||
let response = match message {
|
||||
InputPreprocessorMessage::MouseMove(pos) => {
|
||||
InputPreprocessorMessage::MouseMove(pos, modifier_keys) => {
|
||||
self.handle_modifier_keys(modifier_keys, responses);
|
||||
self.mouse.position = pos;
|
||||
InputMapperMessage::PointerMove.into()
|
||||
}
|
||||
InputPreprocessorMessage::MouseDown(state) => self.translate_mouse_event(state, KeyPosition::Pressed),
|
||||
InputPreprocessorMessage::MouseUp(state) => self.translate_mouse_event(state, KeyPosition::Released),
|
||||
InputPreprocessorMessage::KeyDown(key) => {
|
||||
InputPreprocessorMessage::MouseDown(state, modifier_keys) => {
|
||||
self.handle_modifier_keys(modifier_keys, responses);
|
||||
self.translate_mouse_event(state, KeyPosition::Pressed)
|
||||
}
|
||||
InputPreprocessorMessage::MouseUp(state, modifier_keys) => {
|
||||
self.handle_modifier_keys(modifier_keys, responses);
|
||||
self.translate_mouse_event(state, KeyPosition::Released)
|
||||
}
|
||||
InputPreprocessorMessage::KeyDown(key, modifier_keys) => {
|
||||
self.handle_modifier_keys(modifier_keys, responses);
|
||||
self.keyboard.set(key as usize);
|
||||
InputMapperMessage::KeyDown(key).into()
|
||||
}
|
||||
InputPreprocessorMessage::KeyUp(key) => {
|
||||
InputPreprocessorMessage::KeyUp(key, modifier_keys) => {
|
||||
self.handle_modifier_keys(modifier_keys, responses);
|
||||
self.keyboard.unset(key as usize);
|
||||
InputMapperMessage::KeyUp(key).into()
|
||||
}
|
||||
|
|
@ -71,4 +93,89 @@ impl InputPreprocessor {
|
|||
KeyPosition::Released => InputMapperMessage::KeyUp(key).into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_modifier_keys(&mut self, modifier_keys: ModifierKeys, responses: &mut VecDeque<Message>) {
|
||||
self.handle_modifier_key(Key::KeyControl, modifier_keys.contains(ModifierKeys::CONTROL), responses);
|
||||
self.handle_modifier_key(Key::KeyShift, modifier_keys.contains(ModifierKeys::SHIFT), responses);
|
||||
self.handle_modifier_key(Key::KeyAlt, modifier_keys.contains(ModifierKeys::ALT), responses);
|
||||
}
|
||||
|
||||
fn handle_modifier_key(&mut self, key: Key, key_is_down: bool, responses: &mut VecDeque<Message>) {
|
||||
let key_was_down = self.keyboard.get(key as usize);
|
||||
if key_was_down && !key_is_down {
|
||||
self.keyboard.unset(key as usize);
|
||||
responses.push_back(InputMapperMessage::KeyUp(key).into());
|
||||
} else if !key_was_down && key_is_down {
|
||||
self.keyboard.set(key as usize);
|
||||
responses.push_back(InputMapperMessage::KeyDown(key).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn process_action_mouse_move_handle_modifier_keys() {
|
||||
let mut input_preprocessor = InputPreprocessor::default();
|
||||
let message = InputPreprocessorMessage::MouseMove(ViewportPosition { x: 4, y: 809 }, ModifierKeys::ALT);
|
||||
let mut responses = VecDeque::new();
|
||||
|
||||
input_preprocessor.process_action(message, (), &mut responses);
|
||||
|
||||
assert!(input_preprocessor.keyboard.get(Key::KeyAlt as usize));
|
||||
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::KeyAlt).into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_action_mouse_down_handle_modifier_keys() {
|
||||
let mut input_preprocessor = InputPreprocessor::default();
|
||||
let message = InputPreprocessorMessage::MouseDown(MouseState::new(), ModifierKeys::CONTROL);
|
||||
let mut responses = VecDeque::new();
|
||||
|
||||
input_preprocessor.process_action(message, (), &mut responses);
|
||||
|
||||
assert!(input_preprocessor.keyboard.get(Key::KeyControl as usize));
|
||||
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::KeyControl).into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_action_mouse_up_handle_modifier_keys() {
|
||||
let mut input_preprocessor = InputPreprocessor::default();
|
||||
let message = InputPreprocessorMessage::MouseUp(MouseState::new(), ModifierKeys::SHIFT);
|
||||
let mut responses = VecDeque::new();
|
||||
|
||||
input_preprocessor.process_action(message, (), &mut responses);
|
||||
|
||||
assert!(input_preprocessor.keyboard.get(Key::KeyShift as usize));
|
||||
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::KeyShift).into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_action_key_down_handle_modifier_keys() {
|
||||
let mut input_preprocessor = InputPreprocessor::default();
|
||||
input_preprocessor.keyboard.set(Key::KeyControl as usize);
|
||||
let message = InputPreprocessorMessage::KeyDown(Key::KeyA, ModifierKeys::empty());
|
||||
let mut responses = VecDeque::new();
|
||||
|
||||
input_preprocessor.process_action(message, (), &mut responses);
|
||||
|
||||
assert!(!input_preprocessor.keyboard.get(Key::KeyControl as usize));
|
||||
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyUp(Key::KeyControl).into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_action_key_up_handle_modifier_keys() {
|
||||
let mut input_preprocessor = InputPreprocessor::default();
|
||||
let message = InputPreprocessorMessage::KeyUp(Key::KeyS, ModifierKeys::CONTROL | ModifierKeys::SHIFT);
|
||||
let mut responses = VecDeque::new();
|
||||
|
||||
input_preprocessor.process_action(message, (), &mut responses);
|
||||
|
||||
assert!(input_preprocessor.keyboard.get(Key::KeyControl as usize));
|
||||
assert!(input_preprocessor.keyboard.get(Key::KeyShift as usize));
|
||||
assert!(responses.contains(&InputMapperMessage::KeyDown(Key::KeyControl).into()));
|
||||
assert!(responses.contains(&InputMapperMessage::KeyDown(Key::KeyControl).into()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,6 +102,10 @@ impl<const LENGTH: usize> BitVector<LENGTH> {
|
|||
let (offset, bit) = Self::convert_index(bitvector_index);
|
||||
self.0[offset] ^= bit;
|
||||
}
|
||||
pub fn get(&mut self, bitvector_index: usize) -> bool {
|
||||
let (offset, bit) = Self::convert_index(bitvector_index);
|
||||
(self.0[offset] & bit) != 0
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
let mut result = 0;
|
||||
for storage in self.0.iter() {
|
||||
|
|
|
|||
|
|
@ -5,5 +5,5 @@ pub mod mouse;
|
|||
|
||||
pub use {
|
||||
input_mapper::{InputMapper, InputMapperMessage, InputMapperMessageDiscriminant},
|
||||
input_preprocessor::{InputPreprocessor, InputPreprocessorMessage, InputPreprocessorMessageDiscriminant},
|
||||
input_preprocessor::{InputPreprocessor, InputPreprocessorMessage, InputPreprocessorMessageDiscriminant, ModifierKeys},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
input::{
|
||||
mouse::{MouseKeys, MouseState, ViewportPosition},
|
||||
InputPreprocessorMessage,
|
||||
InputPreprocessorMessage, ModifierKeys,
|
||||
},
|
||||
message_prelude::{Message, ToolMessage},
|
||||
tool::ToolType,
|
||||
|
|
@ -51,15 +51,15 @@ impl EditorTestUtils for Editor {
|
|||
}
|
||||
|
||||
fn move_mouse(&mut self, x: u32, y: u32) {
|
||||
self.input(InputPreprocessorMessage::MouseMove(ViewportPosition { x, y }));
|
||||
self.input(InputPreprocessorMessage::MouseMove(ViewportPosition { x, y }, ModifierKeys::default()));
|
||||
}
|
||||
|
||||
fn mousedown(&mut self, state: MouseState) {
|
||||
self.input(InputPreprocessorMessage::MouseDown(state));
|
||||
self.input(InputPreprocessorMessage::MouseDown(state, ModifierKeys::default()));
|
||||
}
|
||||
|
||||
fn mouseup(&mut self, state: MouseState) {
|
||||
self.handle_message(InputPreprocessorMessage::MouseUp(state)).unwrap()
|
||||
self.handle_message(InputPreprocessorMessage::MouseUp(state, ModifierKeys::default())).unwrap()
|
||||
}
|
||||
|
||||
fn lmb_mousedown(&mut self, x: u32, y: u32) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue