cranpose_platform_android/lib.rs
1#![doc = include_str!("../README.md")]
2
3use cranpose_ui_graphics::{Point, Rect};
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 /// Converts a logical rectangle into the content-relative physical pixels
79 /// Android views use, as `[left, top, width, height]`: the inverse of
80 /// [`Self::pointer_position`], widened to whole pixels.
81 pub fn physical_rect(&self, rect: Rect) -> [i32; 4] {
82 let scale = self.scale_factor;
83 let left = (f64::from(rect.x) * scale - self.input_surface_offset_x_px).floor();
84 let top = (f64::from(rect.y) * scale - self.input_surface_offset_y_px).floor();
85 let right =
86 (f64::from(rect.x + rect.width) * scale - self.input_surface_offset_x_px).ceil();
87 let bottom =
88 (f64::from(rect.y + rect.height) * scale - self.input_surface_offset_y_px).ceil();
89 [
90 left as i32,
91 top as i32,
92 (right - left) as i32,
93 (bottom - top) as i32,
94 ]
95 }
96}
97
98#[cfg(test)]
99#[path = "tests/android_tests.rs"]
100mod tests;