Skip to main content

consortium_hmi_input/
event.rs

1// Copyright 2026 Ethan Wu
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15// SPDX-License-Identifier: Apache-2.0
16
17//! The portable [`InputEvent`] type and its component enums.
18
19use consortium_ipc::IpcSafe;
20use serde::{Deserialize, Serialize};
21
22/// A semantic input event, independent of its hardware or OS source.
23///
24/// Positions are in **logical UI coordinates** — the same units an HMI app
25/// renders at — so the *source* owns device-to-logical calibration and every
26/// backend consumes one coordinate space. `i16` is deliberate: it spans any
27/// realistic panel while staying a fixed, `IpcSafe` width across cores
28/// (unlike `usize`).
29///
30/// The enum is `#[non_exhaustive]`; match with a wildcard arm so new event
31/// kinds can be added without a breaking change.
32#[derive(Clone, Copy, Debug, PartialEq, Eq, IpcSafe, Deserialize, Serialize)]
33#[non_exhaustive]
34pub enum InputEvent {
35    /// A digital button, D-pad direction, or key changed state.
36    Button {
37        /// Which logical control changed.
38        code: ButtonCode,
39        /// `true` on press, `false` on release.
40        pressed: bool,
41    },
42    /// Absolute pointer/cursor motion, in logical UI coordinates.
43    Pointer {
44        /// Logical X, clamped by the source to the UI width.
45        x: i16,
46        /// Logical Y, clamped by the source to the UI height.
47        y: i16,
48    },
49    /// A pointer (mouse) button changed state at the last known position.
50    PointerButton {
51        /// Which pointer button changed.
52        button: PointerButton,
53        /// `true` on press, `false` on release.
54        pressed: bool,
55    },
56    /// A single touch contact, in logical UI coordinates.
57    Touch {
58        /// Stable contact identifier for the lifetime of one touch.
59        id: u8,
60        /// Lifecycle phase of this contact.
61        phase: TouchPhase,
62        /// Logical X of the contact.
63        x: i16,
64        /// Logical Y of the contact.
65        y: i16,
66    },
67    /// A scroll/wheel tick along one axis, in logical detents.
68    Scroll {
69        /// Axis the scroll applies to.
70        axis: ScrollAxis,
71        /// Signed magnitude; sign follows the natural axis direction.
72        delta: i16,
73    },
74    /// A raw keyboard scancode, for text and navigation backends.
75    ///
76    /// The scancode space is backend-defined (e.g. USB HID usage IDs); this
77    /// crate does not enumerate keys, keeping the model small and stable.
78    Key {
79        /// Backend-defined scancode.
80        scancode: u16,
81        /// `true` on press, `false` on release.
82        pressed: bool,
83    },
84}
85
86/// A logical control on a gamepad-style input surface.
87///
88/// Face buttons use direction-neutral `South`/`East`/`West`/`North` names
89/// (the standard-gamepad convention) rather than any one vendor's glyphs; a
90/// backend maps them to its own button identity.
91#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IpcSafe, Deserialize, Serialize)]
92#[non_exhaustive]
93pub enum ButtonCode {
94    /// D-pad up.
95    DpadUp,
96    /// D-pad down.
97    DpadDown,
98    /// D-pad left.
99    DpadLeft,
100    /// D-pad right.
101    DpadRight,
102    /// Bottom face button.
103    South,
104    /// Right face button.
105    East,
106    /// Left face button.
107    West,
108    /// Top face button.
109    North,
110    /// Left shoulder / trigger.
111    L1,
112    /// Right shoulder / trigger.
113    R1,
114    /// Start / menu.
115    Start,
116    /// Select / back.
117    Select,
118}
119
120/// A pointer (mouse) button.
121#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IpcSafe, Deserialize, Serialize)]
122#[non_exhaustive]
123pub enum PointerButton {
124    /// Primary (usually left) button.
125    Left,
126    /// Secondary (usually right) button.
127    Right,
128    /// Middle / wheel button.
129    Middle,
130}
131
132/// Lifecycle phase of a touch contact.
133#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IpcSafe, Deserialize, Serialize)]
134#[non_exhaustive]
135pub enum TouchPhase {
136    /// Contact began.
137    Down,
138    /// Contact moved while held.
139    Move,
140    /// Contact lifted normally.
141    Up,
142    /// Contact aborted by the system (e.g. palm rejection).
143    Cancel,
144}
145
146/// Axis a [`InputEvent::Scroll`] applies to.
147#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IpcSafe, Deserialize, Serialize)]
148#[non_exhaustive]
149pub enum ScrollAxis {
150    /// Vertical scroll.
151    Vertical,
152    /// Horizontal scroll.
153    Horizontal,
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159
160    #[test]
161    fn events_round_trip_through_a_codec() {
162        // Proves the IPC path: an event serialized on one core decodes to an
163        // identical value on another. postcard is Consortium's default codec.
164        let events = [
165            InputEvent::Button {
166                code: ButtonCode::South,
167                pressed: true,
168            },
169            InputEvent::Pointer { x: -12, y: 300 },
170            InputEvent::PointerButton {
171                button: PointerButton::Right,
172                pressed: false,
173            },
174            InputEvent::Touch {
175                id: 2,
176                phase: TouchPhase::Move,
177                x: 120,
178                y: 64,
179            },
180            InputEvent::Scroll {
181                axis: ScrollAxis::Vertical,
182                delta: -3,
183            },
184            InputEvent::Key {
185                scancode: 0x2C,
186                pressed: true,
187            },
188        ];
189
190        let mut buf = [0u8; 32];
191        for event in events {
192            let encoded = postcard::to_slice(&event, &mut buf).expect("encode");
193            let decoded: InputEvent = postcard::from_bytes(encoded).expect("decode");
194            assert_eq!(event, decoded);
195        }
196    }
197}