Skip to main content

cranpose_platform_android/
lib.rs

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