ui/form.rs
1// form -- the kind of device the app runs on: a desktop, a tablet or a phone
2//
3// form_factor() -> FF_DESKTOP / FF_TABLET / FF_PHONE
4// mobile() -> bool a tablet or a phone (form_factor() != FF_DESKTOP)
5//
6// The platform decides, never the window's size alone: Windows, Linux, macOS and the browser are
7// FF_DESKTOP however small a window is dragged (a narrow desktop window keeps the desktop's
8// behaviour); Android and iOS are FF_TABLET, and FF_PHONE where the main window's shorter side is
9// below 600 DIP (Android's own line between a phone and a tablet, `sw600dp`; a tablet's app in a
10// narrow split screen is laid out like a phone's then). The shorter side keeps a phone a phone
11// when it is turned.
12//
13// jfgui_FORM=desktop|tablet|phone decides instead (any platform: tests, and a look at the phone's
14// layout on the desktop; scripts: `system env jfgui_FORM phone`).
15//
16// What the library does by itself on a phone or a tablet (only there):
17// the title bar has no caption buttons (chrome.rs: the system owns the app's window);
18// a menu bar that does not fit its row becomes one menu button (popup.rs, menubar);
19// the status bar's items roll sideways with the finger when the bar is too narrow (chrome.rs);
20// windows and content dialogs keep inside the screen, what does not fit rolls (win.rs, chrome.rs);
21// Android and iOS paint the system's bars in the colours at the app's edges (the hosts).
22// Apps ask form_factor() for what only they know: which panels a phone shows, which a tablet.
23
24use super::consts::*;
25use super::Ui;
26#[cfg(target_os = "android")]
27use gfx::Surface;
28
29/// a tablet's shorter side is at least this many DIP (Android's sw600dp)
30const TABLET_DIP: i32 = 600;
31
32impl Ui {
33 /// ui_form_factor: FF_DESKTOP, FF_TABLET or FF_PHONE (the platform's; jfgui_FORM decides
34 /// instead). A desktop window stays FF_DESKTOP however narrow it is.
35 pub fn form_factor(&self) -> u32 {
36 self.form
37 }
38
39 /// ui_mobile: the app runs on a phone or a tablet (form_factor() != FF_DESKTOP)
40 pub fn mobile(&self) -> bool {
41 self.form != FF_DESKTOP
42 }
43
44 // form_update(w, h): the main window's pass begins (w x h px): the form factor of this frame
45 pub(crate) fn form_update(&mut self, w: i32, h: i32) {
46 let side = self.px2dip(w.min(h));
47 let mobile_os = cfg!(any(target_os = "android", target_os = "ios"));
48 self.form = match plat::env("jfgui_FORM").as_deref() {
49 Some("phone") => FF_PHONE,
50 Some("tablet") => FF_TABLET,
51 Some("desktop") => FF_DESKTOP,
52 _ if !mobile_os => FF_DESKTOP,
53 _ if side > 0 && side < TABLET_DIP => FF_PHONE,
54 _ => FF_TABLET,
55 };
56 }
57}
58
59// (Android) edge_colors(fb) -> the colours (0xRRGGBB) along the frame's top, bottom, left and right
60// edge: on each the most frequent of 9 samples (a widget touching the edge somewhere does not
61// count); the host paints the system's bars in them
62#[cfg(target_os = "android")]
63pub(crate) fn edge_colors(fb: &Surface) -> [u32; 4] {
64 let (w, h) = (fb.w, fb.h);
65 if w <= 0 || h <= 0 || fb.px.len() < (fb.stride * (h - 1) + w * 4) as usize {
66 return [0; 4];
67 }
68 let at = |x: i32, y: i32| {
69 let o = (y * fb.stride + x * 4) as usize;
70 (fb.px[o + 2] as u32) << 16 | (fb.px[o + 1] as u32) << 8 | fb.px[o] as u32
71 };
72 let edge = |f: &dyn Fn(i32) -> u32, len: i32| {
73 let s: [u32; 9] = core::array::from_fn(|k| f(((2 * k as i32 + 1) * len / 18).min(len - 1)));
74 let mut best = (s[0], 0);
75 for &c in &s {
76 let n = s.iter().filter(|&&d| d == c).count();
77 if n > best.1 {
78 best = (c, n);
79 }
80 }
81 best.0
82 };
83 [
84 edge(&|x| at(x, 0), w),
85 edge(&|x| at(x, h - 1), w),
86 edge(&|y| at(0, y), h),
87 edge(&|y| at(w - 1, y), h),
88 ]
89}
90