Skip to main content

cotis_web/
interactivity.rs

1//! Browser input providers for Cotis interactivity traits.
2//!
3//! # Mouse
4//!
5//! [`MouseProvider`](cotis_utils::interactivity::mouse::MouseProvider) is implemented on
6//! [`HTMLRenderer`](crate::renderer::HTMLRenderer). Use the renderer instance in your app
7//! when querying mouse position, buttons, and wheel delta.
8//!
9//! # Keyboard
10//!
11//! [`SimpleKeyboardProvider`](cotis_utils::interactivity::keyboard::SimpleKeyboardProvider) is
12//! implemented on [`HTMLInteractivity`], a zero-sized handle type. Keyboard state is tracked
13//! in a global [`HTMLKeyboardProvider`] updated each frame with delta time from
14//! [`CotisFrameContext::get_delta_time`](cotis_utils::traits::CotisFrameContext::get_delta_time).
15
16use std::collections::HashMap;
17use std::sync::{LazyLock, Mutex};
18
19use cotis_utils::interactivity::keyboard::{KEYBOARD_LIST, KeyboardKey, SimpleKeyboardProvider};
20use cotis_utils::interactivity::mouse;
21use cotis_utils::interactivity::mouse::MouseProvider;
22use cotis_utils::math::Vector2;
23
24use crate::renderer::HTMLRenderer;
25use crate::web_functions::primitives::{
26    get_mouse_position, get_mouse_wheel_move_v, get_next_char, mouse_button_down,
27};
28
29/// Zero-sized type implementing [`SimpleKeyboardProvider`] via global keyboard state.
30pub struct HTMLInteractivity;
31
32impl MouseProvider for HTMLRenderer {
33    fn get_mouse_position(&self) -> Vector2 {
34        let array = get_mouse_position();
35        Vector2 {
36            x: array.0,
37            y: array.1,
38        }
39    }
40
41    fn mouse_button_down(&self, n: mouse::MouseButton) -> bool {
42        mouse_button_down(n)
43    }
44
45    fn get_mouse_wheel_move_v(&self) -> Vector2 {
46        let array = get_mouse_wheel_move_v();
47        Vector2 {
48            x: array.0,
49            y: array.1,
50        }
51    }
52}
53
54pub(crate) struct HTMLKeyboardProvider {
55    keyboard_prev_state: Vec<KeyboardKey>,
56    keyboard_once_state: Vec<KeyboardKey>,
57    keyboard_once_release: Vec<KeyboardKey>,
58    cooldown: HashMap<KeyboardKey, f32>,
59}
60
61impl HTMLKeyboardProvider {
62    const COOLDOWN: f32 = 0.1;
63    const INITIAL_COOLDOWN: f32 = 5.0;
64
65    pub(crate) fn update(&mut self, delta_time: f32) {
66        self.keyboard_once_state.clear();
67        self.keyboard_once_release.clear();
68        for key in KEYBOARD_LIST {
69            if self.is_key_down(key) {
70                if !self.keyboard_prev_state.contains(&key) {
71                    self.keyboard_prev_state.push(key);
72                    self.keyboard_once_state.push(key);
73                    self.cooldown.insert(key, Self::INITIAL_COOLDOWN);
74                }
75            } else {
76                if self.keyboard_prev_state.contains(&key)
77                    && !self.keyboard_once_release.contains(&key)
78                {
79                    self.keyboard_once_release.push(key);
80                }
81                self.keyboard_prev_state.retain(|e| *e != key);
82                self.cooldown.retain(|e, _| *e != key);
83            }
84        }
85        for timer in self.cooldown.values_mut() {
86            if *timer > 0.0 {
87                *timer = (*timer - delta_time).max(0.0);
88            } else {
89                *timer = Self::COOLDOWN;
90            }
91        }
92    }
93
94    fn get_next_char(&mut self) -> Option<char> {
95        get_next_char()
96    }
97
98    fn is_key_pressed(&self, key: KeyboardKey) -> bool {
99        self.keyboard_once_state.contains(&key)
100    }
101
102    fn is_key_pressed_repeat(&self, key: KeyboardKey) -> bool {
103        if !self.keyboard_prev_state.contains(&key) {
104            return false;
105        }
106        if let Some(timer) = self.cooldown.get(&key) {
107            timer <= &0.0
108        } else {
109            false
110        }
111    }
112
113    fn is_key_down(&self, key: KeyboardKey) -> bool {
114        self.keyboard_prev_state.contains(&key)
115    }
116
117    fn is_key_released(&self, key: KeyboardKey) -> bool {
118        self.keyboard_once_release.contains(&key)
119    }
120
121    fn is_key_up(&self, key: KeyboardKey) -> bool {
122        !self.is_key_down(key)
123    }
124
125    fn get_key_pressed(&mut self) -> Option<KeyboardKey> {
126        self.keyboard_once_state.first().cloned()
127    }
128
129    fn get_char_pressed(&mut self) -> Option<char> {
130        self.get_next_char()
131    }
132}
133
134static KEYBOARD_PROVIDER: LazyLock<Mutex<HTMLKeyboardProvider>> = LazyLock::new(|| {
135    Mutex::new(HTMLKeyboardProvider {
136        keyboard_prev_state: Vec::new(),
137        keyboard_once_state: Vec::new(),
138        keyboard_once_release: Vec::new(),
139        cooldown: HashMap::new(),
140    })
141});
142
143impl MouseProvider for HTMLInteractivity {
144    fn get_mouse_position(&self) -> Vector2 {
145        let array = get_mouse_position();
146        Vector2 {
147            x: array.0,
148            y: array.1,
149        }
150    }
151
152    fn mouse_button_down(&self, n: mouse::MouseButton) -> bool {
153        mouse_button_down(n)
154    }
155
156    fn get_mouse_wheel_move_v(&self) -> Vector2 {
157        let array = get_mouse_wheel_move_v();
158        Vector2 {
159            x: array.0,
160            y: array.1,
161        }
162    }
163}
164
165impl SimpleKeyboardProvider for HTMLInteractivity {
166    fn get_pressed_keys(&self) -> indexmap::set::IndexSet<KeyboardKey> {
167        KEYBOARD_PROVIDER
168            .lock()
169            .map(|provider| provider.keyboard_prev_state.iter().cloned().collect())
170            .unwrap_or_default()
171    }
172}