widgetry/
event.rs

1use instant::Instant;
2use winit::event::{
3    ElementState, KeyboardInput, MouseButton, MouseScrollDelta, VirtualKeyCode, WindowEvent,
4};
5
6use geom::Duration;
7
8use crate::{EventCtx, Line, ScreenDims, ScreenPt, TextSpan};
9
10// Mouse-up events longer than this will be considered single clicks
11// Ideally the delay would be a little more tolerant - e.g. 500ms, but because we don't actually
12// have a way to indicate that a single click was handled (and thus *shouldn't* be counted as part of a double click)
13// it's too easy to have false positives.
14const MAX_DOUBLE_CLICK_DURATION: instant::Duration = instant::Duration::from_millis(300);
15
16#[derive(Debug, Clone, Copy, PartialEq)]
17pub enum Event {
18    // Used to initialize the application and also to recalculate menu state when some other event
19    // is used.
20    NoOp,
21    LeftMouseButtonDown,
22    /// Note: When double clicking, there will be two `LeftMouseButtonUp` events in short
23    /// succession - first a `LeftMouseButtonUp { is_double_click: false }`, followed by
24    /// a `LeftMouseButtonUp { is_double_click: true }`.
25    ///
26    /// This was done for ease of implementation - it allows a target to ignore single clicks and
27    /// handle double clicks (or vice versa), but it precludes an obvious way to have a target
28    /// handle single clicks one way while handling double clicks a different way.
29    ///
30    /// e.g. a typical file browser highlights a file with a single click and opens the file with a
31    /// double click, the way we've implemented double clicks here wouldn't work well for that
32    /// case.
33    LeftMouseButtonUp {
34        is_double_click: bool,
35    },
36    RightMouseButtonDown,
37    RightMouseButtonUp,
38    // TODO KeyDown and KeyUp might be nicer, but piston (and probably X.org) hands over repeated
39    // events while a key is held down.
40    KeyPress(Key),
41    KeyRelease(Key),
42    // Some real amount of time has passed since the last update
43    Update(Duration),
44    MouseMovedTo(ScreenPt),
45    WindowLostCursor,
46    WindowGainedCursor,
47    MouseWheelScroll(f64, f64),
48    WindowResized(ScreenDims),
49}
50
51impl Event {
52    pub fn from_winit_event(
53        ev: WindowEvent,
54        scale_factor: f64,
55        previous_click: Instant,
56    ) -> Option<Event> {
57        match ev {
58            WindowEvent::MouseInput { state, button, .. } => match (button, state) {
59                (MouseButton::Left, ElementState::Pressed) => Some(Event::LeftMouseButtonDown),
60                (MouseButton::Left, ElementState::Released) => {
61                    let is_double_click = previous_click.elapsed().le(&MAX_DOUBLE_CLICK_DURATION);
62                    Some(Event::LeftMouseButtonUp { is_double_click })
63                }
64                (MouseButton::Right, ElementState::Pressed) => Some(Event::RightMouseButtonDown),
65                (MouseButton::Right, ElementState::Released) => Some(Event::RightMouseButtonUp),
66                _ => None,
67            },
68            WindowEvent::KeyboardInput { input, .. } => {
69                if let Some(key) = Key::from_winit_key(input) {
70                    if input.state == ElementState::Pressed {
71                        Some(Event::KeyPress(key))
72                    } else {
73                        Some(Event::KeyRelease(key))
74                    }
75                } else {
76                    None
77                }
78            }
79            WindowEvent::CursorMoved { position, .. } => Some(Event::MouseMovedTo(
80                position.to_logical(scale_factor).into(),
81            )),
82            WindowEvent::MouseWheel { delta, .. } => match delta {
83                // "In the beginning" a spinnable mouse wheel was the only input hardware for
84                // scrolling. Each "increment" of the mouse wheel indicated that the application
85                // should scroll one line of text.
86                //
87                // Since the advent of touchpads and tablets, much finer grained scrolling is used,
88                // and consequently some input systems started expressing scroll distances from such
89                // input devices in "pixels" rather than "lines".
90                //
91                // However some backends (e.g. x11) will express all scrolling as `LineDelta` — on
92                // those systems, touchpad drags will simply be scaled to some presumed equivalent
93                // number of lines.
94                //
95                // Widgetry expresses all scrolling in terms of MouseWheelScroll, which uses
96                // "Lines".
97                //
98                // Anymore "a line" usually doesn't correspond to "a literal line of text" in the
99                // application, and must usually just be considered an abstract unit of
100                // measurement, because of things like configurable scroll sensitivity, variable
101                // text size, and the aforementioned advent of touchpads.
102                //
103                // With "Reverse" scrolling, positive values indicate upward or rightward scrolling.
104                // With "Natural" scrolling, it's the opposite.
105                MouseScrollDelta::LineDelta(dx, dy) => {
106                    if dx == 0.0 && dy == 0.0 {
107                        None
108                    } else {
109                        Some(Event::MouseWheelScroll(f64::from(dx), f64::from(dy)))
110                    }
111                }
112                MouseScrollDelta::PixelDelta(pos) => {
113                    // Widgetry expresses all scroll activity in units of "lines", so convert from
114                    // a PixelDelta to a LineDelta.
115
116                    // This scale factor is just a guess - but feels about right.
117                    let scale_factor = 0.01;
118
119                    Some(Event::MouseWheelScroll(
120                        scale_factor * pos.x,
121                        scale_factor * pos.y,
122                    ))
123                }
124            },
125            WindowEvent::Resized(size) => {
126                Some(Event::WindowResized(size.to_logical(scale_factor).into()))
127            }
128            WindowEvent::Focused(gained) => Some(if gained {
129                Event::WindowGainedCursor
130            } else {
131                Event::WindowLostCursor
132            }),
133            _ => None,
134        }
135    }
136}
137
138#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
139pub enum Key {
140    // Case is unspecified.
141    // TODO Would be cool to represent A and UpperA, but then release semantics get weird... hold
142    // shift and A, release shift -- does that trigger a Release(UpperA) and a Press(A)?
143    A,
144    B,
145    C,
146    D,
147    E,
148    F,
149    G,
150    H,
151    I,
152    J,
153    K,
154    L,
155    M,
156    N,
157    O,
158    P,
159    Q,
160    R,
161    S,
162    T,
163    U,
164    V,
165    W,
166    X,
167    Y,
168    Z,
169    // Numbers (not the numpad)
170    Num1,
171    Num2,
172    Num3,
173    Num4,
174    Num5,
175    Num6,
176    Num7,
177    Num8,
178    Num9,
179    Num0,
180    // symbols
181    // TODO shift+number keys
182    LeftBracket,
183    RightBracket,
184    Space,
185    Slash,
186    Dot,
187    Comma,
188    Semicolon,
189    Colon,
190    Equals,
191    SingleQuote,
192    Minus,
193    // Stuff without a straightforward single-character display
194    Escape,
195    Enter,
196    Tab,
197    Backspace,
198    LeftShift,
199    LeftControl,
200    LeftAlt,
201    RightAlt,
202    LeftArrow,
203    RightArrow,
204    UpArrow,
205    DownArrow,
206    F1,
207    F2,
208    F3,
209    F4,
210    F5,
211    F6,
212    F7,
213    F8,
214    F9,
215    F10,
216    F11,
217    F12,
218}
219
220impl Key {
221    pub const NUM_KEYS: [Key; 9] = [
222        Key::Num1,
223        Key::Num2,
224        Key::Num3,
225        Key::Num4,
226        Key::Num5,
227        Key::Num6,
228        Key::Num7,
229        Key::Num8,
230        Key::Num9,
231    ];
232
233    pub fn to_char(self, shift_pressed: bool) -> Option<char> {
234        match self {
235            Key::A => Some(if shift_pressed { 'A' } else { 'a' }),
236            Key::B => Some(if shift_pressed { 'B' } else { 'b' }),
237            Key::C => Some(if shift_pressed { 'C' } else { 'c' }),
238            Key::D => Some(if shift_pressed { 'D' } else { 'd' }),
239            Key::E => Some(if shift_pressed { 'E' } else { 'e' }),
240            Key::F => Some(if shift_pressed { 'F' } else { 'f' }),
241            Key::G => Some(if shift_pressed { 'G' } else { 'g' }),
242            Key::H => Some(if shift_pressed { 'H' } else { 'h' }),
243            Key::I => Some(if shift_pressed { 'I' } else { 'i' }),
244            Key::J => Some(if shift_pressed { 'J' } else { 'j' }),
245            Key::K => Some(if shift_pressed { 'K' } else { 'k' }),
246            Key::L => Some(if shift_pressed { 'L' } else { 'l' }),
247            Key::M => Some(if shift_pressed { 'M' } else { 'm' }),
248            Key::N => Some(if shift_pressed { 'N' } else { 'n' }),
249            Key::O => Some(if shift_pressed { 'O' } else { 'o' }),
250            Key::P => Some(if shift_pressed { 'P' } else { 'p' }),
251            Key::Q => Some(if shift_pressed { 'Q' } else { 'q' }),
252            Key::R => Some(if shift_pressed { 'R' } else { 'r' }),
253            Key::S => Some(if shift_pressed { 'S' } else { 's' }),
254            Key::T => Some(if shift_pressed { 'T' } else { 't' }),
255            Key::U => Some(if shift_pressed { 'U' } else { 'u' }),
256            Key::V => Some(if shift_pressed { 'V' } else { 'v' }),
257            Key::W => Some(if shift_pressed { 'W' } else { 'w' }),
258            Key::X => Some(if shift_pressed { 'X' } else { 'x' }),
259            Key::Y => Some(if shift_pressed { 'Y' } else { 'y' }),
260            Key::Z => Some(if shift_pressed { 'Z' } else { 'z' }),
261            Key::Num1 => Some(if shift_pressed { '!' } else { '1' }),
262            Key::Num2 => Some(if shift_pressed { '@' } else { '2' }),
263            Key::Num3 => Some(if shift_pressed { '#' } else { '3' }),
264            Key::Num4 => Some(if shift_pressed { '$' } else { '4' }),
265            Key::Num5 => Some(if shift_pressed { '%' } else { '5' }),
266            Key::Num6 => Some(if shift_pressed { '^' } else { '6' }),
267            Key::Num7 => Some(if shift_pressed { '&' } else { '7' }),
268            Key::Num8 => Some(if shift_pressed { '*' } else { '8' }),
269            Key::Num9 => Some(if shift_pressed { '(' } else { '9' }),
270            Key::Num0 => Some(if shift_pressed { ')' } else { '0' }),
271            Key::LeftBracket => Some(if shift_pressed { '{' } else { '[' }),
272            Key::RightBracket => Some(if shift_pressed { '}' } else { ']' }),
273            Key::Space => Some(' '),
274            Key::Slash => Some(if shift_pressed { '?' } else { '/' }),
275            Key::Dot => Some(if shift_pressed { '>' } else { '.' }),
276            Key::Comma => Some(if shift_pressed { '<' } else { ',' }),
277            Key::Semicolon => Some(';'),
278            Key::Colon => Some(':'),
279            Key::Equals => Some(if shift_pressed { '+' } else { '=' }),
280            Key::SingleQuote => Some(if shift_pressed { '"' } else { '\'' }),
281            Key::Minus => Some(if shift_pressed { '_' } else { '-' }),
282            Key::Escape
283            | Key::Enter
284            | Key::Tab
285            | Key::Backspace
286            | Key::LeftShift
287            | Key::LeftControl
288            | Key::LeftAlt
289            | Key::RightAlt
290            | Key::LeftArrow
291            | Key::RightArrow
292            | Key::UpArrow
293            | Key::DownArrow
294            | Key::F1
295            | Key::F2
296            | Key::F3
297            | Key::F4
298            | Key::F5
299            | Key::F6
300            | Key::F7
301            | Key::F8
302            | Key::F9
303            | Key::F10
304            | Key::F11
305            | Key::F12 => None,
306        }
307    }
308
309    pub fn describe(self) -> String {
310        match self {
311            Key::Escape => "Escape".to_string(),
312            Key::Enter => "Enter".to_string(),
313            Key::Tab => "Tab".to_string(),
314            Key::Backspace => "Backspace".to_string(),
315            Key::LeftShift => "Shift".to_string(),
316            Key::LeftControl => "left Control".to_string(),
317            Key::LeftAlt => "left Alt".to_string(),
318            Key::RightAlt => "right Alt".to_string(),
319            Key::LeftArrow => "← arrow".to_string(),
320            Key::RightArrow => "→ arrow".to_string(),
321            Key::UpArrow => "↑".to_string(),
322            Key::DownArrow => "↓".to_string(),
323            Key::F1 => "F1".to_string(),
324            Key::F2 => "F2".to_string(),
325            Key::F3 => "F3".to_string(),
326            Key::F4 => "F4".to_string(),
327            Key::F5 => "F5".to_string(),
328            Key::F6 => "F6".to_string(),
329            Key::F7 => "F7".to_string(),
330            Key::F8 => "F8".to_string(),
331            Key::F9 => "F9".to_string(),
332            Key::F10 => "F10".to_string(),
333            Key::F11 => "F11".to_string(),
334            Key::F12 => "F12".to_string(),
335            // These have to_char, but override here
336            Key::Space => "Space".to_string(),
337            _ => self.to_char(false).unwrap().to_string(),
338        }
339    }
340
341    fn from_winit_key(input: KeyboardInput) -> Option<Key> {
342        let key = input.virtual_keycode?;
343        Some(match key {
344            VirtualKeyCode::A => Key::A,
345            VirtualKeyCode::B => Key::B,
346            VirtualKeyCode::C => Key::C,
347            VirtualKeyCode::D => Key::D,
348            VirtualKeyCode::E => Key::E,
349            VirtualKeyCode::F => Key::F,
350            VirtualKeyCode::G => Key::G,
351            VirtualKeyCode::H => Key::H,
352            VirtualKeyCode::I => Key::I,
353            VirtualKeyCode::J => Key::J,
354            VirtualKeyCode::K => Key::K,
355            VirtualKeyCode::L => Key::L,
356            VirtualKeyCode::M => Key::M,
357            VirtualKeyCode::N => Key::N,
358            VirtualKeyCode::O => Key::O,
359            VirtualKeyCode::P => Key::P,
360            VirtualKeyCode::Q => Key::Q,
361            VirtualKeyCode::R => Key::R,
362            VirtualKeyCode::S => Key::S,
363            VirtualKeyCode::T => Key::T,
364            VirtualKeyCode::U => Key::U,
365            VirtualKeyCode::V => Key::V,
366            VirtualKeyCode::W => Key::W,
367            VirtualKeyCode::X => Key::X,
368            VirtualKeyCode::Y => Key::Y,
369            VirtualKeyCode::Z => Key::Z,
370            VirtualKeyCode::Key1 | VirtualKeyCode::Numpad1 => Key::Num1,
371            VirtualKeyCode::Key2 | VirtualKeyCode::Numpad2 => Key::Num2,
372            VirtualKeyCode::Key3 | VirtualKeyCode::Numpad3 => Key::Num3,
373            VirtualKeyCode::Key4 | VirtualKeyCode::Numpad4 => Key::Num4,
374            VirtualKeyCode::Key5 | VirtualKeyCode::Numpad5 => Key::Num5,
375            VirtualKeyCode::Key6 | VirtualKeyCode::Numpad6 => Key::Num6,
376            VirtualKeyCode::Key7 | VirtualKeyCode::Numpad7 => Key::Num7,
377            VirtualKeyCode::Key8 | VirtualKeyCode::Numpad8 => Key::Num8,
378            VirtualKeyCode::Key9 | VirtualKeyCode::Numpad9 => Key::Num9,
379            VirtualKeyCode::Key0 | VirtualKeyCode::Numpad0 => Key::Num0,
380            VirtualKeyCode::LBracket => Key::LeftBracket,
381            VirtualKeyCode::RBracket => Key::RightBracket,
382            VirtualKeyCode::Space => Key::Space,
383            VirtualKeyCode::Slash => Key::Slash,
384            VirtualKeyCode::Period => Key::Dot,
385            VirtualKeyCode::Comma => Key::Comma,
386            VirtualKeyCode::Semicolon => Key::Semicolon,
387            VirtualKeyCode::Colon => Key::Colon,
388            VirtualKeyCode::Equals => Key::Equals,
389            VirtualKeyCode::Apostrophe => Key::SingleQuote,
390            VirtualKeyCode::Minus => Key::Minus,
391            VirtualKeyCode::Escape => Key::Escape,
392            VirtualKeyCode::Return => Key::Enter,
393            VirtualKeyCode::Tab => Key::Tab,
394            VirtualKeyCode::Back => Key::Backspace,
395            VirtualKeyCode::LShift => Key::LeftShift,
396            VirtualKeyCode::LControl => Key::LeftControl,
397            VirtualKeyCode::LAlt => Key::LeftAlt,
398            VirtualKeyCode::RAlt => Key::RightAlt,
399            VirtualKeyCode::Left => Key::LeftArrow,
400            VirtualKeyCode::Right => Key::RightArrow,
401            VirtualKeyCode::Up => Key::UpArrow,
402            VirtualKeyCode::Down => Key::DownArrow,
403            VirtualKeyCode::F1 => Key::F1,
404            VirtualKeyCode::F2 => Key::F2,
405            VirtualKeyCode::F3 => Key::F3,
406            VirtualKeyCode::F4 => Key::F4,
407            VirtualKeyCode::F5 => Key::F5,
408            VirtualKeyCode::F6 => Key::F6,
409            VirtualKeyCode::F7 => Key::F7,
410            VirtualKeyCode::F8 => Key::F8,
411            VirtualKeyCode::F9 => Key::F9,
412            VirtualKeyCode::F10 => Key::F10,
413            VirtualKeyCode::F11 => Key::F11,
414            VirtualKeyCode::F12 => Key::F12,
415            _ => {
416                println!("Unknown winit key {:?}", key);
417                return None;
418            }
419        })
420    }
421
422    pub fn txt(self, ctx: &EventCtx) -> TextSpan {
423        Line(self.describe()).fg(ctx.style().text_hotkey_color)
424    }
425}
426
427// TODO This is not an ideal representation at all.
428#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
429pub enum MultiKey {
430    Normal(Key),
431    LCtrl(Key),
432    Any(Vec<Key>),
433}
434
435impl MultiKey {
436    pub fn describe(&self) -> String {
437        match self {
438            MultiKey::Normal(key) => key.describe(),
439            MultiKey::LCtrl(key) => format!("Ctrl+{}", key.describe()),
440            MultiKey::Any(ref keys) => keys
441                .iter()
442                .map(|k| k.describe())
443                .collect::<Vec<_>>()
444                .join(", "),
445        }
446    }
447
448    pub fn txt(&self, ctx: &EventCtx) -> TextSpan {
449        Line(self.describe()).fg(ctx.style().text_hotkey_color)
450    }
451}
452
453pub fn lctrl(key: Key) -> MultiKey {
454    MultiKey::LCtrl(key)
455}
456
457pub fn hotkeys(keys: Vec<Key>) -> MultiKey {
458    MultiKey::Any(keys)
459}
460
461impl std::convert::From<Key> for Option<MultiKey> {
462    fn from(key: Key) -> Option<MultiKey> {
463        Some(MultiKey::Normal(key))
464    }
465}
466
467impl std::convert::From<Key> for MultiKey {
468    fn from(key: Key) -> MultiKey {
469        MultiKey::Normal(key)
470    }
471}