Skip to main content

kobo_core/device/
touch.rs

1// SPDX-License-Identifier: MIT
2// Copyright (c) 2026 Nayeem Bin Ahsan
3//! Touch->display transform + gesture predicates.
4//!
5//! The transform uses per-device flags from `DeviceConfig`:
6//! - `touch_switch_xy`: swap X and Y axes
7//! - `touch_mirrored_x`: mirror the horizontal axis
8//! - `touch_mirrored_y`: mirror the vertical axis
9
10pub const SWIPE_MIN_DX: f32 = 80.0;
11pub const SWIPE_MAX_MS: u128 = 500;
12pub const DOUBLE_TAP_MAX_PRESS_MS: u128 = 300;
13pub const DOUBLE_TAP_WINDOW_MS: u128 = 400;
14
15/// Per-device touch configuration.
16#[derive(Clone, Copy)]
17pub struct TouchConfig {
18    pub switch_xy: bool,
19    pub mirrored_x: bool,
20    pub mirrored_y: bool,
21    pub raw_x_max: i32,
22    pub raw_y_max: i32,
23    pub screen_w: i32,
24    pub screen_h: i32,
25}
26
27/// Raw evdev -> display-space transform.
28pub fn to_display(rx: i32, ry: i32, cfg: &TouchConfig) -> (f32, f32) {
29    let (mut x, mut y) = if cfg.switch_xy { (ry, rx) } else { (rx, ry) };
30
31    if cfg.mirrored_x {
32        let max = if cfg.switch_xy {
33            cfg.raw_y_max
34        } else {
35            cfg.raw_x_max
36        };
37        x = max - x;
38    }
39    if cfg.mirrored_y {
40        let max = if cfg.switch_xy {
41            cfg.raw_x_max
42        } else {
43            cfg.raw_y_max
44        };
45        y = max - y;
46    }
47
48    (x as f32, y as f32)
49}
50
51#[allow(dead_code)]
52pub fn is_swipe(swipe_dx: f32, swipe_dy: f32, dt_ms: u128) -> bool {
53    swipe_dx.abs() > SWIPE_MIN_DX && dt_ms < SWIPE_MAX_MS && swipe_dx.abs() > swipe_dy.abs()
54}
55
56pub fn is_double_tap(dt_ms: u128, since_prev_tap_ms: u128) -> bool {
57    dt_ms < DOUBLE_TAP_MAX_PRESS_MS && since_prev_tap_ms < DOUBLE_TAP_WINDOW_MS
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    // Libra Colour cyttsp5_mt: switch_xy=true, mirrored_x=true
65    fn libra_cfg() -> TouchConfig {
66        TouchConfig {
67            switch_xy: true,
68            mirrored_x: true,
69            mirrored_y: false,
70            raw_x_max: 1447,
71            raw_y_max: 1071,
72            screen_w: 1072,
73            screen_h: 1448,
74        }
75    }
76
77    // Standard touch panel: no swap, no mirror
78    fn plain_cfg() -> TouchConfig {
79        TouchConfig {
80            switch_xy: false,
81            mirrored_x: false,
82            mirrored_y: false,
83            raw_x_max: 1071,
84            raw_y_max: 1447,
85            screen_w: 1072,
86            screen_h: 1448,
87        }
88    }
89
90    #[test]
91    fn libra_left_edge_maps_small_x() {
92        let cfg = libra_cfg();
93        let (dx, _) = to_display(736, 1035, &cfg);
94        assert!(
95            dx < 100.0,
96            "left-edge tap must map to small display_x, got {dx}"
97        );
98    }
99
100    #[test]
101    fn libra_right_edge_maps_large_x() {
102        let cfg = libra_cfg();
103        let (dx, _) = to_display(693, 36, &cfg);
104        assert!(
105            dx > 970.0,
106            "right-edge tap must map to large display_x, got {dx}"
107        );
108    }
109
110    #[test]
111    fn plain_passes_through() {
112        let cfg = plain_cfg();
113        let (dx, dy) = to_display(100, 200, &cfg);
114        assert_eq!(dx, 100.0);
115        assert_eq!(dy, 200.0);
116    }
117
118    #[test]
119    fn plain_mirrored_x() {
120        let mut cfg = plain_cfg();
121        cfg.mirrored_x = true;
122        let (dx, _) = to_display(0, 0, &cfg);
123        assert_eq!(dx, cfg.raw_x_max as f32);
124    }
125
126    #[test]
127    fn is_swipe_detects_fast_horizontal_flick() {
128        assert!(is_swipe(200.0, 5.0, 120));
129        assert!(is_swipe(-250.0, 40.0, 200));
130    }
131
132    #[test]
133    fn is_swipe_rejects_small_or_slow_or_vertical() {
134        assert!(!is_swipe(20.0, 5.0, 300));
135        assert!(!is_swipe(200.0, 5.0, 600));
136        assert!(!is_swipe(100.0, 300.0, 100));
137    }
138
139    #[test]
140    fn is_double_tap_within_window() {
141        assert!(is_double_tap(150, 200));
142        assert!(!is_double_tap(400, 200));
143        assert!(!is_double_tap(150, 500));
144    }
145}