Skip to main content

cranpose_platform_android/
lib.rs

1use cranpose_ui_graphics::Point;
2
3/// Platform abstraction for Android.
4///
5/// This type manages platform-specific conversions (e.g., density, pointer coordinates)
6/// and provides a bridge between Android's event system and Compose's logical coordinate space.
7#[derive(Debug, Clone)]
8pub struct AndroidPlatform {
9    scale_factor: f64,
10    input_surface_offset_x_px: f64,
11    input_surface_offset_y_px: f64,
12}
13
14impl Default for AndroidPlatform {
15    fn default() -> Self {
16        Self {
17            scale_factor: 1.0,
18            input_surface_offset_x_px: 0.0,
19            input_surface_offset_y_px: 0.0,
20        }
21    }
22}
23
24impl AndroidPlatform {
25    /// Creates a new Android platform with default configuration.
26    pub fn new() -> Self {
27        Self::default()
28    }
29
30    /// Updates the platform's scale factor.
31    ///
32    /// This should be called when the device density changes.
33    pub fn set_scale_factor(&mut self, scale_factor: f64) {
34        self.scale_factor = scale_factor;
35    }
36
37    /// Updates the native-surface offset for Android input coordinates.
38    ///
39    /// Android `MotionEvent` positions are relative to the activity content
40    /// area, while the renderer draws into the full `ANativeWindow`. Freeform
41    /// and desktop window modes can inflate that native surface around the
42    /// content area for shadows/decor, so input must be shifted into native
43    /// surface coordinates before density conversion.
44    pub fn set_input_surface_offset_px(&mut self, x_px: f64, y_px: f64) {
45        self.input_surface_offset_x_px = x_px;
46        self.input_surface_offset_y_px = y_px;
47    }
48
49    /// Returns the configured native-surface input offset in physical pixels.
50    pub fn input_surface_offset_px(&self) -> (f64, f64) {
51        (
52            self.input_surface_offset_x_px,
53            self.input_surface_offset_y_px,
54        )
55    }
56
57    /// Converts a physical Android pointer position into logical coordinates.
58    ///
59    /// Android provides pointer positions in content-relative physical pixels;
60    /// this method first shifts them into the native surface coordinate space,
61    /// then scales them back to logical pixels using the platform's current
62    /// scale factor.
63    pub fn pointer_position(&self, physical_x: f64, physical_y: f64) -> Point {
64        let scale = self.scale_factor;
65        Point {
66            x: ((physical_x + self.input_surface_offset_x_px) / scale) as f32,
67            y: ((physical_y + self.input_surface_offset_y_px) / scale) as f32,
68        }
69    }
70
71    /// Returns the current scale factor (density).
72    pub fn scale_factor(&self) -> f32 {
73        self.scale_factor as f32
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::AndroidPlatform;
80
81    #[test]
82    fn default_platform_uses_mdpi_and_zero_input_offset() {
83        let platform = AndroidPlatform::new();
84
85        assert_eq!(platform.scale_factor(), 1.0);
86        assert_eq!(platform.input_surface_offset_px(), (0.0, 0.0));
87        assert_eq!(platform.pointer_position(12.0, 34.0).x, 12.0);
88        assert_eq!(platform.pointer_position(12.0, 34.0).y, 34.0);
89    }
90
91    #[test]
92    fn pointer_position_applies_density() {
93        let mut platform = AndroidPlatform::new();
94        platform.set_scale_factor(2.0);
95
96        let logical = platform.pointer_position(80.0, 120.0);
97
98        assert_eq!(platform.scale_factor(), 2.0);
99        assert_eq!(logical.x, 40.0);
100        assert_eq!(logical.y, 60.0);
101    }
102
103    #[test]
104    fn pointer_position_applies_surface_offset_before_density() {
105        let mut platform = AndroidPlatform::new();
106        platform.set_scale_factor(1.5);
107        platform.set_input_surface_offset_px(39.0, 21.0);
108
109        let logical = platform.pointer_position(111.0, 129.0);
110
111        assert_eq!(platform.input_surface_offset_px(), (39.0, 21.0));
112        assert_eq!(logical.x, 100.0);
113        assert_eq!(logical.y, 100.0);
114    }
115}