Skip to main content

kui_core/
event.rs

1//! Typed readings of the core's own event payloads (backlog DX7).
2//!
3//! A payload is a [`Value`] because it crosses into Lua, Node and C in
4//! one shape (`schema::EVENTS` is its spelling). A Rust handler read it
5//! back field by field — `get_f32("x")`, `get_str("phase")`, a `KeyPress`
6//! rebuilt from nine keys — which is the reading done once here instead.
7//! Each view is `None` for an event of another kind, so a handler
8//! matches on the view it wants:
9//!
10//! ```ignore
11//! if let Some(d) = ev.drag() {
12//!     self.split = d.ratio().x;
13//! } else if let Some((KeyPhase::Down, k)) = ev.key_press() {
14//!     self.bind(k);
15//! }
16//! ```
17//!
18//! The app's own tag is still [`UiEvent::message`]; these are the fields
19//! the core adds around it. The payload stays the wire, and nothing here
20//! changes what an event carries.
21
22use crate::geom::{Rect, Vec2};
23use crate::input::{KeyCode, KeyMods, KeyPhase, KeyPress, MouseButton, UiEvent};
24use crate::value::Value;
25
26/// Which part of a drag an event reports.
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub enum DragPhase {
29    Start,
30    Move,
31    End,
32}
33
34/// A `{kind:"drag"}` event: an `onDrag` node's pointer capture.
35#[derive(Clone, Copy, Debug, PartialEq)]
36pub struct Drag {
37    pub phase: DragPhase,
38    /// Where the pointer is.
39    pub pos: Vec2,
40    /// How far it is from where it pressed — in every phase, so a value
41    /// is `start + delta`, never a sum.
42    pub delta: Vec2,
43    /// The dragged node's parent's rect, for fractions without a query.
44    pub parent: Rect,
45    /// On a `cells` grid: the `(row, col)` under the pointer.
46    pub cell: Option<(u32, u32)>,
47    /// Inside an `onKey` sink that draws `role="line"` rows: the line, the
48    /// byte in its text, and the press's click count.
49    pub line: Option<u32>,
50    pub byte: Option<usize>,
51    pub clicks: Option<u32>,
52}
53
54impl Drag {
55    /// The pointer's place across the parent, 0 at its left or top edge
56    /// and 1 at its right or bottom, clamped: a divider's split, a
57    /// slider's value. 0 on an axis the parent has no size on.
58    pub fn ratio(&self) -> Vec2 {
59        let along = |p: f32, at: f32, len: f32| {
60            if len > 0.0 {
61                ((p - at) / len).clamp(0.0, 1.0)
62            } else {
63                0.0
64            }
65        };
66        Vec2::new(
67            along(self.pos.x, self.parent.x, self.parent.w),
68            along(self.pos.y, self.parent.y, self.parent.h),
69        )
70    }
71}
72
73/// Which part of a held non-primary button an event reports (backlog
74/// F105).
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76pub enum ButtonPhase {
77    Press,
78    Move,
79    Release,
80}
81
82/// A `{kind:"button"}` event: a non-primary button an `onButton` node
83/// claimed, captured by it from press to release (backlog F105).
84#[derive(Clone, Copy, Debug, PartialEq)]
85pub struct ButtonEvent {
86    pub phase: ButtonPhase,
87    pub button: MouseButton,
88    /// Where the pointer is.
89    pub pos: Vec2,
90    /// The driver's click count, on the press only.
91    pub clicks: Option<u32>,
92    /// On a `cells` grid: the `(row, col)` under the pointer, clamped to
93    /// the grid.
94    pub cell: Option<(u32, u32)>,
95    /// Inside an `onKey` sink that draws `role="line"` rows: the line and
96    /// the byte in its text, as a drag carries them (backlog RG75).
97    pub line: Option<u32>,
98    pub byte: Option<usize>,
99}
100
101/// A `{kind:"scroll"}` event: the wheel over an `onScroll` node.
102#[derive(Clone, Copy, Debug, PartialEq)]
103pub struct Scroll {
104    pub pos: Vec2,
105    pub delta: Vec2,
106    /// On a `cells` grid: whole rows the wheel moved, the remainder
107    /// carried to the next event; later history is negative.
108    pub lines: Option<i64>,
109}
110
111/// Which half of a hover an event reports.
112#[derive(Clone, Copy, Debug, PartialEq, Eq)]
113pub enum HoverPhase {
114    Enter,
115    Leave,
116}
117
118/// What moved to change the hover (backlog DX20).
119#[derive(Clone, Copy, Debug, PartialEq, Eq)]
120pub enum HoverBy {
121    /// The pointer moved, or left the window.
122    Pointer,
123    /// The pointer stayed and what is under it changed: a list scrolled
124    /// by the wheel or the keys, a row that grew, a float that opened. A
125    /// picker that follows the pointer ignores these.
126    Content,
127}
128
129/// A `{kind:"hover"}` event: an `onHover` node entered or left.
130#[derive(Clone, Copy, Debug, PartialEq, Eq)]
131pub struct Hover {
132    pub phase: HoverPhase,
133    pub by: HoverBy,
134}
135
136/// A `{kind:"layout"}` event: an `onLayout` node's placed rect.
137#[derive(Clone, Copy, Debug, PartialEq)]
138pub struct Layout {
139    pub rect: Rect,
140    pub parent: Rect,
141    pub scale: f32,
142}
143
144/// A `{kind:"text"}` event: what a focused key sink or editor was given
145/// to insert.
146#[derive(Clone, Copy, Debug, PartialEq)]
147pub struct TextInput<'a> {
148    pub text: &'a str,
149    /// The clipboard's answer to a paste the app asked for, not typing.
150    pub pasted: bool,
151    /// Typed under secure keyboard entry: keep it out of logs and history.
152    pub concealed: bool,
153    /// A clipboard manager or a password tool marked it as not for keeping.
154    pub transient: bool,
155}
156
157fn point(p: &Value) -> Option<Vec2> {
158    Some(Vec2::new(p.get_f32("x")?, p.get_f32("y")?))
159}
160
161fn rect(p: &Value) -> Option<Rect> {
162    Some(Rect::new(
163        p.get_f32("x")?,
164        p.get_f32("y")?,
165        p.get_f32("w")?,
166        p.get_f32("h")?,
167    ))
168}
169
170fn small(p: &Value, key: &str) -> Option<u32> {
171    p.get_int(key).and_then(|v| u32::try_from(v).ok())
172}
173
174impl UiEvent {
175    /// The app's tag inside a core event's payload — `onDrag`'s, `onKey`'s
176    /// — as sent; [`UiEvent::message`] reads it typed.
177    pub fn tag(&self) -> Option<&Value> {
178        self.payload.get("tag")
179    }
180
181    /// This event as a drag, if it is one.
182    pub fn drag(&self) -> Option<Drag> {
183        let p = &self.payload;
184        if self.kind()? != "drag" {
185            return None;
186        }
187        let phase = match p.get_str("phase")? {
188            "start" => DragPhase::Start,
189            "move" => DragPhase::Move,
190            "end" => DragPhase::End,
191            _ => return None,
192        };
193        let cell = p
194            .get("cell")
195            .and_then(|c| Some((small(c, "row")?, small(c, "col")?)));
196        Some(Drag {
197            phase,
198            pos: point(p)?,
199            delta: Vec2::new(p.get_f32("dx")?, p.get_f32("dy")?),
200            parent: p.get("parent").and_then(rect).unwrap_or_default(),
201            cell,
202            line: small(p, "line"),
203            byte: p.get_int("byte").and_then(|v| usize::try_from(v).ok()),
204            clicks: small(p, "clicks"),
205        })
206    }
207
208    /// This event as a held non-primary button's press, move or release,
209    /// if it is one (backlog F105).
210    pub fn button(&self) -> Option<ButtonEvent> {
211        let p = &self.payload;
212        if self.kind()? != "button" {
213            return None;
214        }
215        let phase = match p.get_str("phase")? {
216            "press" => ButtonPhase::Press,
217            "move" => ButtonPhase::Move,
218            "release" => ButtonPhase::Release,
219            _ => return None,
220        };
221        let button = match p.get("button")? {
222            Value::Str(name) => MouseButton::from_name(name)?,
223            Value::Int(code) => MouseButton::from_code(u32::try_from(*code).ok()?),
224            _ => return None,
225        };
226        let cell = p
227            .get("cell")
228            .and_then(|c| Some((small(c, "row")?, small(c, "col")?)));
229        Some(ButtonEvent {
230            phase,
231            button,
232            pos: point(p)?,
233            clicks: small(p, "clicks"),
234            cell,
235            line: small(p, "line"),
236            byte: p.get_int("byte").and_then(|v| usize::try_from(v).ok()),
237        })
238    }
239
240    /// This event as a key press or release on a key sink, if it is one
241    /// (`key_press`, since `key` is the node the event is about):
242    /// the [`KeyPress`] the core built it from, so a keymap binds the same
243    /// value a headless `press` sends.
244    pub fn key_press(&self) -> Option<(KeyPhase, KeyPress)> {
245        let p = &self.payload;
246        if self.kind()? != "key" {
247            return None;
248        }
249        let phase = match p.get_str("phase")? {
250            "down" => KeyPhase::Down,
251            "up" => KeyPhase::Up,
252            _ => return None,
253        };
254        let code = KeyCode::from_name(p.get_str("code")?)?;
255        let physical = p
256            .get_str("physical")
257            .and_then(KeyCode::from_name)
258            .unwrap_or(KeyCode::Unknown);
259        let press = KeyPress {
260            code,
261            physical,
262            mods: mods(p),
263            text: p.get_str("text").map(str::to_string),
264            repeat: p.get_bool("repeat").unwrap_or(false),
265            location: p
266                .get_str("location")
267                .and_then(crate::input::KeyLocation::from_name)
268                .unwrap_or_default(),
269            locks: crate::input::KeyLocks {
270                caps: p.get_bool("caps_lock").unwrap_or(false),
271                num: p.get_bool("num_lock").unwrap_or(false),
272            },
273        };
274        Some((phase, press))
275    }
276
277    /// This event as inserted text, if it is some.
278    pub fn text(&self) -> Option<TextInput<'_>> {
279        let p = &self.payload;
280        if self.kind()? != "text" {
281            return None;
282        }
283        Some(TextInput {
284            text: p.get_str("text")?,
285            pasted: p.get_bool("pasted").unwrap_or(false),
286            concealed: p.get_bool("concealed").unwrap_or(false),
287            transient: p.get_bool("transient").unwrap_or(false),
288        })
289    }
290
291    /// This event as a wheel scroll, if it is one.
292    pub fn scroll(&self) -> Option<Scroll> {
293        let p = &self.payload;
294        if self.kind()? != "scroll" {
295            return None;
296        }
297        Some(Scroll {
298            pos: point(p)?,
299            delta: Vec2::new(p.get_f32("dx")?, p.get_f32("dy")?),
300            lines: p.get_int("lines"),
301        })
302    }
303
304    /// This event as a hover edge, if it is one.
305    pub fn hover(&self) -> Option<Hover> {
306        if self.kind()? != "hover" {
307            return None;
308        }
309        let phase = match self.payload.get_str("phase")? {
310            "enter" => HoverPhase::Enter,
311            "leave" => HoverPhase::Leave,
312            _ => return None,
313        };
314        let by = match self.payload.get_str("by") {
315            Some("content") => HoverBy::Content,
316            _ => HoverBy::Pointer,
317        };
318        Some(Hover { phase, by })
319    }
320
321    /// This event as a modifier change, if it is one: the keys now held.
322    pub fn modifiers(&self) -> Option<KeyMods> {
323        if self.kind()? != "modifiers" {
324            return None;
325        }
326        Some(mods(&self.payload))
327    }
328
329    /// This event as an `onLayout` report, if it is one.
330    pub fn layout(&self) -> Option<Layout> {
331        let p = &self.payload;
332        if self.kind()? != "layout" {
333            return None;
334        }
335        Some(Layout {
336            rect: rect(p)?,
337            parent: p.get("parent").and_then(rect).unwrap_or_default(),
338            scale: p.get_f32("scale").unwrap_or(1.0),
339        })
340    }
341}
342
343/// The four modifier flags a `key` and a `modifiers` payload both carry.
344fn mods(p: &Value) -> KeyMods {
345    let on = |k| p.get_bool(k).unwrap_or(false);
346    KeyMods {
347        shift: on("shift"),
348        ctrl: on("ctrl"),
349        alt: on("alt"),
350        super_key: on("super"),
351    }
352}