1use std::{cell::RefCell, mem::replace, rc::Rc};
2
3use ribir_macros::Query;
4
5use crate::prelude::*;
6
7#[derive(Clone)]
8pub struct OverlayStyle {
9 pub close_policy: ClosePolicy,
10 pub mask_brush: Option<Brush>,
11}
12
13bitflags! {
14 #[derive(Clone, Copy)]
15 pub struct ClosePolicy: u8 {
16 const NONE = 0b0000;
17 const ESC = 0b0001;
18 const TAP_OUTSIDE = 0b0010;
19 }
20}
21
22impl CustomStyle for OverlayStyle {
23 fn default_style(_: &BuildCtx) -> Self {
24 Self {
25 close_policy: ClosePolicy::ESC | ClosePolicy::TAP_OUTSIDE,
26 mask_brush: Some(Color::from_f32_rgba(0.3, 0.3, 0.3, 0.3).into()),
27 }
28 }
29}
30
31#[derive(Clone)]
33pub struct OverlayCloseHandle(OverlayState);
34impl OverlayCloseHandle {
35 pub fn close(&self) { self.0.close() }
36}
37
38struct OverlayData {
39 builder: Box<dyn Fn(OverlayCloseHandle) -> BoxedWidget>,
40 style: RefCell<Option<OverlayStyle>>,
41 state: OverlayState,
42}
43
44#[derive(Clone)]
45pub struct Overlay(Rc<OverlayData>);
46
47impl Overlay {
48 pub fn new<M>(widget: M) -> Self
71 where
72 M: WidgetBuilder + 'static + Clone,
73 {
74 Self(Rc::new(OverlayData {
75 builder: Box::new(move |_| widget.clone().box_it()),
76 style: RefCell::new(None),
77 state: OverlayState::default(),
78 }))
79 }
80
81 pub fn new_with_handle<O, M>(builder: M) -> Self
110 where
111 M: Fn(OverlayCloseHandle) -> O + 'static,
112 O: WidgetBuilder + 'static,
113 {
114 Self(Rc::new(OverlayData {
115 builder: Box::new(move |ctrl| builder(ctrl).box_it()),
116 style: RefCell::new(None),
117 state: OverlayState::default(),
118 }))
119 }
120
121 pub fn with_style(&self, style: OverlayStyle) { *self.0.style.borrow_mut() = Some(style); }
124
125 pub fn show(&self, wnd: Rc<Window>) {
128 if self.is_show() {
129 return;
130 }
131 let w = (self.0.builder)(self.0.state.close_handle());
132 let style = self.0.style.borrow().clone();
133 self.0.state.show(w, style, wnd);
134 }
135
136 pub fn show_map<O, F>(&self, f: F, wnd: Rc<Window>)
173 where
174 F: Fn(BoxedWidget, OverlayCloseHandle) -> O + 'static,
175 O: WidgetBuilder + 'static,
176 {
177 if self.is_show() {
178 return;
179 }
180
181 let close_handle = self.0.state.close_handle();
182 let w = f((self.0.builder)(close_handle.clone()), close_handle);
183 let style = self.0.style.borrow().clone();
184 self.0.state.show(w, style, wnd);
185 }
186
187 pub fn show_at(&self, pos: Point, wnd: Rc<Window>) {
190 if self.is_show() {
191 return;
192 }
193 self.show_map(
194 move |w, _| {
195 fn_widget! {
196 @$w { anchor: Anchor::from_point(pos) }
197 }
198 },
199 wnd,
200 );
201 }
202
203 pub fn is_show(&self) -> bool { self.0.state.is_show() }
205
206 pub fn close(&self) { self.0.state.close() }
208}
209
210enum OverlayInnerState {
211 ToShow(Instant, Rc<Window>),
212 Showing(WidgetId, Rc<Window>),
213 Hided,
214}
215
216#[derive(Clone)]
217struct OverlayState(Rc<RefCell<OverlayInnerState>>);
218impl Default for OverlayState {
219 fn default() -> Self { OverlayState(Rc::new(RefCell::new(OverlayInnerState::Hided))) }
220}
221
222impl OverlayState {
223 fn close(&self) {
224 let state = replace(&mut *self.0.borrow_mut(), OverlayInnerState::Hided);
225 if let OverlayInnerState::Showing(wid, wnd) = state {
226 let _ = AppCtx::spawn_local(async move {
227 let root = wnd.widget_tree.borrow().root();
228 wid.dispose_subtree(&mut wnd.widget_tree.borrow_mut());
229 wnd.widget_tree.borrow_mut().mark_dirty(root);
230 });
231 }
232 }
233
234 fn is_show(&self) -> bool { !matches!(*self.0.borrow(), OverlayInnerState::Hided) }
235
236 fn show(&self, w: impl WidgetBuilder + 'static, style: Option<OverlayStyle>, wnd: Rc<Window>) {
237 if self.is_show() {
238 return;
239 }
240 let this = self.clone();
241 let instant = Instant::now();
242 *this.0.borrow_mut() = OverlayInnerState::ToShow(instant, wnd);
243 let _ = AppCtx::spawn_local(async move {
244 let wnd = match (instant, &*this.0.borrow()) {
245 (instant, OverlayInnerState::ToShow(crate_at, wnd)) if &instant == crate_at => wnd.clone(),
246 _ => return,
247 };
248 let build_ctx = BuildCtx::new(None, &wnd.widget_tree);
249 let style = style.unwrap_or_else(|| OverlayStyle::of(&build_ctx));
250 let w = this.wrap_style(w, style).build(&build_ctx);
251 let wid = w.id();
252 *this.0.borrow_mut() = OverlayInnerState::Showing(wid, wnd.clone());
253 let root = wnd.widget_tree.borrow().root();
254 build_ctx.append_child(root, w);
255 build_ctx.on_subtree_mounted(wid);
256 build_ctx.mark_dirty(wid);
257 });
258 }
259
260 fn wrap_style(&self, w: impl WidgetBuilder, style: OverlayStyle) -> impl WidgetBuilder {
261 let this = self.clone();
262 fn_widget! {
263 let OverlayStyle { close_policy, mask_brush } = style;
264 let this2 = this.clone();
265 @Container {
266 size: Size::new(f32::INFINITY, f32::INFINITY),
267 background: mask_brush.unwrap_or_else(|| Color::from_u32(0).into()),
268 on_tap: move |e| {
269 if close_policy.contains(ClosePolicy::TAP_OUTSIDE)
270 && e.target() == e.current_target() {
271 this.close();
272 }
273 },
274 on_key_down: move |e| {
275 if close_policy.contains(ClosePolicy::ESC)
276 && *e.key() == VirtualKey::Named(NamedKey::Escape) {
277 this2.close();
278 }
279 },
280 @$w {}
281 }
282 }
283 }
284
285 fn close_handle(&self) -> OverlayCloseHandle { OverlayCloseHandle(self.clone()) }
286}
287
288#[derive(Query)]
289pub(crate) struct OverlayRoot {}
290
291impl Render for OverlayRoot {
292 fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size {
293 let mut size = ZERO_SIZE;
294 let mut layouter = ctx.first_child_layouter();
295 while let Some(mut l) = layouter {
296 let child_size = l.perform_widget_layout(clamp);
297 size = size.max(child_size);
298 layouter = l.into_next_sibling();
299 }
300 size
301 }
302
303 fn paint(&self, _: &mut PaintingCtx) {}
304}
305
306#[cfg(test)]
307mod tests {
308 use std::{cell::RefCell, rc::Rc};
309
310 use ribir_dev_helper::assert_layout_result_by_path;
311
312 use crate::{prelude::*, reset_test_env, test_helper::*};
313
314 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
315 #[test]
316 fn overlay() {
317 reset_test_env!();
318 let size = Size::zero();
319 let widget = fn_widget! {
320 @MockBox {
321 size,
322 @MockBox { size }
323 }
324 };
325
326 let mut wnd = TestWindow::new(widget);
327 let w_log = Rc::new(RefCell::new(vec![]));
328 let r_log = w_log.clone();
329 let overlay = Overlay::new(fn_widget! {
330 @MockBox {
331 size,
332 on_mounted: {
333 let w_log = w_log.clone();
334 move |_| { w_log.borrow_mut().push("mounted");}
335 },
336 on_disposed: move |_| { w_log.borrow_mut().push("disposed");}
337 }
338 });
339 wnd.draw_frame();
340
341 let root = wnd.widget_tree.borrow().root();
342 assert_eq!(wnd.widget_tree.borrow().count(root), 3);
343
344 overlay.show(wnd.0.clone());
345 overlay.close();
346 overlay.show_at(Point::new(50., 30.), wnd.0.clone());
347 wnd.draw_frame();
348 assert_eq!(*r_log.borrow(), &["mounted"]);
349 assert_layout_result_by_path!(wnd, {path = [1, 0, 0, 0], x == 50., y == 30.,});
352
353 overlay.close();
354 wnd.draw_frame();
355 assert_eq!(*r_log.borrow(), &["mounted", "disposed"]);
356 assert_eq!(wnd.widget_tree.borrow().count(root), 3);
357 }
358}