Skip to main content

kozan_platform/pipeline/
input_state.rs

1//! Per-window input state — cursor position and modifier keys.
2//!
3//! Chrome: `RenderWidgetHostImpl` tracks per-widget input state.
4//! The main thread updates this before routing events to threads.
5
6use kozan_core::input::{ButtonState, Modifiers, MouseButton};
7
8/// Per-window cursor and modifier tracking.
9///
10/// All coordinates are logical (CSS) pixels — physical-to-logical
11/// conversion happens at the platform boundary (set_cursor_physical).
12pub struct InputState {
13    cursor_x: f64,
14    cursor_y: f64,
15    scale_factor: f64,
16    modifiers: Modifiers,
17}
18
19impl InputState {
20    pub fn new(scale_factor: f64) -> Self {
21        Self {
22            cursor_x: 0.0,
23            cursor_y: 0.0,
24            scale_factor,
25            modifiers: Modifiers::EMPTY,
26        }
27    }
28
29    pub fn set_cursor_physical(&mut self, px: f64, py: f64) {
30        self.cursor_x = px / self.scale_factor;
31        self.cursor_y = py / self.scale_factor;
32    }
33
34    pub fn cursor(&self) -> (f64, f64) {
35        (self.cursor_x, self.cursor_y)
36    }
37
38    pub fn set_scale_factor(&mut self, factor: f64) {
39        self.scale_factor = factor;
40    }
41
42    pub fn scale_factor(&self) -> f64 {
43        self.scale_factor
44    }
45
46    pub fn modifiers(&self) -> Modifiers {
47        self.modifiers
48    }
49
50    pub fn set_modifiers_from_keyboard(&mut self, shift: bool, ctrl: bool, alt: bool, meta: bool) {
51        let mut m = Modifiers::EMPTY;
52        if shift {
53            m = m.with_shift();
54        }
55        if ctrl {
56            m = m.with_ctrl();
57        }
58        if alt {
59            m = m.with_alt();
60        }
61        if meta {
62            m = m.with_meta();
63        }
64        let mouse_bits = self.modifiers.bits() & Self::MOUSE_BUTTON_MASK;
65        self.modifiers = Modifiers::from_bits(m.bits() | mouse_bits);
66    }
67
68    pub fn update_button_modifier(&mut self, button: &MouseButton, state: ButtonState) {
69        let flag = match button {
70            MouseButton::Left => Modifiers::EMPTY.with_left_button(),
71            MouseButton::Right => Modifiers::EMPTY.with_right_button(),
72            MouseButton::Middle => Modifiers::EMPTY.with_middle_button(),
73            _ => return,
74        };
75        match state {
76            ButtonState::Pressed => self.modifiers |= flag,
77            ButtonState::Released => {
78                self.modifiers = Modifiers::from_bits(self.modifiers.bits() & !flag.bits());
79            }
80        }
81    }
82
83    const MOUSE_BUTTON_MASK: u16 = {
84        let l = Modifiers::EMPTY.with_left_button().bits();
85        let r = Modifiers::EMPTY.with_right_button().bits();
86        let m = Modifiers::EMPTY.with_middle_button().bits();
87        l | r | m
88    };
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn physical_to_logical_conversion() {
97        let mut state = InputState::new(2.0);
98        state.set_cursor_physical(200.0, 400.0);
99        assert_eq!(state.cursor(), (100.0, 200.0));
100    }
101
102    #[test]
103    fn modifiers_keyboard_preserves_mouse() {
104        let mut state = InputState::new(1.0);
105        state.update_button_modifier(&MouseButton::Left, ButtonState::Pressed);
106        state.set_modifiers_from_keyboard(true, false, false, false);
107        assert!(state.modifiers().shift());
108        assert!(state.modifiers().left_button());
109    }
110
111    #[test]
112    fn scale_factor_change() {
113        let mut state = InputState::new(1.0);
114        state.set_cursor_physical(100.0, 100.0);
115        assert_eq!(state.cursor(), (100.0, 100.0));
116        state.set_scale_factor(2.0);
117        state.set_cursor_physical(100.0, 100.0);
118        assert_eq!(state.cursor(), (50.0, 50.0));
119    }
120}