Skip to main content

brep_app/automation/
pointer.rs

1//! The stateful virtual pointer and keyboard (§6): position, held buttons and
2//! modifiers persist across frames; each input command becomes one or more
3//! `egui::Event`s pushed into the frame's `RawInput`.
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
7#[serde(rename_all = "snake_case")]
8pub enum Button {
9    Primary,
10    Secondary,
11    Middle,
12    Extra1,
13    Extra2,
14}
15
16impl Button {
17    pub fn egui(self) -> egui::PointerButton {
18        match self {
19            Button::Primary => egui::PointerButton::Primary,
20            Button::Secondary => egui::PointerButton::Secondary,
21            Button::Middle => egui::PointerButton::Middle,
22            Button::Extra1 => egui::PointerButton::Extra1,
23            Button::Extra2 => egui::PointerButton::Extra2,
24        }
25    }
26    fn index(self) -> usize {
27        self as usize
28    }
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
32#[serde(rename_all = "snake_case")]
33pub enum WheelUnit {
34    Point,
35    Line,
36    Page,
37}
38
39#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
40#[serde(deny_unknown_fields)]
41pub struct Modifiers {
42    #[serde(default)]
43    pub ctrl: bool,
44    #[serde(default)]
45    pub shift: bool,
46    #[serde(default)]
47    pub alt: bool,
48    /// Cmd on macOS; treated as Ctrl elsewhere.
49    #[serde(default)]
50    pub command: bool,
51}
52
53impl Modifiers {
54    pub fn egui(self) -> egui::Modifiers {
55        egui::Modifiers {
56            alt: self.alt,
57            ctrl: self.ctrl || self.command,
58            shift: self.shift,
59            mac_cmd: self.command,
60            command: self.ctrl || self.command,
61        }
62    }
63}
64
65/// The pointer as reported to callers.
66#[derive(Debug, Clone, Serialize, schemars::JsonSchema)]
67pub struct PointerState {
68    /// Position in egui points, `null` when the pointer is not over the surface.
69    pub pos: Option<[f32; 2]>,
70    pub buttons: Vec<Button>,
71    pub modifiers: Modifiers,
72}
73
74#[derive(Debug, Clone, Default)]
75pub struct Pointer {
76    pub pos: Option<egui::Pos2>,
77    pub buttons: [bool; 5],
78    pub modifiers: Modifiers,
79}
80
81const ALL_BUTTONS: [Button; 5] = [Button::Primary, Button::Secondary, Button::Middle, Button::Extra1, Button::Extra2];
82
83impl Pointer {
84    pub fn state(&self) -> PointerState {
85        PointerState {
86            pos: self.pos.map(|p| [p.x, p.y]),
87            buttons: ALL_BUTTONS.iter().copied().filter(|b| self.buttons[b.index()]).collect(),
88            modifiers: self.modifiers,
89        }
90    }
91
92    pub fn moved(&mut self, x: f32, y: f32, out: &mut Vec<egui::Event>) {
93        let pos = egui::pos2(x, y);
94        self.pos = Some(pos);
95        out.push(egui::Event::PointerMoved(pos));
96    }
97
98    pub fn button(&mut self, button: Button, pressed: bool, out: &mut Vec<egui::Event>) -> Result<(), String> {
99        let pos = self.pos.ok_or("the pointer has no position yet: move it first")?;
100        self.buttons[button.index()] = pressed;
101        out.push(egui::Event::PointerButton { pos, button: button.egui(), pressed, modifiers: self.modifiers.egui() });
102        Ok(())
103    }
104
105    pub fn gone(&mut self, out: &mut Vec<egui::Event>) {
106        self.pos = None;
107        self.buttons = [false; 5];
108        out.push(egui::Event::PointerGone);
109    }
110
111    pub fn wheel(&mut self, dx: f32, dy: f32, unit: WheelUnit, out: &mut Vec<egui::Event>) {
112        let unit = match unit {
113            WheelUnit::Point => egui::MouseWheelUnit::Point,
114            WheelUnit::Line => egui::MouseWheelUnit::Line,
115            WheelUnit::Page => egui::MouseWheelUnit::Page,
116        };
117        out.push(egui::Event::MouseWheel {
118            unit,
119            delta: egui::vec2(dx, dy),
120            phase: egui::TouchPhase::Move,
121            modifiers: self.modifiers.egui(),
122        });
123    }
124
125    pub fn set_modifiers(&mut self, m: Modifiers) {
126        self.modifiers = m;
127    }
128
129    pub fn key(&mut self, name: &str, pressed: bool, repeat: bool, out: &mut Vec<egui::Event>) -> Result<(), String> {
130        let key = egui::Key::from_name(name).ok_or_else(|| format!("unknown key `{name}` (egui key names: A..Z, 0..9, Enter, Escape, Tab, Space, Backspace, Delete, ArrowLeft/Right/Up/Down, Home, End, PageUp/Down, F1..F12, Minus, Plus, …)"))?;
131        out.push(egui::Event::Key { key, physical_key: None, pressed, repeat, modifiers: self.modifiers.egui() });
132        Ok(())
133    }
134
135    pub fn text(&mut self, text: &str, out: &mut Vec<egui::Event>) {
136        if !text.is_empty() {
137            out.push(egui::Event::Text(text.to_string()));
138        }
139    }
140}