Skip to main content

cranpose_platform_android/
lib.rs

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