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;