ltk/input/event.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
//! Platform-neutral input events: what a platform backend hands the runtime.
//!
//! A backend turns what its input system reports into these types and passes them to the runtime through a small set of entry points on `AppData`:
//!
//! | Source | Entry points |
//! | --- | --- |
//! | Mouse, touchpad | `pointer_input( &PointerInput )` |
//! | Fingers | `touch_input( &TouchInput )`, and `reset_touch_state()` when the platform cancels the touch sequence |
//! | Keyboard | `keyboard_enter( focus )`, `keyboard_leave()`, `key_press( KeyInput )`, `key_release( &KeyInput )`, `key_repeated( KeyInput )`, `modifiers_changed( ctrl, shift, alt )`, `set_repeat_info( rate, delay )` |
//!
//! Everything past those entry points is shared by every backend: hover and focus, the gesture machine (tap, long-press, drag, scroll, swipe, fling), text editing, the context menu, the built-in on-screen keyboard, back handling, key repeat and every `App` input callback. `platform/wayland/input.rs` is the Wayland backend's side and the reference for a new one; it carries no input logic.
//!
//! The types carry only what that shared logic reads. What a protocol needs for its own requests stays with the backend, which records it before it forwards the event: Wayland's serials, which a window move, a selection or a cursor shape quote back to the compositor, are the example.
//!
//! # Units
//!
//! - **Positions** are in the **logical** coordinates of the surface the event belongs to, as a compositor reports them. The handlers scale them to physical pixels with the surface's scale factor, so a backend whose platform reports physical pixels divides by that factor first.
//! - **Time** is in milliseconds on the platform's monotonic input clock. Only the difference between two events is read (fling velocity), so the clock's origin does not matter.
//! - **Scroll distance** is in logical pixels, positive downwards and rightwards, as `wl_pointer.axis` reports it.
//!
//! # Surfaces
//!
//! Pointer events and the first event of each finger name the [`SurfaceFocus`] they land on. A backend with a single surface always passes `SurfaceFocus::Main`. Later events of a finger go to the surface it landed on, wherever it moves, so they carry no focus. The keyboard's focus is set by `keyboard_enter`.
use crate::app::{ MouseButton, RawKey };
use crate::event_loop::SurfaceFocus;
use crate::Keysym;
/// A key pressed, released or repeated.
#[ derive( Debug, Clone, PartialEq ) ]
pub( crate ) struct KeyInput
{
pub keysym: Keysym,
/// The physical key, in the platform's own numbering. A press and its release carry the same code even when a modifier changed the keysym in between, as Shift does to a letter.
pub keycode: u32,
/// What the key types once dead keys and compose sequences are applied. `None` or empty for a key that types nothing.
pub text: Option<String>,
}
impl KeyInput
{
/// A key with no physical counterpart, such as one tapped on the built-in on-screen keyboard.
pub( crate ) fn synthetic( keysym: Keysym, text: Option<String> ) -> Self
{
Self { keysym, keycode: 0, text }
}
/// The key as [`App::on_raw_key_event`](crate::App::on_raw_key_event) reports it, with the modifiers held. A release types nothing.
pub( crate ) fn raw( &self, pressed: bool, ctrl: bool, shift: bool, alt: bool ) -> RawKey
{
RawKey
{
keysym: self.keysym,
keycode: self.keycode,
pressed,
ctrl,
shift,
alt,
text: self.text.clone().filter( |text| pressed && !text.is_empty() ),
}
}
}
/// One event from a mouse or a touchpad.
#[ derive( Debug, Clone, Copy, PartialEq ) ]
pub( crate ) struct PointerInput
{
pub focus: SurfaceFocus,
/// Where the pointer is, in logical coordinates of `focus`.
pub position: ( f64, f64 ),
pub kind: PointerKind,
}
#[ derive( Debug, Clone, Copy, PartialEq ) ]
pub( crate ) enum PointerKind
{
/// The pointer came onto the surface. The runtime then sets the cursor's shape, so a backend that must claim the cursor first does so before forwarding it.
Enter,
Leave,
Motion,
Press( PointerButton ),
Release( PointerButton ),
/// A wheel or a touchpad scrolled.
Axis
{
horizontal: Axis,
vertical: Axis,
source: AxisSource,
},
}
#[ derive( Debug, Clone, Copy, PartialEq, Eq ) ]
pub( crate ) enum PointerButton
{
Left,
Right,
Middle,
/// The side button that means back, `BTN_SIDE` or `BTN_BACK` on Linux.
Back,
/// Any other button. The runtime ignores it.
Other,
}
impl PointerButton
{
/// The button as [`App::on_mouse_button`](crate::App::on_mouse_button) reports it, which knows only the three main ones.
pub( crate ) fn mouse_button( self ) -> Option<MouseButton>
{
match self
{
PointerButton::Left => Some( MouseButton::Left ),
PointerButton::Right => Some( MouseButton::Right ),
PointerButton::Middle => Some( MouseButton::Middle ),
_ => None,
}
}
}
/// Scroll along one axis.
#[ derive( Debug, Clone, Copy, PartialEq, Default ) ]
pub( crate ) struct Axis
{
/// In logical pixels, positive downwards or rightwards.
pub distance: f64,
/// Wheel detents, fractional for a high-resolution wheel. `0.0` for a touchpad and any other source without detents.
pub notches: f32,
}
#[ derive( Debug, Clone, Copy, PartialEq, Eq ) ]
pub( crate ) enum AxisSource
{
Wheel,
/// A touchpad, or another source that scrolls continuously.
Continuous,
/// A tilted wheel, or a source the platform does not name.
Other,
}
impl AxisSource
{
/// What a viewport multiplies [`Axis::distance`] by: a wheel reports a few pixels per detent, which alone would scroll a page far too slowly.
pub( crate ) fn scroll_factor( self ) -> f32
{
match self
{
AxisSource::Wheel => 10.0,
_ => 1.0,
}
}
}
/// One finger's event.
#[ derive( Debug, Clone, Copy, PartialEq ) ]
pub( crate ) struct TouchInput
{
/// Names the finger from its down to its up. The platform may give the same id to a later finger.
pub id: i32,
/// Milliseconds on the platform's monotonic input clock.
pub time: u32,
pub phase: TouchPhase,
}
#[ derive( Debug, Clone, Copy, PartialEq ) ]
pub( crate ) enum TouchPhase
{
/// The finger landed on `focus`, at `position` in its logical coordinates.
Down
{
focus: SurfaceFocus,
position: ( f64, f64 ),
},
/// In logical coordinates of the surface the finger landed on.
Motion
{
position: ( f64, f64 ),
},
/// The finger lifted where its last motion left it.
Up,
}
#[ cfg( test ) ]
mod tests
{
use super::*;
fn key( text: Option<&str> ) -> KeyInput
{
KeyInput { keysym: Keysym::a, keycode: 38, text: text.map( String::from ) }
}
#[ test ]
fn a_pressed_key_reports_what_it_types()
{
let raw = key( Some( "a" ) ).raw( true, false, false, true );
assert_eq!( raw.keysym, Keysym::a );
assert_eq!( raw.keycode, 38 );
assert!( raw.pressed && raw.alt && !raw.ctrl && !raw.shift );
assert_eq!( raw.text.as_deref(), Some( "a" ) );
}
#[ test ]
fn a_released_key_types_nothing()
{
let raw = key( Some( "a" ) ).raw( false, false, false, false );
assert!( !raw.pressed );
assert_eq!( raw.text, None );
}
#[ test ]
fn empty_text_is_no_text()
{
assert_eq!( key( Some( "" ) ).raw( true, false, false, false ).text, None );
assert_eq!( key( None ).raw( true, false, false, false ).text, None );
}
#[ test ]
fn a_synthetic_key_has_no_physical_code()
{
let key = KeyInput::synthetic( Keysym::Left, None );
assert_eq!( key.keycode, 0 );
assert_eq!( key.raw( true, true, false, false ).keysym, Keysym::Left );
}
#[ test ]
fn only_the_three_main_buttons_reach_the_app()
{
assert_eq!( PointerButton::Left.mouse_button(), Some( MouseButton::Left ) );
assert_eq!( PointerButton::Right.mouse_button(), Some( MouseButton::Right ) );
assert_eq!( PointerButton::Middle.mouse_button(), Some( MouseButton::Middle ) );
assert_eq!( PointerButton::Back.mouse_button(), None );
assert_eq!( PointerButton::Other.mouse_button(), None );
}
#[ test ]
fn a_wheel_scrolls_further_than_a_touchpad()
{
assert_eq!( AxisSource::Wheel.scroll_factor(), 10.0 );
assert_eq!( AxisSource::Continuous.scroll_factor(), 1.0 );
assert_eq!( AxisSource::Other.scroll_factor(), 1.0 );
}
}