ferroui_browser/
browser_insets_manager.rs1use crate::interop::dom_helper;
2use crate::windowing_platform::BrowserWindowingPlatform;
3use ferroui_base::media::Color;
4use ferroui_base::reactive::IDisposable;
5use ferroui_base::Thickness;
6use ferroui_controls::platform::{IInsetsManager, InsetsManagerBase, SafeAreaChangedArgs};
7use std::rc::Rc;
8
9pub(crate) trait IInsetsPage {
11 fn is_fullscreen(&self) -> bool;
12 fn set_fullscreen(&self, is_fullscreen: bool);
13 fn get_safe_area_padding(&self) -> Vec<f64>;
14}
15
16struct InsetsPage;
17
18impl IInsetsPage for InsetsPage {
19 fn is_fullscreen(&self) -> bool {
20 dom_helper::is_fullscreen(&BrowserWindowingPlatform::global_this())
21 }
22
23 fn set_fullscreen(&self, is_fullscreen: bool) {
24 dom_helper::set_fullscreen(&BrowserWindowingPlatform::global_this(), is_fullscreen);
25 }
26
27 fn get_safe_area_padding(&self) -> Vec<f64> {
28 dom_helper::get_safe_area_padding(&BrowserWindowingPlatform::global_this())
29 }
30}
31
32pub struct BrowserInsetsManager {
36 base: InsetsManagerBase,
37 page: Box<dyn IInsetsPage>,
38}
39
40impl Default for BrowserInsetsManager {
41 fn default() -> Self {
42 Self::new()
43 }
44}
45
46impl BrowserInsetsManager {
47 pub fn new() -> Self {
49 Self::with_page(Box::new(InsetsPage))
50 }
51
52 pub(crate) fn with_page(page: Box<dyn IInsetsPage>) -> Self {
53 Self { base: InsetsManagerBase::new(), page }
54 }
55
56 pub fn notify_safe_area_padding_changed(&self) {
58 self.base.on_safe_area_changed(SafeAreaChangedArgs::new(self.safe_area_padding()));
59 }
60}
61
62impl IInsetsManager for BrowserInsetsManager {
63 fn is_system_bar_visible(&self) -> Option<bool> {
66 Some(self.page.is_fullscreen())
67 }
68
69 fn set_is_system_bar_visible(&self, value: Option<bool>) {
70 self.page.set_fullscreen(!value.unwrap_or(true));
71 }
72
73 fn display_edge_to_edge_preference(&self) -> bool {
74 self.base.display_edge_to_edge_preference()
75 }
76
77 fn set_display_edge_to_edge_preference(&self, value: bool) {
78 self.base.set_display_edge_to_edge_preference(value);
79 }
80
81 fn displays_edge_to_edge(&self) -> bool {
82 self.base.displays_edge_to_edge()
83 }
84
85 fn safe_area_padding(&self) -> Thickness {
86 match self.page.get_safe_area_padding()[..] {
87 [left, top, right, bottom, ..] => Thickness::new(left, top, right, bottom),
88 _ => Thickness::default(),
89 }
90 }
91
92 fn system_bar_color(&self) -> Option<Color> {
93 self.base.system_bar_color()
94 }
95
96 fn set_system_bar_color(&self, value: Option<Color>) {
97 self.base.set_system_bar_color(value);
98 }
99
100 fn safe_area_changed(&self, handler: Rc<dyn Fn(&SafeAreaChangedArgs)>) -> Rc<dyn IDisposable> {
101 self.base.safe_area_changed(handler)
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use ferroui_base::threading::Dispatcher;
109 use std::cell::{Cell, RefCell};
110
111 #[derive(Default)]
112 struct PageState {
113 fullscreen: Cell<bool>,
114 requests: RefCell<Vec<bool>>,
115 padding: RefCell<Vec<f64>>,
116 }
117
118 struct FakePage(Rc<PageState>);
119
120 impl IInsetsPage for FakePage {
121 fn is_fullscreen(&self) -> bool {
122 self.0.fullscreen.get()
123 }
124
125 fn set_fullscreen(&self, is_fullscreen: bool) {
126 self.0.requests.borrow_mut().push(is_fullscreen);
127 }
128
129 fn get_safe_area_padding(&self) -> Vec<f64> {
130 self.0.padding.borrow().clone()
131 }
132 }
133
134 fn manager() -> (BrowserInsetsManager, Rc<PageState>) {
135 let state = Rc::new(PageState::default());
136 (BrowserInsetsManager::with_page(Box::new(FakePage(state.clone()))), state)
137 }
138
139 #[test]
140 fn the_system_bar_visibility_is_the_full_screen_of_the_page() {
141 let (manager, state) = manager();
142 assert_eq!(Some(false), manager.is_system_bar_visible());
143 state.fullscreen.set(true);
144 assert_eq!(Some(true), manager.is_system_bar_visible());
145 }
146
147 #[test]
148 fn hiding_the_system_bars_requests_the_full_screen() {
149 let (manager, state) = manager();
150 manager.set_is_system_bar_visible(Some(false));
151 manager.set_is_system_bar_visible(Some(true));
152 manager.set_is_system_bar_visible(None);
153 assert_eq!(vec![true, false, false], *state.requests.borrow());
154 }
155
156 #[test]
157 fn the_safe_area_is_read_as_left_top_right_bottom() {
158 let (manager, state) = manager();
159 *state.padding.borrow_mut() = vec![1.0, 2.0, 3.0, 4.0];
160 assert_eq!(Thickness::new(1.0, 2.0, 3.0, 4.0), manager.safe_area_padding());
161
162 state.padding.borrow_mut().clear();
163 assert_eq!(Thickness::default(), manager.safe_area_padding());
164 }
165
166 #[test]
167 fn the_edge_to_edge_preference_and_the_bar_color_are_kept() {
168 let (manager, _) = manager();
169 assert!(!manager.display_edge_to_edge_preference());
170 manager.set_display_edge_to_edge_preference(true);
171 assert!(manager.display_edge_to_edge_preference());
172 assert!(manager.displays_edge_to_edge());
173
174 assert_eq!(None, manager.system_bar_color());
175 manager.set_system_bar_color(Some(Color::from_rgb(1, 2, 3)));
176 assert_eq!(Some(Color::from_rgb(1, 2, 3)), manager.system_bar_color());
177 }
178
179 #[test]
180 fn a_change_of_the_safe_area_is_reported_with_the_current_padding() {
181 let _scope = Dispatcher::unit_test_scope();
182 let (manager, state) = manager();
183 *state.padding.borrow_mut() = vec![0.0, 10.0, 0.0, 20.0];
184 let reported = Rc::new(RefCell::new(Vec::new()));
185 let _subscription = manager.safe_area_changed({
186 let reported = reported.clone();
187 Rc::new(move |args: &SafeAreaChangedArgs| reported.borrow_mut().push(args.safe_area_padding()))
188 });
189
190 manager.notify_safe_area_padding_changed();
191
192 assert_eq!(vec![Thickness::new(0.0, 10.0, 0.0, 20.0)], *reported.borrow());
193 }
194}