mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-16 14:58:05 +08:00
Restructure project directories (#333)
`/client/web` -> `/frontend` `/client/cli` -> *delete for now* `/client/native` -> *delete for now* `/core/editor` -> `/editor` `/core/document` -> `/graphene` `/core/renderer` -> `/charcoal` `/core/proc-macro` -> `/proc-macros` *(now plural)*
This commit is contained in:
315
editor/src/input/input_mapper.rs
Normal file
315
editor/src/input/input_mapper.rs
Normal file
@@ -0,0 +1,315 @@
|
||||
use super::{
|
||||
keyboard::{Key, KeyStates, NUMBER_OF_KEYS},
|
||||
InputPreprocessor,
|
||||
};
|
||||
use crate::consts::{MINUS_KEY_ZOOM_RATE, PLUS_KEY_ZOOM_RATE};
|
||||
use crate::message_prelude::*;
|
||||
use crate::tool::ToolType;
|
||||
|
||||
use std::fmt::Write;
|
||||
|
||||
const NUDGE_AMOUNT: f64 = 1.;
|
||||
const SHIFT_NUDGE_AMOUNT: f64 = 10.;
|
||||
|
||||
#[impl_message(Message, InputMapper)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash)]
|
||||
pub enum InputMapperMessage {
|
||||
PointerMove,
|
||||
MouseScroll,
|
||||
KeyUp(Key),
|
||||
#[child]
|
||||
KeyDown(Key),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
struct MappingEntry {
|
||||
trigger: InputMapperMessage,
|
||||
modifiers: KeyStates,
|
||||
action: Message,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct KeyMappingEntries(Vec<MappingEntry>);
|
||||
|
||||
impl KeyMappingEntries {
|
||||
fn match_mapping(&self, keys: &KeyStates, actions: ActionList) -> Option<Message> {
|
||||
for entry in self.0.iter() {
|
||||
let all_required_modifiers_pressed = ((*keys & entry.modifiers) ^ entry.modifiers).is_empty();
|
||||
if all_required_modifiers_pressed && actions.iter().flatten().any(|action| entry.action.to_discriminant() == *action) {
|
||||
return Some(entry.action.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
fn push(&mut self, entry: MappingEntry) {
|
||||
self.0.push(entry)
|
||||
}
|
||||
|
||||
const fn new() -> Self {
|
||||
Self(Vec::new())
|
||||
}
|
||||
|
||||
fn key_array() -> [Self; NUMBER_OF_KEYS] {
|
||||
const DEFAULT: KeyMappingEntries = KeyMappingEntries::new();
|
||||
[DEFAULT; NUMBER_OF_KEYS]
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for KeyMappingEntries {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Mapping {
|
||||
key_up: [KeyMappingEntries; NUMBER_OF_KEYS],
|
||||
key_down: [KeyMappingEntries; NUMBER_OF_KEYS],
|
||||
pointer_move: KeyMappingEntries,
|
||||
mouse_scroll: KeyMappingEntries,
|
||||
}
|
||||
|
||||
macro_rules! modifiers {
|
||||
($($m:ident),*) => {{
|
||||
#[allow(unused_mut)]
|
||||
let mut state = KeyStates::new();
|
||||
$(
|
||||
state.set(Key::$m as usize);
|
||||
)*
|
||||
state
|
||||
}};
|
||||
}
|
||||
macro_rules! entry {
|
||||
{action=$action:expr, key_down=$key:ident $(, modifiers=[$($m:ident),* $(,)?])?} => {{
|
||||
entry!{action=$action, message=InputMapperMessage::KeyDown(Key::$key) $(, modifiers=[$($m),*])?}
|
||||
}};
|
||||
{action=$action:expr, key_up=$key:ident $(, modifiers=[$($m:ident),* $(,)?])?} => {{
|
||||
entry!{action=$action, message=InputMapperMessage::KeyUp(Key::$key) $(, modifiers=[$($m),* ])?}
|
||||
}};
|
||||
{action=$action:expr, message=$message:expr $(, modifiers=[$($m:ident),* $(,)?])?} => {{
|
||||
&[MappingEntry {trigger: $message, modifiers: modifiers!($($($m),*)?), action: $action.into()}]
|
||||
}};
|
||||
{action=$action:expr, triggers=[$($m:ident),* $(,)?]} => {{
|
||||
&[
|
||||
MappingEntry {trigger:InputMapperMessage::PointerMove, action: $action.into(), modifiers: modifiers!()},
|
||||
$(
|
||||
MappingEntry {trigger:InputMapperMessage::KeyDown(Key::$m), action: $action.into(), modifiers: modifiers!()},
|
||||
MappingEntry {trigger:InputMapperMessage::KeyUp(Key::$m), action: $action.into(), modifiers: modifiers!()},
|
||||
)*
|
||||
]
|
||||
}};
|
||||
}
|
||||
macro_rules! mapping {
|
||||
//[$(<action=$action:expr; message=$key:expr; $(modifiers=[$($m:ident),* $(,)?];)?>)*] => {{
|
||||
[$($entry:expr),* $(,)?] => {{
|
||||
let mut key_up = KeyMappingEntries::key_array();
|
||||
let mut key_down = KeyMappingEntries::key_array();
|
||||
let mut pointer_move: KeyMappingEntries = Default::default();
|
||||
let mut mouse_scroll: KeyMappingEntries = Default::default();
|
||||
$(
|
||||
for entry in $entry {
|
||||
let arr = match entry.trigger {
|
||||
InputMapperMessage::KeyDown(key) => &mut key_down[key as usize],
|
||||
InputMapperMessage::KeyUp(key) => &mut key_up[key as usize],
|
||||
InputMapperMessage::PointerMove => &mut pointer_move,
|
||||
InputMapperMessage::MouseScroll => &mut mouse_scroll,
|
||||
};
|
||||
arr.push(entry.clone());
|
||||
}
|
||||
)*
|
||||
(key_up, key_down, pointer_move, mouse_scroll)
|
||||
}};
|
||||
}
|
||||
|
||||
impl Default for Mapping {
|
||||
fn default() -> Self {
|
||||
use Key::*;
|
||||
let mappings = mapping![
|
||||
entry! {action=DocumentsMessage::PasteLayers{path: vec![], insert_index: -1}, key_down=KeyV, modifiers=[KeyControl]},
|
||||
entry! {action=MovementMessage::EnableSnapping, key_down=KeyShift},
|
||||
entry! {action=MovementMessage::DisableSnapping, key_up=KeyShift},
|
||||
// Select
|
||||
entry! {action=SelectMessage::MouseMove, message=InputMapperMessage::PointerMove},
|
||||
entry! {action=SelectMessage::DragStart, key_down=Lmb},
|
||||
entry! {action=SelectMessage::DragStop, key_up=Lmb},
|
||||
entry! {action=SelectMessage::Abort, key_down=Rmb},
|
||||
entry! {action=SelectMessage::Abort, key_down=KeyEscape},
|
||||
// Eyedropper
|
||||
entry! {action=EyedropperMessage::LeftMouseDown, key_down=Lmb},
|
||||
entry! {action=EyedropperMessage::RightMouseDown, key_down=Rmb},
|
||||
// Rectangle
|
||||
entry! {action=RectangleMessage::DragStart, key_down=Lmb},
|
||||
entry! {action=RectangleMessage::DragStop, key_up=Lmb},
|
||||
entry! {action=RectangleMessage::Abort, key_down=Rmb},
|
||||
entry! {action=RectangleMessage::Abort, key_down=KeyEscape},
|
||||
entry! {action=RectangleMessage::Resize{center: KeyAlt, lock_ratio: KeyShift}, triggers=[KeyAlt, KeyShift]},
|
||||
// Ellipse
|
||||
entry! {action=EllipseMessage::DragStart, key_down=Lmb},
|
||||
entry! {action=EllipseMessage::DragStop, key_up=Lmb},
|
||||
entry! {action=EllipseMessage::Abort, key_down=Rmb},
|
||||
entry! {action=EllipseMessage::Abort, key_down=KeyEscape},
|
||||
entry! {action=EllipseMessage::Resize{center: KeyAlt, lock_ratio: KeyShift}, triggers=[KeyAlt, KeyShift]},
|
||||
// Shape
|
||||
entry! {action=ShapeMessage::DragStart, key_down=Lmb},
|
||||
entry! {action=ShapeMessage::DragStop, key_up=Lmb},
|
||||
entry! {action=ShapeMessage::Abort, key_down=Rmb},
|
||||
entry! {action=ShapeMessage::Abort, key_down=KeyEscape},
|
||||
entry! {action=ShapeMessage::Resize{center: KeyAlt, lock_ratio: KeyShift}, triggers=[KeyAlt, KeyShift]},
|
||||
// Line
|
||||
entry! {action=LineMessage::DragStart, key_down=Lmb},
|
||||
entry! {action=LineMessage::DragStop, key_up=Lmb},
|
||||
entry! {action=LineMessage::Abort, key_down=Rmb},
|
||||
entry! {action=LineMessage::Abort, key_down=KeyEscape},
|
||||
entry! {action=LineMessage::Redraw{center: KeyAlt, lock_angle: KeyControl, snap_angle: KeyShift}, triggers=[KeyAlt, KeyShift, KeyControl]},
|
||||
// Pen
|
||||
entry! {action=PenMessage::PointerMove, message=InputMapperMessage::PointerMove},
|
||||
entry! {action=PenMessage::DragStart, key_down=Lmb},
|
||||
entry! {action=PenMessage::DragStop, key_up=Lmb},
|
||||
entry! {action=PenMessage::Confirm, key_down=Rmb},
|
||||
entry! {action=PenMessage::Confirm, key_down=KeyEscape},
|
||||
entry! {action=PenMessage::Confirm, key_down=KeyEnter},
|
||||
// Fill
|
||||
entry! {action=FillMessage::MouseDown, key_down=Lmb},
|
||||
// Tool Actions
|
||||
entry! {action=ToolMessage::SelectTool(ToolType::Fill), key_down=KeyF},
|
||||
entry! {action=ToolMessage::SelectTool(ToolType::Rectangle), key_down=KeyM},
|
||||
entry! {action=ToolMessage::SelectTool(ToolType::Ellipse), key_down=KeyE},
|
||||
entry! {action=ToolMessage::SelectTool(ToolType::Select), key_down=KeyV},
|
||||
entry! {action=ToolMessage::SelectTool(ToolType::Line), key_down=KeyL},
|
||||
entry! {action=ToolMessage::SelectTool(ToolType::Pen), key_down=KeyP},
|
||||
entry! {action=ToolMessage::SelectTool(ToolType::Shape), key_down=KeyY},
|
||||
entry! {action=ToolMessage::SelectTool(ToolType::Eyedropper), key_down=KeyI},
|
||||
entry! {action=ToolMessage::ResetColors, key_down=KeyX, modifiers=[KeyShift, KeyControl]},
|
||||
entry! {action=ToolMessage::SwapColors, key_down=KeyX, modifiers=[KeyShift]},
|
||||
// Document Actions
|
||||
entry! {action=DocumentMessage::Undo, key_down=KeyZ, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentMessage::DeselectAllLayers, key_down=KeyA, modifiers=[KeyControl, KeyAlt]},
|
||||
entry! {action=DocumentMessage::SelectAllLayers, key_down=KeyA, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentMessage::DeleteSelectedLayers, key_down=KeyDelete},
|
||||
entry! {action=DocumentMessage::DeleteSelectedLayers, key_down=KeyX},
|
||||
entry! {action=DocumentMessage::DeleteSelectedLayers, key_down=KeyBackspace},
|
||||
entry! {action=DocumentMessage::ExportDocument, key_down=KeyE, modifiers=[KeyControl]},
|
||||
entry! {action=MovementMessage::MouseMove, message=InputMapperMessage::PointerMove},
|
||||
entry! {action=MovementMessage::RotateCanvasBegin{snap:false}, key_down=Mmb, modifiers=[KeyControl]},
|
||||
entry! {action=MovementMessage::RotateCanvasBegin{snap:true}, key_down=Mmb, modifiers=[KeyControl, KeyShift]},
|
||||
entry! {action=MovementMessage::ZoomCanvasBegin, key_down=Mmb, modifiers=[KeyShift]},
|
||||
entry! {action=MovementMessage::ZoomCanvasToFitAll, key_down=Key0, modifiers=[KeyControl]},
|
||||
entry! {action=MovementMessage::TranslateCanvasBegin, key_down=Mmb},
|
||||
entry! {action=MovementMessage::TranslateCanvasEnd, key_up=Mmb},
|
||||
entry! {action=MovementMessage::MultiplyCanvasZoom(PLUS_KEY_ZOOM_RATE), key_down=KeyPlus, modifiers=[KeyControl]},
|
||||
entry! {action=MovementMessage::MultiplyCanvasZoom(PLUS_KEY_ZOOM_RATE), key_down=KeyEquals, modifiers=[KeyControl]},
|
||||
entry! {action=MovementMessage::MultiplyCanvasZoom(MINUS_KEY_ZOOM_RATE), key_down=KeyMinus, modifiers=[KeyControl]},
|
||||
entry! {action=MovementMessage::SetCanvasZoom(1.), key_down=Key1, modifiers=[KeyControl]},
|
||||
entry! {action=MovementMessage::SetCanvasZoom(2.), key_down=Key2, modifiers=[KeyControl]},
|
||||
entry! {action=MovementMessage::WheelCanvasZoom, message=InputMapperMessage::MouseScroll, modifiers=[KeyControl]},
|
||||
entry! {action=MovementMessage::WheelCanvasTranslate{use_y_as_x: true}, message=InputMapperMessage::MouseScroll, modifiers=[KeyShift]},
|
||||
entry! {action=MovementMessage::WheelCanvasTranslate{use_y_as_x: false}, message=InputMapperMessage::MouseScroll},
|
||||
entry! {action=DocumentsMessage::NewDocument, key_down=KeyN, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentsMessage::NextDocument, key_down=KeyTab, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentsMessage::PrevDocument, key_down=KeyTab, modifiers=[KeyControl, KeyShift]},
|
||||
entry! {action=DocumentsMessage::CloseAllDocumentsWithConfirmation, key_down=KeyW, modifiers=[KeyControl, KeyAlt]},
|
||||
entry! {action=DocumentsMessage::CloseActiveDocumentWithConfirmation, key_down=KeyW, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentMessage::DuplicateSelectedLayers, key_down=KeyD, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentsMessage::CopySelectedLayers, key_down=KeyC, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers(-SHIFT_NUDGE_AMOUNT, -SHIFT_NUDGE_AMOUNT), key_down=KeyArrowUp, modifiers=[KeyShift, KeyArrowLeft]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers(SHIFT_NUDGE_AMOUNT, -SHIFT_NUDGE_AMOUNT), key_down=KeyArrowUp, modifiers=[KeyShift, KeyArrowRight]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers(0., -SHIFT_NUDGE_AMOUNT), key_down=KeyArrowUp, modifiers=[KeyShift]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers(-SHIFT_NUDGE_AMOUNT, SHIFT_NUDGE_AMOUNT), key_down=KeyArrowDown, modifiers=[KeyShift, KeyArrowLeft]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers(SHIFT_NUDGE_AMOUNT, SHIFT_NUDGE_AMOUNT), key_down=KeyArrowDown, modifiers=[KeyShift, KeyArrowRight]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers(0., SHIFT_NUDGE_AMOUNT), key_down=KeyArrowDown, modifiers=[KeyShift]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers(-SHIFT_NUDGE_AMOUNT, -SHIFT_NUDGE_AMOUNT), key_down=KeyArrowLeft, modifiers=[KeyShift, KeyArrowUp]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers(-SHIFT_NUDGE_AMOUNT, SHIFT_NUDGE_AMOUNT), key_down=KeyArrowLeft, modifiers=[KeyShift, KeyArrowDown]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers(-SHIFT_NUDGE_AMOUNT, 0.), key_down=KeyArrowLeft, modifiers=[KeyShift]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers(SHIFT_NUDGE_AMOUNT, -SHIFT_NUDGE_AMOUNT), key_down=KeyArrowRight, modifiers=[KeyShift, KeyArrowUp]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers(SHIFT_NUDGE_AMOUNT, SHIFT_NUDGE_AMOUNT), key_down=KeyArrowRight, modifiers=[KeyShift, KeyArrowDown]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers(SHIFT_NUDGE_AMOUNT, 0.), key_down=KeyArrowRight, modifiers=[KeyShift]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers(-NUDGE_AMOUNT, -NUDGE_AMOUNT), key_down=KeyArrowUp, modifiers=[KeyArrowLeft]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers(NUDGE_AMOUNT, -NUDGE_AMOUNT), key_down=KeyArrowUp, modifiers=[KeyArrowRight]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers(0., -NUDGE_AMOUNT), key_down=KeyArrowUp},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers(-NUDGE_AMOUNT, NUDGE_AMOUNT), key_down=KeyArrowDown, modifiers=[KeyArrowLeft]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers(NUDGE_AMOUNT, NUDGE_AMOUNT), key_down=KeyArrowDown, modifiers=[KeyArrowRight]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers(0., NUDGE_AMOUNT), key_down=KeyArrowDown},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers(-NUDGE_AMOUNT, -NUDGE_AMOUNT), key_down=KeyArrowLeft, modifiers=[KeyArrowUp]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers(-NUDGE_AMOUNT, NUDGE_AMOUNT), key_down=KeyArrowLeft, modifiers=[KeyArrowDown]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers(-NUDGE_AMOUNT, 0.), key_down=KeyArrowLeft},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers(NUDGE_AMOUNT, -NUDGE_AMOUNT), key_down=KeyArrowRight, modifiers=[KeyArrowUp]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers(NUDGE_AMOUNT, NUDGE_AMOUNT), key_down=KeyArrowRight, modifiers=[KeyArrowDown]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers(NUDGE_AMOUNT, 0.), key_down=KeyArrowRight},
|
||||
entry! {action=DocumentMessage::ReorderSelectedLayers(i32::MAX), key_down=KeyRightCurlyBracket, modifiers=[KeyControl]}, // TODO: Use KeyRightBracket with ctrl+shift modifiers once input system is fixed
|
||||
entry! {action=DocumentMessage::ReorderSelectedLayers(1), key_down=KeyRightBracket, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentMessage::ReorderSelectedLayers(-1), key_down=KeyLeftBracket, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentMessage::ReorderSelectedLayers(i32::MIN), key_down=KeyLeftCurlyBracket, modifiers=[KeyControl]}, // TODO: Use KeyLeftBracket with ctrl+shift modifiers once input system is fixed
|
||||
// Global Actions
|
||||
entry! {action=GlobalMessage::LogInfo, key_down=Key1},
|
||||
entry! {action=GlobalMessage::LogDebug, key_down=Key2},
|
||||
entry! {action=GlobalMessage::LogTrace, key_down=Key3},
|
||||
];
|
||||
|
||||
let (mut key_up, mut key_down, mut pointer_move, mut mouse_scroll) = mappings;
|
||||
let sort = |list: &mut KeyMappingEntries| list.0.sort_by(|u, v| v.modifiers.ones().cmp(&u.modifiers.ones()));
|
||||
for list in [&mut key_up, &mut key_down] {
|
||||
for sublist in list {
|
||||
sort(sublist);
|
||||
}
|
||||
}
|
||||
sort(&mut pointer_move);
|
||||
sort(&mut mouse_scroll);
|
||||
Self {
|
||||
key_up,
|
||||
key_down,
|
||||
pointer_move,
|
||||
mouse_scroll,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mapping {
|
||||
fn match_message(&self, message: InputMapperMessage, keys: &KeyStates, actions: ActionList) -> Option<Message> {
|
||||
use InputMapperMessage::*;
|
||||
let list = match message {
|
||||
KeyDown(key) => &self.key_down[key as usize],
|
||||
KeyUp(key) => &self.key_up[key as usize],
|
||||
PointerMove => &self.pointer_move,
|
||||
MouseScroll => &self.mouse_scroll,
|
||||
};
|
||||
list.match_mapping(keys, actions)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct InputMapper {
|
||||
mapping: Mapping,
|
||||
}
|
||||
|
||||
impl InputMapper {
|
||||
pub fn hints(&self, actions: ActionList) -> String {
|
||||
let mut output = String::new();
|
||||
let mut actions = actions
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter(|a| !matches!(*a, MessageDiscriminant::Tool(ToolMessageDiscriminant::SelectTool) | MessageDiscriminant::Global(_)));
|
||||
self.mapping
|
||||
.key_down
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, m)| {
|
||||
let ma = m.0.iter().find_map(|m| actions.find_map(|a| (a == m.action.to_discriminant()).then(|| m.action.to_discriminant())));
|
||||
|
||||
ma.map(|a| unsafe { (std::mem::transmute_copy::<usize, Key>(&i), a) })
|
||||
})
|
||||
.for_each(|(k, a)| {
|
||||
let _ = write!(output, "{}: {}, ", k.to_discriminant().local_name(), a.local_name().split('.').last().unwrap());
|
||||
});
|
||||
output.replace("Key", "")
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageHandler<InputMapperMessage, (&InputPreprocessor, ActionList)> for InputMapper {
|
||||
fn process_action(&mut self, message: InputMapperMessage, data: (&InputPreprocessor, ActionList), responses: &mut VecDeque<Message>) {
|
||||
let (input, actions) = data;
|
||||
if let Some(message) = self.mapping.match_message(message, &input.keyboard, actions) {
|
||||
responses.push_back(message);
|
||||
}
|
||||
}
|
||||
advertise_actions!();
|
||||
}
|
||||
198
editor/src/input/input_preprocessor.rs
Normal file
198
editor/src/input/input_preprocessor.rs
Normal file
@@ -0,0 +1,198 @@
|
||||
use std::usize;
|
||||
|
||||
use super::keyboard::{Key, KeyStates};
|
||||
use super::mouse::{MouseKeys, MouseState, ScrollDelta, ViewportPosition};
|
||||
use crate::message_prelude::*;
|
||||
use bitflags::bitflags;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use graphene::DocumentResponse;
|
||||
|
||||
#[impl_message(Message, InputPreprocessor)]
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum InputPreprocessorMessage {
|
||||
MouseDown(MouseState, ModifierKeys),
|
||||
MouseUp(MouseState, ModifierKeys),
|
||||
MouseMove(ViewportPosition, ModifierKeys),
|
||||
MouseScroll(ScrollDelta, ModifierKeys),
|
||||
KeyUp(Key, ModifierKeys),
|
||||
KeyDown(Key, ModifierKeys),
|
||||
ViewportResize(ViewportPosition),
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Default)]
|
||||
#[repr(transparent)]
|
||||
pub struct ModifierKeys: u8 {
|
||||
const CONTROL = 0b0000_0001;
|
||||
const SHIFT = 0b0000_0010;
|
||||
const ALT = 0b0000_0100;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Hash)]
|
||||
pub struct InputPreprocessor {
|
||||
pub keyboard: KeyStates,
|
||||
pub mouse: MouseState,
|
||||
pub viewport_size: ViewportPosition,
|
||||
}
|
||||
|
||||
enum KeyPosition {
|
||||
Pressed,
|
||||
Released,
|
||||
}
|
||||
|
||||
impl MessageHandler<InputPreprocessorMessage, ()> for InputPreprocessor {
|
||||
fn process_action(&mut self, message: InputPreprocessorMessage, _data: (), responses: &mut VecDeque<Message>) {
|
||||
match message {
|
||||
InputPreprocessorMessage::MouseMove(pos, modifier_keys) => {
|
||||
self.handle_modifier_keys(modifier_keys, responses);
|
||||
self.mouse.position = pos;
|
||||
responses.push_back(InputMapperMessage::PointerMove.into());
|
||||
}
|
||||
InputPreprocessorMessage::MouseScroll(delta, modifier_keys) => {
|
||||
self.handle_modifier_keys(modifier_keys, responses);
|
||||
self.mouse.scroll_delta = delta;
|
||||
responses.push_back(InputMapperMessage::MouseScroll.into());
|
||||
}
|
||||
InputPreprocessorMessage::MouseDown(state, modifier_keys) => {
|
||||
self.handle_modifier_keys(modifier_keys, responses);
|
||||
responses.push_back(self.translate_mouse_event(state, KeyPosition::Pressed));
|
||||
}
|
||||
InputPreprocessorMessage::MouseUp(state, modifier_keys) => {
|
||||
self.handle_modifier_keys(modifier_keys, responses);
|
||||
responses.push_back(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);
|
||||
responses.push_back(InputMapperMessage::KeyDown(key).into());
|
||||
}
|
||||
InputPreprocessorMessage::KeyUp(key, modifier_keys) => {
|
||||
self.handle_modifier_keys(modifier_keys, responses);
|
||||
self.keyboard.unset(key as usize);
|
||||
responses.push_back(InputMapperMessage::KeyUp(key).into());
|
||||
}
|
||||
InputPreprocessorMessage::ViewportResize(size) => {
|
||||
responses.push_back(
|
||||
graphene::Operation::TransformLayer {
|
||||
path: vec![],
|
||||
transform: glam::DAffine2::from_translation((size.as_f64() - self.viewport_size.as_f64()) / 2.).to_cols_array(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
self.viewport_size = size;
|
||||
}
|
||||
};
|
||||
}
|
||||
// clean user input and if possible reconstruct it
|
||||
// store the changes in the keyboard if it is a key event
|
||||
// transform canvas coordinates to document coordinates
|
||||
advertise_actions!();
|
||||
}
|
||||
|
||||
impl InputPreprocessor {
|
||||
fn translate_mouse_event(&mut self, new_state: MouseState, position: KeyPosition) -> Message {
|
||||
// Calculate the difference between the two key states (binary xor)
|
||||
let diff = self.mouse.mouse_keys ^ new_state.mouse_keys;
|
||||
self.mouse = new_state;
|
||||
let key = match diff {
|
||||
MouseKeys::LEFT => Key::Lmb,
|
||||
MouseKeys::RIGHT => Key::Rmb,
|
||||
MouseKeys::MIDDLE => Key::Mmb,
|
||||
_ => {
|
||||
log::warn!("The number of buttons modified at the same time was not equal to 1. Modification: {:#010b}", diff);
|
||||
Key::UnknownKey
|
||||
}
|
||||
};
|
||||
match position {
|
||||
KeyPosition::Pressed => InputMapperMessage::KeyDown(key).into(),
|
||||
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((4, 809).into(), 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()));
|
||||
}
|
||||
}
|
||||
189
editor/src/input/keyboard.rs
Normal file
189
editor/src/input/keyboard.rs
Normal file
@@ -0,0 +1,189 @@
|
||||
use crate::message_prelude::*;
|
||||
|
||||
pub const NUMBER_OF_KEYS: usize = Key::NumKeys as usize;
|
||||
// Edit this to specify the storage type used
|
||||
// TODO: Increase size of type
|
||||
pub type StorageType = u128;
|
||||
|
||||
// base 2 logarithm of the storage type used to represents how many bits you need to fully address every bit in that storage type
|
||||
const STORAGE_SIZE: u32 = (std::mem::size_of::<StorageType>() * 8).trailing_zeros();
|
||||
const STORAGE_SIZE_BITS: usize = 1 << STORAGE_SIZE;
|
||||
const KEY_MASK_STORAGE_LENGTH: usize = (NUMBER_OF_KEYS + STORAGE_SIZE_BITS - 1) >> STORAGE_SIZE;
|
||||
pub type KeyStates = BitVector<KEY_MASK_STORAGE_LENGTH>;
|
||||
|
||||
#[impl_message(Message, InputMapperMessage, KeyDown)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Key {
|
||||
UnknownKey,
|
||||
// MouseKeys
|
||||
Lmb,
|
||||
Rmb,
|
||||
Mmb,
|
||||
|
||||
// Keyboard keys
|
||||
KeyA,
|
||||
KeyB,
|
||||
KeyC,
|
||||
KeyD,
|
||||
KeyE,
|
||||
KeyF,
|
||||
KeyG,
|
||||
KeyH,
|
||||
KeyI,
|
||||
KeyJ,
|
||||
KeyK,
|
||||
KeyL,
|
||||
KeyM,
|
||||
KeyN,
|
||||
KeyO,
|
||||
KeyP,
|
||||
KeyQ,
|
||||
KeyR,
|
||||
KeyS,
|
||||
KeyT,
|
||||
KeyU,
|
||||
KeyV,
|
||||
KeyW,
|
||||
KeyX,
|
||||
KeyY,
|
||||
KeyZ,
|
||||
Key0,
|
||||
Key1,
|
||||
Key2,
|
||||
Key3,
|
||||
Key4,
|
||||
Key5,
|
||||
Key6,
|
||||
Key7,
|
||||
Key8,
|
||||
Key9,
|
||||
KeyEnter,
|
||||
KeyEquals,
|
||||
KeyMinus,
|
||||
KeyPlus,
|
||||
KeyShift,
|
||||
KeyControl,
|
||||
KeyDelete,
|
||||
KeyBackspace,
|
||||
KeyAlt,
|
||||
KeyEscape,
|
||||
KeyTab,
|
||||
KeyArrowUp,
|
||||
KeyArrowDown,
|
||||
KeyArrowLeft,
|
||||
KeyArrowRight,
|
||||
KeyLeftBracket,
|
||||
KeyRightBracket,
|
||||
KeyLeftCurlyBracket,
|
||||
KeyRightCurlyBracket,
|
||||
|
||||
// This has to be the last element in the enum.
|
||||
NumKeys,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct BitVector<const LENGTH: usize>([StorageType; LENGTH]);
|
||||
|
||||
use std::{
|
||||
fmt::{Display, Formatter},
|
||||
ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign},
|
||||
usize,
|
||||
};
|
||||
|
||||
impl<const LENGTH: usize> BitVector<LENGTH> {
|
||||
#[inline]
|
||||
fn convert_index(bitvector_index: usize) -> (usize, StorageType) {
|
||||
let bit = 1 << (bitvector_index & (STORAGE_SIZE_BITS as StorageType - 1) as usize);
|
||||
let offset = bitvector_index >> STORAGE_SIZE;
|
||||
(offset, bit)
|
||||
}
|
||||
pub const fn new() -> Self {
|
||||
Self([0; LENGTH])
|
||||
}
|
||||
pub fn set(&mut self, bitvector_index: usize) {
|
||||
let (offset, bit) = Self::convert_index(bitvector_index);
|
||||
self.0[offset] |= bit;
|
||||
}
|
||||
pub fn unset(&mut self, bitvector_index: usize) {
|
||||
let (offset, bit) = Self::convert_index(bitvector_index);
|
||||
self.0[offset] &= !bit;
|
||||
}
|
||||
pub fn toggle(&mut self, bitvector_index: usize) {
|
||||
let (offset, bit) = Self::convert_index(bitvector_index);
|
||||
self.0[offset] ^= bit;
|
||||
}
|
||||
pub fn get(&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() {
|
||||
result |= storage;
|
||||
}
|
||||
result == 0
|
||||
}
|
||||
pub fn ones(&self) -> u32 {
|
||||
let mut result = 0;
|
||||
for storage in self.0.iter() {
|
||||
result += storage.count_ones();
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl<const LENGTH: usize> Default for BitVector<LENGTH> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const LENGTH: usize> Display for BitVector<LENGTH> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
for storage in self.0.iter().rev() {
|
||||
write!(f, "{:0width$b}", storage, width = STORAGE_SIZE_BITS)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! bit_ops {
|
||||
($(($op:ident, $func:ident)),* $(,)?) => {
|
||||
$(
|
||||
impl<const LENGTH: usize> $op for BitVector<LENGTH> {
|
||||
type Output = Self;
|
||||
fn $func(self, right: Self) -> Self::Output {
|
||||
let mut result = Self::new();
|
||||
for ((left, right), new) in self.0.iter().zip(right.0.iter()).zip(result.0.iter_mut()) {
|
||||
*new = $op::$func(left, right);
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
impl<const LENGTH: usize> $op for &BitVector<LENGTH> {
|
||||
type Output = BitVector<LENGTH>;
|
||||
fn $func(self, right: Self) -> Self::Output {
|
||||
let mut result = BitVector::<LENGTH>::new();
|
||||
for ((left, right), new) in self.0.iter().zip(right.0.iter()).zip(result.0.iter_mut()) {
|
||||
*new = $op::$func(left, right);
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
macro_rules! bit_ops_assign {
|
||||
($(($op:ident, $func:ident)),* $(,)?) => {
|
||||
$(impl<const LENGTH: usize> $op for BitVector<LENGTH> {
|
||||
fn $func(&mut self, right: Self) {
|
||||
for (left, right) in self.0.iter_mut().zip(right.0.iter()) {
|
||||
$op::$func(left, right);
|
||||
}
|
||||
}
|
||||
})*
|
||||
};
|
||||
}
|
||||
|
||||
bit_ops!((BitAnd, bitand), (BitOr, bitor), (BitXor, bitxor));
|
||||
bit_ops_assign!((BitAndAssign, bitand_assign), (BitOrAssign, bitor_assign), (BitXorAssign, bitxor_assign));
|
||||
9
editor/src/input/mod.rs
Normal file
9
editor/src/input/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
pub mod input_mapper;
|
||||
pub mod input_preprocessor;
|
||||
pub mod keyboard;
|
||||
pub mod mouse;
|
||||
|
||||
pub use {
|
||||
input_mapper::{InputMapper, InputMapperMessage, InputMapperMessageDiscriminant},
|
||||
input_preprocessor::{InputPreprocessor, InputPreprocessorMessage, InputPreprocessorMessageDiscriminant, ModifierKeys},
|
||||
};
|
||||
62
editor/src/input/mouse.rs
Normal file
62
editor/src/input/mouse.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use bitflags::bitflags;
|
||||
use glam::DVec2;
|
||||
|
||||
// origin is top left
|
||||
pub type ViewportPosition = glam::UVec2;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash)]
|
||||
pub struct ScrollDelta {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub z: i32,
|
||||
}
|
||||
impl ScrollDelta {
|
||||
pub fn new(x: i32, y: i32, z: i32) -> ScrollDelta {
|
||||
ScrollDelta { x, y, z }
|
||||
}
|
||||
pub fn as_dvec2(&self) -> DVec2 {
|
||||
DVec2::new(self.x as f64, self.y as f64)
|
||||
}
|
||||
pub fn scroll_delta(&self) -> f64 {
|
||||
let (dx, dy) = (self.x, self.y);
|
||||
dy.signum() as f64 * ((dy * dy + i32::min(dy.abs(), dx.abs()).pow(2)) as f64).sqrt()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash)]
|
||||
pub struct MouseState {
|
||||
pub position: ViewportPosition,
|
||||
pub mouse_keys: MouseKeys,
|
||||
pub scroll_delta: ScrollDelta,
|
||||
}
|
||||
|
||||
impl MouseState {
|
||||
pub fn new() -> MouseState {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn from_pos(x: u32, y: u32) -> MouseState {
|
||||
MouseState {
|
||||
position: (x, y).into(),
|
||||
mouse_keys: MouseKeys::default(),
|
||||
scroll_delta: ScrollDelta::default(),
|
||||
}
|
||||
}
|
||||
pub fn from_u8_pos(keys: u8, position: ViewportPosition) -> Self {
|
||||
let mouse_keys = MouseKeys::from_bits(keys).expect("invalid modifier keys");
|
||||
Self {
|
||||
position,
|
||||
mouse_keys,
|
||||
scroll_delta: ScrollDelta::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
bitflags! {
|
||||
#[derive(Default)]
|
||||
#[repr(transparent)]
|
||||
pub struct MouseKeys: u8 {
|
||||
const LEFT = 0b0000_0001;
|
||||
const RIGHT = 0b0000_0010;
|
||||
const MIDDLE = 0b0000_0100;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user