1use ferroui_base::{PixelPoint, PixelRect};
9use ferroui_controls::platform::{
10 ITopLevelImpl, PlatformHandle, PlatformScreen, Screen, ScreenOrientation, ScreensBase, ScreensBaseImpl,
11 ScreensBaseImplExt,
12};
13use std::cell::RefCell;
14use std::rc::{Rc, Weak};
15
16const DEFAULT_DISPLAY: i32 = 0;
18
19pub(crate) mod surface_orientation {
21 pub const ROTATION_0: i32 = 0;
22 pub const ROTATION_90: i32 = 1;
23 pub const ROTATION_180: i32 = 2;
24 pub const ROTATION_270: i32 = 3;
25}
26
27pub(crate) mod android_orientation {
29 pub const PORTRAIT: i32 = 1;
30 pub const LANDSCAPE: i32 = 2;
31 pub const SQUARE: i32 = 3;
32}
33
34#[derive(Clone, Debug, PartialEq)]
36pub(crate) struct DisplayInfo {
37 pub display_id: i32,
38 pub name: String,
39 pub rotation: i32,
41 pub has_window_metrics: bool,
44 pub bounds: PixelRect,
45 pub scaling: f64,
48 pub orientation: i32,
51}
52
53const DISPLAY_LENGTH: usize = 9;
55
56impl DisplayInfo {
57 pub fn from_arrays(values: &[f32], names: &[String]) -> Vec<DisplayInfo> {
60 values
61 .chunks_exact(DISPLAY_LENGTH)
62 .enumerate()
63 .map(|(index, display)| DisplayInfo {
64 display_id: display[0] as i32,
65 name: names.get(index).cloned().unwrap_or_default(),
66 rotation: display[1] as i32,
67 has_window_metrics: display[2] != 0.0,
68 bounds: PixelRect::new(display[3] as i32, display[4] as i32, display[5] as i32, display[6] as i32),
69 scaling: f64::from(display[7]),
70 orientation: display[8] as i32,
71 })
72 .collect()
73 }
74
75 pub(crate) fn natural_orientation(&self) -> ScreenOrientation {
77 if !self.has_window_metrics {
78 return ScreenOrientation::Portrait;
79 }
80
81 if self.orientation == android_orientation::SQUARE {
82 ScreenOrientation::None
83 } else if self.rotation == surface_orientation::ROTATION_0 || self.rotation == surface_orientation::ROTATION_180
84 {
85 if self.orientation == android_orientation::LANDSCAPE {
86 ScreenOrientation::Landscape
87 } else {
88 ScreenOrientation::Portrait
89 }
90 } else if self.orientation == android_orientation::PORTRAIT {
91 ScreenOrientation::Landscape
92 } else {
93 ScreenOrientation::Portrait
94 }
95 }
96
97 pub(crate) fn current_orientation(&self) -> ScreenOrientation {
98 use surface_orientation::{ROTATION_0, ROTATION_180, ROTATION_270, ROTATION_90};
99
100 match (self.rotation, self.natural_orientation()) {
101 (_, ScreenOrientation::None) => ScreenOrientation::None,
102 (ROTATION_0, ScreenOrientation::Landscape) => ScreenOrientation::Landscape,
103 (ROTATION_90, ScreenOrientation::Landscape) => ScreenOrientation::Portrait,
104 (ROTATION_180, ScreenOrientation::Landscape) => ScreenOrientation::LandscapeFlipped,
105 (ROTATION_270, ScreenOrientation::Landscape) => ScreenOrientation::PortraitFlipped,
106 (ROTATION_0, _) => ScreenOrientation::Portrait,
107 (ROTATION_90, _) => ScreenOrientation::Landscape,
108 (ROTATION_180, _) => ScreenOrientation::PortraitFlipped,
109 (ROTATION_270, _) => ScreenOrientation::LandscapeFlipped,
110 _ => ScreenOrientation::Portrait,
111 }
112 }
113
114 pub(crate) fn working_area(&self) -> PixelRect {
118 let (left, top, right, bottom) = (0, 0, 0, 0);
119 PixelRect::new(
120 self.bounds.x + left,
121 self.bounds.y + top,
122 self.bounds.width - (left + right),
123 self.bounds.height - (top + bottom),
124 )
125 }
126}
127
128pub(crate) trait IDisplaySource {
130 fn displays(&self) -> Vec<DisplayInfo>;
132
133 fn display_of_top_level(&self, top_level: &dyn ITopLevelImpl) -> Option<i32>;
135}
136
137pub struct AndroidScreen {
139 base: PlatformScreen,
140 display_id: i32,
141}
142
143impl AndroidScreen {
144 fn new(display_id: i32) -> Self {
145 Self {
146 base: PlatformScreen::new(Rc::new(PlatformHandle::new(display_id as isize, Some("DisplayId")))),
147 display_id,
148 }
149 }
150
151 pub fn display_id(&self) -> i32 {
153 self.display_id
154 }
155
156 pub(crate) fn refresh(&self, display: &DisplayInfo) {
157 self.base.set_display_name(Some(display.name.clone()));
158 self.base.set_is_primary(display.display_id == DEFAULT_DISPLAY);
159
160 if display.has_window_metrics {
161 self.base.set_bounds(display.bounds);
162 self.base.set_working_area(display.working_area());
163 if display.scaling > 0.0 {
165 self.base.set_scaling(display.scaling);
166 }
167 } else {
168 self.base.set_scaling(display.scaling);
169 self.base.set_bounds(display.bounds);
170 self.base.set_working_area(display.bounds);
171 }
172
173 self.base.set_current_orientation(display.current_orientation());
174 }
175}
176
177impl AsRef<PlatformScreen> for AndroidScreen {
178 fn as_ref(&self) -> &PlatformScreen {
179 &self.base
180 }
181}
182
183pub struct AndroidScreens {
185 base: ScreensBase<i32, AndroidScreen>,
186 weak_self: Weak<AndroidScreens>,
187 source: Box<dyn IDisplaySource>,
188 displays: RefCell<Vec<DisplayInfo>>,
191}
192
193impl AndroidScreens {
194 pub(crate) fn with_source(source: Box<dyn IDisplaySource>) -> Rc<AndroidScreens> {
195 Rc::new_cyclic(|weak_self| AndroidScreens {
196 base: ScreensBase::new(),
197 weak_self: weak_self.clone(),
198 source,
199 displays: RefCell::new(Vec::new()),
200 })
201 }
202
203 pub(crate) fn on_changed(&self) {
205 if let Some(this) = self.weak_self.upgrade() {
206 ScreensBaseImplExt::on_changed(&this);
207 }
208 }
209}
210
211impl ScreensBaseImpl for AndroidScreens {
212 type Key = i32;
213 type Screen = AndroidScreen;
214
215 fn screens_base(&self) -> &ScreensBase<i32, AndroidScreen> {
216 &self.base
217 }
218
219 fn get_all_screen_keys(&self) -> Vec<i32> {
220 let displays = self.source.displays();
221 let keys = displays.iter().map(|display| display.display_id).collect();
222 *self.displays.borrow_mut() = displays;
223 keys
224 }
225
226 fn create_screen_from_key(&self, key: &i32) -> Rc<AndroidScreen> {
227 Rc::new(AndroidScreen::new(*key))
228 }
229
230 fn screen_changed(&self, screen: &Rc<AndroidScreen>) {
231 let display = self.displays.borrow().iter().find(|display| display.display_id == screen.display_id).cloned();
232 if let Some(display) = display {
233 screen.refresh(&display);
234 }
235 }
236
237 fn screen_from_top_level_core(&self, top_level: &dyn ITopLevelImpl) -> Option<Rc<Screen>> {
238 let display = self.source.display_of_top_level(top_level)?;
239 self.try_get_screen(&display).map(|screen| screen.base.screen().clone())
240 }
241
242 fn screen_from_point_core(&self, _point: PixelPoint) -> Option<Rc<Screen>> {
243 None
244 }
245
246 fn screen_from_rect_core(&self, _rect: PixelRect) -> Option<Rc<Screen>> {
247 None
248 }
249}
250
251#[cfg(target_os = "android")]
252mod imp {
253 use super::{AndroidScreens, DisplayInfo, IDisplaySource};
254 use crate::interop::java::{
255 call_static_object, call_static_void, float_array_of, string_array_of, JavaClass, JavaObject, JavaValue,
256 };
257 use crate::interop::natives::{next_handle, PLATFORM_HELPER};
258 use crate::platform::skia_platform::TopLevelImpl;
259 use ferroui_controls::platform::ITopLevelImpl;
260 use std::cell::RefCell;
261 use std::collections::HashMap;
262 use std::rc::{Rc, Weak};
263
264 ferroui_base::ferro_thread_local! {
265 static SCREENS: RefCell<HashMap<i64, Weak<AndroidScreens>>> = RefCell::new(HashMap::new());
266 }
267
268 struct ContextDisplaySource {
271 context: JavaObject,
272 handle: i64,
273 listener: Option<JavaObject>,
274 }
275
276 impl IDisplaySource for ContextDisplaySource {
277 fn displays(&self) -> Vec<DisplayInfo> {
278 let helper = JavaClass::find(PLATFORM_HELPER);
279 let context = [JavaValue::Object(Some(&self.context))];
280 let values = call_static_object(&helper, "getDisplays", "(Landroid/content/Context;)[F", &context);
281 let names = call_static_object(
282 &helper,
283 "getDisplayNames",
284 "(Landroid/content/Context;)[Ljava/lang/String;",
285 &context,
286 );
287 match (values, names) {
288 (Some(values), Some(names)) => {
289 DisplayInfo::from_arrays(&float_array_of(&values), &string_array_of(&names))
290 }
291 _ => Vec::new(),
292 }
293 }
294
295 fn display_of_top_level(&self, top_level: &dyn ITopLevelImpl) -> Option<i32> {
296 top_level.as_any().downcast_ref::<TopLevelImpl>()?.display_id()
297 }
298 }
299
300 impl Drop for ContextDisplaySource {
301 fn drop(&mut self) {
302 SCREENS.with(|screens| screens.borrow_mut().remove(&self.handle));
303 if let Some(listener) = &self.listener {
304 call_static_void(
305 &JavaClass::find(PLATFORM_HELPER),
306 "unregisterDisplayListener",
307 "(Landroid/content/Context;Ljava/lang/Object;)V",
308 &[JavaValue::Object(Some(&self.context)), JavaValue::Object(Some(listener))],
309 );
310 }
311 }
312 }
313
314 impl AndroidScreens {
315 pub(crate) fn new(context: &JavaObject) -> Rc<AndroidScreens> {
318 let handle = next_handle();
319 let listener = call_static_object(
320 &JavaClass::find(PLATFORM_HELPER),
321 "registerDisplayListener",
322 "(Landroid/content/Context;J)Ljava/lang/Object;",
323 &[JavaValue::Object(Some(context)), JavaValue::Long(handle)],
324 )
325 .map(|listener| listener.to_global());
326 let screens = AndroidScreens::with_source(Box::new(ContextDisplaySource {
327 context: context.clone(),
328 handle,
329 listener,
330 }));
331 SCREENS.with(|all| all.borrow_mut().insert(handle, Rc::downgrade(&screens)));
332 screens
333 }
334
335 pub(crate) fn on_displays_changed(handle: i64) {
337 let screens = SCREENS.with(|all| all.borrow().get(&handle).and_then(Weak::upgrade));
338 if let Some(screens) = screens {
339 screens.on_changed();
340 }
341 }
342 }
343}
344
345#[cfg(test)]
346mod tests {
347 use super::*;
349 use ferroui_controls::platform::IScreenImpl;
350 use std::cell::Cell;
351
352 fn display(display_id: i32, rotation: i32, orientation: i32) -> DisplayInfo {
353 DisplayInfo {
354 display_id,
355 name: format!("Display {display_id}"),
356 rotation,
357 has_window_metrics: true,
358 bounds: PixelRect::new(0, 0, 1080, 2400),
359 scaling: 2.625,
360 orientation,
361 }
362 }
363
364 struct Source {
365 displays: Rc<RefCell<Vec<DisplayInfo>>>,
366 top_level_display: Rc<Cell<Option<i32>>>,
367 }
368
369 impl IDisplaySource for Source {
370 fn displays(&self) -> Vec<DisplayInfo> {
371 self.displays.borrow().clone()
372 }
373
374 fn display_of_top_level(&self, _top_level: &dyn ITopLevelImpl) -> Option<i32> {
375 self.top_level_display.get()
376 }
377 }
378
379 #[test]
380 fn the_arrays_of_the_java_layer_are_read_per_display() {
381 let values = [
382 0.0, 1.0, 1.0, 0.0, 0.0, 2400.0, 1080.0, 2.625, 2.0, 7.0, 0.0, 0.0, 0.0, 0.0, 1920.0, 1080.0, 1.0, 0.0,
384 ];
385 let displays = DisplayInfo::from_arrays(&values, &["Built-in".to_string()]);
386
387 assert_eq!(displays.len(), 2);
388 assert_eq!(displays[0].display_id, 0);
389 assert_eq!(displays[0].name, "Built-in");
390 assert_eq!(displays[0].rotation, surface_orientation::ROTATION_90);
391 assert!(displays[0].has_window_metrics);
392 assert_eq!(displays[0].bounds, PixelRect::new(0, 0, 2400, 1080));
393 assert_eq!(displays[0].scaling, 2.625);
394 assert_eq!(displays[0].orientation, android_orientation::LANDSCAPE);
395 assert_eq!(displays[1].display_id, 7);
396 assert_eq!(displays[1].name, "");
397 assert!(!displays[1].has_window_metrics);
398 }
399
400 #[test]
401 fn a_phone_is_portrait_at_rotation_zero_and_landscape_when_turned() {
402 use android_orientation::{LANDSCAPE, PORTRAIT};
403 use surface_orientation::{ROTATION_0, ROTATION_180, ROTATION_270, ROTATION_90};
404
405 assert_eq!(display(0, ROTATION_0, PORTRAIT).current_orientation(), ScreenOrientation::Portrait);
407 assert_eq!(display(0, ROTATION_90, LANDSCAPE).current_orientation(), ScreenOrientation::Landscape);
408 assert_eq!(display(0, ROTATION_180, PORTRAIT).current_orientation(), ScreenOrientation::PortraitFlipped);
409 assert_eq!(display(0, ROTATION_270, LANDSCAPE).current_orientation(), ScreenOrientation::LandscapeFlipped);
410 }
411
412 #[test]
413 fn a_tablet_is_landscape_at_rotation_zero() {
414 use android_orientation::{LANDSCAPE, PORTRAIT};
415 use surface_orientation::{ROTATION_0, ROTATION_180, ROTATION_270, ROTATION_90};
416
417 assert_eq!(display(0, ROTATION_0, LANDSCAPE).current_orientation(), ScreenOrientation::Landscape);
418 assert_eq!(display(0, ROTATION_90, PORTRAIT).current_orientation(), ScreenOrientation::Portrait);
419 assert_eq!(display(0, ROTATION_180, LANDSCAPE).current_orientation(), ScreenOrientation::LandscapeFlipped);
420 assert_eq!(display(0, ROTATION_270, PORTRAIT).current_orientation(), ScreenOrientation::PortraitFlipped);
421 }
422
423 #[test]
424 fn a_square_display_has_no_orientation_and_real_metrics_are_portrait() {
425 assert_eq!(display(0, 1, android_orientation::SQUARE).current_orientation(), ScreenOrientation::None);
426
427 let mut old = display(0, surface_orientation::ROTATION_90, 0);
428 old.has_window_metrics = false;
429 assert_eq!(old.natural_orientation(), ScreenOrientation::Portrait);
430 assert_eq!(old.current_orientation(), ScreenOrientation::Landscape);
431 }
432
433 #[test]
434 fn the_screens_follow_the_displays_of_the_source() {
435 let _scope = ferroui_base::threading::Dispatcher::unit_test_scope();
436 let displays = Rc::new(RefCell::new(vec![display(0, 0, android_orientation::PORTRAIT)]));
437 let top_level_display = Rc::new(Cell::new(None));
438 let screens = AndroidScreens::with_source(Box::new(Source {
439 displays: displays.clone(),
440 top_level_display: top_level_display.clone(),
441 }));
442
443 let all = screens.all_screens();
444 assert_eq!(all.len(), 1);
445 assert!(all[0].is_primary());
446 assert_eq!(all[0].display_name().as_deref(), Some("Display 0"));
447 assert_eq!(all[0].bounds(), PixelRect::new(0, 0, 1080, 2400));
448 assert_eq!(all[0].working_area(), PixelRect::new(0, 0, 1080, 2400));
449 assert_eq!(all[0].scaling(), 2.625);
450 assert_eq!(all[0].current_orientation(), ScreenOrientation::Portrait);
451
452 let changed = Rc::new(Cell::new(0));
454 screens.set_changed(Some(Rc::new({
455 let changed = changed.clone();
456 move || changed.set(changed.get() + 1)
457 })));
458 {
459 let mut displays = displays.borrow_mut();
460 displays[0].rotation = surface_orientation::ROTATION_90;
461 displays[0].orientation = android_orientation::LANDSCAPE;
462 displays[0].bounds = PixelRect::new(0, 0, 2400, 1080);
463 displays.push(display(3, 0, android_orientation::LANDSCAPE));
464 }
465 screens.on_changed();
466 screens.on_changed();
467 assert_eq!(changed.get(), 0);
469 ferroui_base::threading::Dispatcher::ui_thread().run_jobs(None);
470
471 assert_eq!(changed.get(), 1);
472 let all = screens.all_screens();
473 assert_eq!(all.len(), 2);
474 assert_eq!(all[0].current_orientation(), ScreenOrientation::Landscape);
475 assert_eq!(all[0].bounds(), PixelRect::new(0, 0, 2400, 1080));
476 assert!(!all[1].is_primary());
477
478 assert!(screens.screen_from_point(PixelPoint::new(10, 10)).is_none());
479 assert!(screens.screen_from_rect(PixelRect::new(0, 0, 10, 10)).is_none());
480 }
481}