euv_ui/component/layout/hook/
impl.rs1use super::*;
2
3impl UseEuvLayout {
7 pub fn use_resize() -> Signal<bool> {
18 let mobile_signal: Signal<bool> = App::use_signal(Router::is_mobile);
19 let timer_signal: Signal<Option<i32>> = App::use_signal(|| None);
20 let debounce_closure: Closure<dyn FnMut()> = Closure::wrap(Box::new(move || {
21 let mobile: bool = Router::is_mobile();
22 mobile_signal.set(mobile);
23 }));
24 let debounce_callback: Function = debounce_closure
25 .as_ref()
26 .unchecked_ref::<Function>()
27 .clone();
28 debounce_closure.forget();
29 let Some(timeout_window) = window() else {
30 return mobile_signal;
31 };
32 App::use_window_event("resize", move || {
33 let old_timer: Option<i32> = timer_signal.get();
34 if let Some(timer_id) = old_timer {
35 timeout_window.clear_timeout_with_handle(timer_id);
36 }
37 let new_timer: i32 = timeout_window
38 .set_timeout_with_callback_and_timeout_and_arguments_0(
39 &debounce_callback,
40 RESIZE_DEBOUNCE_MILLIS,
41 )
42 .unwrap_or_default();
43 timer_signal.set(Some(new_timer));
44 });
45 mobile_signal
46 }
47
48 pub fn use_drawer_toggle(drawer_open: Signal<bool>) -> Option<Rc<dyn Fn(Event)>> {
64 Some(Rc::new(move |_: Event| {
65 let is_open: bool = drawer_open.get();
66 if is_open {
67 Router::overlay_stack_close();
68 }
69 drawer_open.set(!is_open);
70 }))
71 }
72
73 pub fn use_safe_area_fix() {
99 Self::cache_safe_area_insets();
100 App::use_window_event("fullscreenchange", || {
101 let Some(window_value) = window() else {
102 return;
103 };
104 let Some(document_value) = window_value.document() else {
105 return;
106 };
107 let is_fullscreen: bool = document_value.fullscreen_element().is_some();
108 if is_fullscreen {
109 NATIVE_FULLSCREEN_ACTIVE.with(|flag: &Cell<bool>| flag.set(true));
110 Router::overlay_push_state();
111 } else {
112 let was_active: bool =
113 NATIVE_FULLSCREEN_ACTIVE.with(|flag: &Cell<bool>| flag.get());
114 if was_active {
115 NATIVE_FULLSCREEN_ACTIVE.with(|flag: &Cell<bool>| flag.set(false));
116 let exit_by_popstate: bool =
117 NATIVE_FULLSCREEN_EXIT_BY_POPSTATE.with(|flag: &Cell<bool>| flag.get());
118 if exit_by_popstate {
119 NATIVE_FULLSCREEN_EXIT_BY_POPSTATE
120 .with(|flag: &Cell<bool>| flag.set(false));
121 } else {
122 Router::overlay_back(None);
123 }
124 }
125 Self::apply_cached_insets();
126 }
127 });
128 App::use_window_event("webkitfullscreenchange", || {
129 Self::apply_cached_insets();
130 });
131 App::use_window_event("resize", || {
132 Self::apply_cached_insets();
133 });
134 Router::register_popstate_guard(Rc::new(|| {
135 if !NATIVE_FULLSCREEN_ACTIVE.with(|flag: &Cell<bool>| flag.get()) {
136 return false;
137 }
138 NATIVE_FULLSCREEN_EXIT_BY_POPSTATE.with(|flag: &Cell<bool>| flag.set(true));
139 let Some(window_value) = window() else {
140 return false;
141 };
142 let Some(document_value) = window_value.document() else {
143 return false;
144 };
145 document_value.exit_fullscreen();
146 true
147 }));
148 }
149
150 fn cache_safe_area_insets() {
162 let top_cached: String =
163 SAFE_AREA_INSET_TOP.with(|cell: &RefCell<String>| cell.borrow().clone());
164 if !top_cached.is_empty() {
165 return;
166 }
167 let Some(win) = window() else {
168 return;
169 };
170 let Some(document_value) = win.document() else {
171 return;
172 };
173 let Some(body) = document_value.body() else {
174 return;
175 };
176 let Ok(created_element) = document_value.create_element("div") else {
177 return;
178 };
179 let sentinel: HtmlElement = created_element.unchecked_into();
180 let _: Result<(), JsValue> = sentinel.style().set_property("position", "absolute");
181 let _: Result<(), JsValue> = sentinel.style().set_property("visibility", "hidden");
182 let _: Result<(), JsValue> = sentinel.style().set_property("pointer-events", "none");
183 let _: Result<(), JsValue> = sentinel
184 .style()
185 .set_property("padding-top", "env(safe-area-inset-top, 0px)");
186 let _: Result<(), JsValue> = sentinel
187 .style()
188 .set_property("padding-right", "env(safe-area-inset-right, 0px)");
189 let _: Result<(), JsValue> = sentinel
190 .style()
191 .set_property("padding-bottom", "env(safe-area-inset-bottom, 0px)");
192 let _: Result<(), JsValue> = sentinel
193 .style()
194 .set_property("padding-left", "env(safe-area-inset-left, 0px)");
195 let _: Result<Node, JsValue> = body.append_child(&sentinel);
196 let Some(computed) = win.get_computed_style(&sentinel).ok().flatten() else {
197 let _: Result<Node, JsValue> = body.remove_child(&sentinel);
198 return;
199 };
200 let top_value: String = computed
201 .get_property_value("padding-top")
202 .unwrap_or_default();
203 let right_value: String = computed
204 .get_property_value("padding-right")
205 .unwrap_or_default();
206 let bottom_value: String = computed
207 .get_property_value("padding-bottom")
208 .unwrap_or_default();
209 let left_value: String = computed
210 .get_property_value("padding-left")
211 .unwrap_or_default();
212 let _: Result<Node, JsValue> = body.remove_child(&sentinel);
213 if top_value.is_empty() || top_value == "0px" {
214 return;
215 }
216 SAFE_AREA_INSET_TOP.with(|cell: &RefCell<String>| *cell.borrow_mut() = top_value);
217 SAFE_AREA_INSET_RIGHT.with(|cell: &RefCell<String>| *cell.borrow_mut() = right_value);
218 SAFE_AREA_INSET_BOTTOM.with(|cell: &RefCell<String>| *cell.borrow_mut() = bottom_value);
219 SAFE_AREA_INSET_LEFT.with(|cell: &RefCell<String>| *cell.borrow_mut() = left_value);
220 }
221
222 pub fn apply_cached_insets() {
237 let top_value: String =
238 SAFE_AREA_INSET_TOP.with(|cell: &RefCell<String>| cell.borrow().clone());
239 if top_value.is_empty() {
240 return;
241 }
242 let right_value: String =
243 SAFE_AREA_INSET_RIGHT.with(|cell: &RefCell<String>| cell.borrow().clone());
244 let bottom_value: String =
245 SAFE_AREA_INSET_BOTTOM.with(|cell: &RefCell<String>| cell.borrow().clone());
246 let left_value: String =
247 SAFE_AREA_INSET_LEFT.with(|cell: &RefCell<String>| cell.borrow().clone());
248 let Some(window_value) = window() else {
249 return;
250 };
251 let Some(document_value) = window_value.document() else {
252 return;
253 };
254 let apply_to = |element: &HtmlElement| {
255 let _: Result<(), JsValue> = element
256 .style()
257 .set_property("--safe-area-inset-top", &top_value);
258 let _: Result<(), JsValue> = element
259 .style()
260 .set_property("--safe-area-inset-right", &right_value);
261 let _: Result<(), JsValue> = element
262 .style()
263 .set_property("--safe-area-inset-bottom", &bottom_value);
264 let _: Result<(), JsValue> = element
265 .style()
266 .set_property("--safe-area-inset-left", &left_value);
267 };
268 if let Some(app_root) = document_value
269 .query_selector(".c_mobile_app_root")
270 .ok()
271 .flatten()
272 .map(|element: Element| element.unchecked_into::<HtmlElement>())
273 .or_else(|| {
274 document_value
275 .query_selector(".c_app_root")
276 .ok()
277 .flatten()
278 .map(|element: Element| element.unchecked_into::<HtmlElement>())
279 })
280 {
281 apply_to(&app_root);
282 }
283 if let Some(canvas_fullscreen) = document_value
284 .query_selector(".c_canvas_container_fullscreen")
285 .ok()
286 .flatten()
287 .map(|element: Element| element.unchecked_into::<HtmlElement>())
288 {
289 apply_to(&canvas_fullscreen);
290 }
291 }
292}