Skip to main content

siglus_scene_vm/runtime/
input.rs

1//! Runtime input state (winit-agnostic).
2//!
3//! Siglus scripts query input via numeric forms (INPUT/MOUSE/KEYLIST) and helper
4//! key objects. The original engine stores per-key state in fixed tables.
5//!
6//! Runtime input model:
7//! - A fixed 0..=255 virtual-key table (down + edge "stock" flags)
8//! - Mouse position
9//! - Mouse wheel delta since last read / frame
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum VmKey {
13    Escape,
14    Enter,
15    Space,
16    Backspace,
17    Tab,
18    Shift,
19    Alt,
20    ArrowUp,
21    ArrowDown,
22    ArrowLeft,
23    ArrowRight,
24    /// Function keys (F1..F12).
25    F(u8),
26    /// Digit keys 0..9.
27    Digit(u8),
28    /// Latin letter keys A..Z.
29    Letter(char),
30    /// Any other unmapped physical key.
31    Other(u32),
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
35pub enum VmMouseButton {
36    Left,
37    Right,
38    Middle,
39    Other(u8),
40}
41
42#[derive(Debug, Clone, Copy)]
43struct KeyState {
44    down: bool,
45    down_stock: bool,
46    up_stock: bool,
47    // Mirrors tona3 C_input_state::BUTTON::down_up_stock:
48    // 0 = no down/up sequence pending; 1 = down happened; 2 = down+up completed.
49    // NEXT/frame clears only state 2, so state 1 survives until the matching up event.
50    down_up_stock: u8,
51    flick_stock: bool,
52    flick_angle: f32,
53    flick_pixel: f32,
54    flick_mm: f32,
55    flick_start: Option<(i32, i32)>,
56}
57
58impl KeyState {
59    const fn new() -> Self {
60        Self {
61            down: false,
62            down_stock: false,
63            up_stock: false,
64            down_up_stock: 0,
65            flick_stock: false,
66            flick_angle: 0.0,
67            flick_pixel: 0.0,
68            flick_mm: 0.0,
69            flick_start: None,
70        }
71    }
72
73    fn clear_all(&mut self) {
74        self.down = false;
75        self.down_stock = false;
76        self.up_stock = false;
77        self.down_up_stock = 0;
78        self.flick_stock = false;
79        self.flick_angle = 0.0;
80        self.flick_pixel = 0.0;
81        self.flick_mm = 0.0;
82        self.flick_start = None;
83    }
84
85    fn clear_stocks(&mut self) {
86        self.down_stock = false;
87        self.up_stock = false;
88        // tona3 BUTTON::frame() only clears completed down-up stock.
89        // The intermediate state (1) must persist across NEXT/frame until set_up().
90        if self.down_up_stock == 2 {
91            self.down_up_stock = 0;
92        }
93        self.flick_stock = false;
94    }
95
96    fn use_stocks(&mut self) {
97        self.down_stock = false;
98        self.up_stock = false;
99        self.down_up_stock = 0;
100        self.flick_stock = false;
101    }
102}
103
104#[derive(Debug, Clone)]
105pub struct InputState {
106    keys: [KeyState; 256],
107
108    pub mouse_x: i32,
109    pub mouse_y: i32,
110    mouse_position_valid: bool,
111
112    wheel_delta: i32,
113
114    /// Last key-down event since start.
115    pub last_key_down: Option<VmKey>,
116    /// Last mouse-down event since start.
117    pub last_mouse_down: Option<VmMouseButton>,
118}
119
120impl Default for InputState {
121    fn default() -> Self {
122        Self {
123            keys: [KeyState::new(); 256],
124            mouse_x: -1,
125            mouse_y: -1,
126            mouse_position_valid: false,
127            wheel_delta: 0,
128            last_key_down: None,
129            last_mouse_down: None,
130        }
131    }
132}
133
134impl InputState {
135    // ---------------------------------------------------------------------
136    // Virtual key helpers
137    // ---------------------------------------------------------------------
138
139    /// Returns true if the given virtual-key is currently held down.
140    pub fn vk_is_down(&self, vk: u8) -> bool {
141        self.keys[vk as usize].down
142    }
143
144    /// Returns true if the key transitioned to down since the last `next_frame`.
145    pub fn vk_down_stock(&self, vk: u8) -> bool {
146        self.keys[vk as usize].down_stock
147    }
148
149    /// Returns true if the key transitioned to up since the last `next_frame`.
150    pub fn vk_up_stock(&self, vk: u8) -> bool {
151        self.keys[vk as usize].up_stock
152    }
153
154    /// Returns true if a down+up pair happened since the last `next_frame`.
155    pub fn vk_down_up_stock(&self, vk: u8) -> bool {
156        self.keys[vk as usize].down_up_stock == 2
157    }
158
159    /// Returns true if a flick was detected since the last `next_frame`.
160    pub fn vk_flick_stock(&self, vk: u8) -> bool {
161        self.keys[vk as usize].flick_stock
162    }
163
164    /// Returns flick angle (radians) for the last flick on this key.
165    pub fn vk_flick_angle(&self, vk: u8) -> f32 {
166        self.keys[vk as usize].flick_angle
167    }
168
169    /// Returns flick distance in pixels for the last flick on this key.
170    pub fn vk_flick_pixel(&self, vk: u8) -> f32 {
171        self.keys[vk as usize].flick_pixel
172    }
173
174    /// Returns flick distance in millimeters for the last flick on this key.
175    pub fn vk_flick_mm(&self, vk: u8) -> f32 {
176        self.keys[vk as usize].flick_mm
177    }
178
179    fn vk_set_down(&mut self, vk: u8) {
180        let st = &mut self.keys[vk as usize];
181        if !st.down {
182            st.down = true;
183            st.down_stock = true;
184        }
185        if st.down_up_stock == 0 {
186            st.down_up_stock = 1;
187        }
188        // If both edges happen within the same frame, mark the completed down-up stock.
189        if st.down_stock && st.up_stock {
190            st.down_up_stock = 2;
191        }
192    }
193
194    fn vk_set_up(&mut self, vk: u8) {
195        let st = &mut self.keys[vk as usize];
196        if st.down {
197            st.down = false;
198            st.up_stock = true;
199            if st.down_up_stock == 1 {
200                st.down_up_stock = 2;
201            }
202        }
203    }
204
205    fn vk_set_flick_start(&mut self, vk: u8) {
206        if !is_mouse_vk(vk) {
207            return;
208        }
209        let st = &mut self.keys[vk as usize];
210        st.flick_stock = false;
211        st.flick_start = Some((self.mouse_x, self.mouse_y));
212    }
213
214    fn vk_set_flick_end(&mut self, vk: u8) {
215        if !is_mouse_vk(vk) {
216            return;
217        }
218        let st = &mut self.keys[vk as usize];
219        let Some((sx, sy)) = st.flick_start.take() else {
220            return;
221        };
222        let dx = (self.mouse_x - sx) as f32;
223        let dy = (self.mouse_y - sy) as f32;
224        let dist = (dx * dx + dy * dy).sqrt();
225        if dist < FLICK_MIN_PIXEL {
226            return;
227        }
228        // Note: the original engine seems to treat angle as atan2(dx, dy).
229        st.flick_angle = dx.atan2(dy);
230        st.flick_pixel = dist;
231        st.flick_mm = dist * MM_PER_PX;
232        st.flick_stock = true;
233    }
234
235    /// Clears all keys (including held-down state) and all edge stocks.
236    pub fn clear_all(&mut self) {
237        for st in &mut self.keys {
238            st.clear_all();
239        }
240        self.wheel_delta = 0;
241        self.last_key_down = None;
242        self.last_mouse_down = None;
243    }
244
245    pub fn has_mouse_position(&self) -> bool {
246        self.mouse_position_valid
247    }
248
249    /// Clears only keyboard-visible state and leaves mouse state intact.
250    pub fn clear_keyboard(&mut self) {
251        for (idx, st) in self.keys.iter_mut().enumerate() {
252            if matches!(idx as u8, 0x01 | 0x02 | 0x04) {
253                continue;
254            }
255            st.clear_all();
256        }
257        self.last_key_down = None;
258    }
259
260    /// Clears only mouse-visible state and leaves keyboard state intact.
261    pub fn clear_mouse(&mut self) {
262        for vk in [0x01usize, 0x02usize, 0x04usize] {
263            self.keys[vk].clear_all();
264        }
265        self.wheel_delta = 0;
266        self.last_mouse_down = None;
267    }
268
269    /// Consumes current input edges while preserving held-down state.
270    ///
271    /// Mirrors tona3 C_input_state::use(): clear down/up/down_up/flick stocks
272    /// for mouse and keyboard plus wheel, but do not release held keys.
273    pub fn use_current(&mut self) {
274        for st in &mut self.keys {
275            st.use_stocks();
276        }
277        self.wheel_delta = 0;
278        self.last_key_down = None;
279        self.last_mouse_down = None;
280    }
281
282    /// Advances to the next frame: clears edge stocks but keeps held-down state.
283    pub fn next_frame(&mut self) {
284        for st in &mut self.keys {
285            st.clear_stocks();
286        }
287        self.wheel_delta = 0;
288    }
289
290    /// Advances only keyboard state to the next frame.
291    pub fn next_keyboard_frame(&mut self) {
292        for (idx, st) in self.keys.iter_mut().enumerate() {
293            if matches!(idx as u8, 0x01 | 0x02 | 0x04) {
294                continue;
295            }
296            st.clear_stocks();
297        }
298        self.last_key_down = None;
299    }
300
301    /// Advances only mouse state to the next frame.
302    pub fn next_mouse_frame(&mut self) {
303        for vk in [0x01usize, 0x02usize, 0x04usize] {
304            self.keys[vk].clear_stocks();
305        }
306        self.wheel_delta = 0;
307        self.last_mouse_down = None;
308    }
309
310    // ---------------------------------------------------------------------
311    // Wheel
312    // ---------------------------------------------------------------------
313
314    pub fn on_mouse_wheel(&mut self, delta_y: i32) {
315        self.wheel_delta = self.wheel_delta.saturating_add(delta_y);
316    }
317
318    /// Reads and clears the accumulated wheel delta.
319    pub fn take_wheel_delta(&mut self) -> i32 {
320        let v = self.wheel_delta;
321        self.wheel_delta = 0;
322        v
323    }
324
325    // ---------------------------------------------------------------------
326    // Bridge from platform key/mouse events
327    // ---------------------------------------------------------------------
328
329    pub fn is_key_down(&self, k: VmKey) -> bool {
330        vmkey_to_vk(k)
331            .map(|vk| self.vk_is_down(vk))
332            .unwrap_or(false)
333    }
334
335    pub fn is_mouse_down(&self, b: VmMouseButton) -> bool {
336        match b {
337            VmMouseButton::Left => self.vk_is_down(0x01),
338            VmMouseButton::Right => self.vk_is_down(0x02),
339            VmMouseButton::Middle => self.vk_is_down(0x04),
340            VmMouseButton::Other(_) => false,
341        }
342    }
343
344    pub fn on_key_down(&mut self, k: VmKey) {
345        if let Some(vk) = vmkey_to_vk(k) {
346            self.vk_set_down(vk);
347        }
348        self.last_key_down = Some(k);
349    }
350
351    pub fn on_key_up(&mut self, k: VmKey) {
352        if let Some(vk) = vmkey_to_vk(k) {
353            self.vk_set_up(vk);
354        }
355    }
356
357    pub fn on_mouse_down(&mut self, b: VmMouseButton) {
358        match b {
359            VmMouseButton::Left => {
360                self.vk_set_down(0x01);
361                self.vk_set_flick_start(0x01);
362            }
363            VmMouseButton::Right => {
364                self.vk_set_down(0x02);
365                self.vk_set_flick_start(0x02);
366            }
367            VmMouseButton::Middle => {
368                self.vk_set_down(0x04);
369                self.vk_set_flick_start(0x04);
370            }
371            VmMouseButton::Other(_) => {}
372        }
373        self.last_mouse_down = Some(b);
374    }
375
376    pub fn on_mouse_up(&mut self, b: VmMouseButton) {
377        match b {
378            VmMouseButton::Left => {
379                self.vk_set_flick_end(0x01);
380                self.vk_set_up(0x01);
381            }
382            VmMouseButton::Right => {
383                self.vk_set_flick_end(0x02);
384                self.vk_set_up(0x02);
385            }
386            VmMouseButton::Middle => {
387                self.vk_set_flick_end(0x04);
388                self.vk_set_up(0x04);
389            }
390            VmMouseButton::Other(_) => {}
391        }
392    }
393
394    pub fn on_mouse_move(&mut self, x: i32, y: i32) {
395        self.mouse_x = x;
396        self.mouse_y = y;
397        self.mouse_position_valid = true;
398    }
399
400    /// Returns a direction bitmask based on arrow keys.
401    ///
402    /// Bit layout:
403    ///   1=left, 2=right, 4=up, 8=down
404    pub fn dir_mask(&self) -> i64 {
405        let mut m = 0;
406        if self.vk_is_down(0x25) {
407            m |= 1;
408        }
409        if self.vk_is_down(0x27) {
410            m |= 2;
411        }
412        if self.vk_is_down(0x26) {
413            m |= 4;
414        }
415        if self.vk_is_down(0x28) {
416            m |= 8;
417        }
418        m
419    }
420}
421
422pub(crate) fn vmkey_to_vk_code(k: VmKey) -> Option<u8> {
423    vmkey_to_vk(k)
424}
425
426fn vmkey_to_vk(k: VmKey) -> Option<u8> {
427    match k {
428        VmKey::Escape => Some(0x1B),
429        VmKey::Enter => Some(0x0D),
430        VmKey::Space => Some(0x20),
431        VmKey::Backspace => Some(0x08),
432        VmKey::Tab => Some(0x09),
433        VmKey::Shift => Some(0x10),
434        VmKey::Alt => Some(0x12),
435
436        VmKey::ArrowLeft => Some(0x25),
437        VmKey::ArrowUp => Some(0x26),
438        VmKey::ArrowRight => Some(0x27),
439        VmKey::ArrowDown => Some(0x28),
440
441        VmKey::F(n) if (1..=12).contains(&n) => Some(0x6F + n), // F1=0x70
442        VmKey::Digit(n) if n <= 9 => Some(0x30 + n),
443        VmKey::Letter(c) => {
444            let uc = c.to_ascii_uppercase();
445            if ('A'..='Z').contains(&uc) {
446                Some(uc as u8)
447            } else {
448                None
449            }
450        }
451        VmKey::Other(_) => None,
452        _ => None,
453    }
454}
455
456fn is_mouse_vk(vk: u8) -> bool {
457    matches!(vk, 0x01 | 0x02 | 0x04)
458}
459
460const FLICK_MIN_PIXEL: f32 = 30.0;
461const MM_PER_PX: f32 = 25.4 / 96.0;