blockworx_canvas2d/input/
dom.rs1use blockworx_geom::{Pos2, pos2};
4use blockworx_paint::{Button, PointerKind, ScrollPx};
5use web_sys::{DragEvent, KeyboardEvent, PointerEvent, WheelEvent};
6
7use super::{Edge, Held, KeySample, Named, Phase, PointerId, PointerSample, WheelSample};
8
9const DRAGGED: PointerId = PointerId(-1);
13
14const PIXELS_PER_LINE: f32 = 24.0;
17
18const LINES_PER_PAGE: f32 = 20.0;
20
21#[must_use]
25pub fn pointer(event: &PointerEvent, phase: Phase, origin: Pos2) -> PointerSample {
26 PointerSample {
27 id: PointerId(event.pointer_id()),
28 at: at(event.client_x(), event.client_y(), origin),
29 phase,
30 button: matches!(phase, Phase::Down | Phase::Up)
31 .then(|| button_of(event.button()))
32 .flatten(),
33 held: held(event.buttons()),
34 kind: if event.pointer_type() == "touch" {
35 PointerKind::Touch
36 } else {
37 PointerKind::Mouse
38 },
39 shift: event.shift_key().into(),
40 }
41}
42
43#[must_use]
50pub fn dragged(event: &DragEvent, origin: Pos2) -> PointerSample {
51 PointerSample {
52 id: DRAGGED,
53 at: at(event.client_x(), event.client_y(), origin),
54 phase: Phase::Moved,
55 button: None,
56 held: Held::NONE,
57 kind: PointerKind::Mouse,
58 shift: event.shift_key().into(),
59 }
60}
61
62#[must_use]
63pub fn wheel(event: &WheelEvent, origin: Pos2) -> WheelSample {
64 WheelSample {
65 at: at(event.client_x(), event.client_y(), origin),
66 scroll: ScrollPx::up(-pixels(event.delta_y() as f32, event.delta_mode())),
69 shift: event.shift_key().into(),
70 }
71}
72
73#[must_use]
77pub fn key(event: &KeyboardEvent, edge: Edge) -> Option<KeySample> {
78 let named = match event.key().as_str() {
79 "Escape" => Named::Escape,
80 "Delete" | "Backspace" => Named::Delete,
81 " " | "Spacebar" => Named::Space,
82 _ => return None,
83 };
84 Some(KeySample {
85 key: named,
86 edge,
87 shift: event.shift_key().into(),
88 })
89}
90
91fn at(client_x: i32, client_y: i32, origin: Pos2) -> Pos2 {
92 pos2(client_x as f32 - origin.x, client_y as f32 - origin.y)
93}
94
95fn held(buttons: u16) -> Held {
96 Held::of(
97 [
98 (1, Button::Primary),
99 (2, Button::Secondary),
100 (4, Button::Middle),
101 ]
102 .into_iter()
103 .filter(|&(mask, _)| buttons & mask != 0)
104 .map(|(_, button)| button),
105 )
106}
107
108#[must_use]
112pub fn button_of(button: i16) -> Option<Button> {
113 match button {
114 0 => Some(Button::Primary),
115 1 => Some(Button::Middle),
116 2 => Some(Button::Secondary),
117 _ => None,
118 }
119}
120
121fn pixels(delta: f32, mode: u32) -> f32 {
122 match mode {
123 1 => delta * PIXELS_PER_LINE,
124 2 => delta * PIXELS_PER_LINE * LINES_PER_PAGE,
125 _ => delta,
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn the_dom_button_mask_names_the_buttons_it_holds() {
135 assert_eq!(held(0), Held::NONE);
136 assert_eq!(held(1), Held::of([Button::Primary]));
137 assert_eq!(held(5), Held::of([Button::Primary, Button::Middle]));
138 assert!(held(2).holds(Button::Secondary));
139 }
140
141 #[test]
144 fn the_dom_button_index_is_not_the_mask() {
145 assert_eq!(button_of(0), Some(Button::Primary));
146 assert_eq!(button_of(1), Some(Button::Middle));
147 assert_eq!(button_of(2), Some(Button::Secondary));
148 assert_eq!(button_of(3), None);
149 }
150
151 #[test]
152 fn a_wheel_reported_in_lines_or_pages_is_read_in_pixels() {
153 assert_eq!(pixels(3.0, 0), 3.0);
154 assert_eq!(pixels(3.0, 1), 3.0 * PIXELS_PER_LINE);
155 assert!(
156 pixels(1.0, 2) > pixels(1.0, 1),
157 "a page is more than a line"
158 );
159 }
160}