ribir_core/context/
build_ctx.rs

1use std::{
2  cell::{OnceCell, Ref, RefCell},
3  rc::Rc,
4};
5
6use ribir_algo::Sc;
7
8use crate::{
9  prelude::*,
10  widget::widget_id::new_node,
11  window::{DelayEvent, WindowId},
12};
13
14/// A context provide during build the widget tree.
15pub struct BuildCtx<'a> {
16  pub(crate) themes: OnceCell<Vec<Sc<Theme>>>,
17  /// The widget which this `BuildCtx` is created from. It's not means this
18  /// is the parent of the widget which is builded by this `BuildCtx`.
19  ctx_from: Option<WidgetId>,
20  pub(crate) tree: &'a RefCell<WidgetTree>,
21}
22
23/// A handle of `BuildCtx` that you can store it and access the `BuildCtx` later
24/// in anywhere.
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26pub struct BuildCtxHandle {
27  ctx_from: Option<WidgetId>,
28  wnd_id: WindowId,
29}
30
31impl<'a> BuildCtx<'a> {
32  /// Return the window of this context is created from.
33  pub fn window(&self) -> Rc<Window> { self.tree.borrow().window() }
34
35  /// Get the widget which this `BuildCtx` is created from.
36  pub fn ctx_from(&self) -> WidgetId {
37    self
38      .ctx_from
39      .unwrap_or_else(|| self.tree.borrow().root())
40  }
41
42  /// Create a handle of this `BuildCtx` which support `Clone`, `Copy` and
43  /// convert back to this `BuildCtx`. This let you can store the `BuildCtx`.
44  pub fn handle(&self) -> BuildCtxHandle {
45    BuildCtxHandle { wnd_id: self.window().id(), ctx_from: self.ctx_from }
46  }
47
48  #[inline]
49  pub(crate) fn new(from: Option<WidgetId>, tree: &'a RefCell<WidgetTree>) -> Self {
50    Self { themes: OnceCell::new(), ctx_from: from, tree }
51  }
52
53  pub(crate) fn new_with_data(
54    from: Option<WidgetId>, tree: &'a RefCell<WidgetTree>, data: Vec<Sc<Theme>>,
55  ) -> Self {
56    let themes: OnceCell<Vec<Sc<Theme>>> = OnceCell::new();
57    // Safety: we just create the `OnceCell` and it's empty.
58    unsafe { themes.set(data).unwrap_unchecked() };
59
60    Self { themes, ctx_from: from, tree }
61  }
62
63  pub(crate) fn find_cfg<T>(&self, f: impl Fn(&Theme) -> Option<&T>) -> Option<&T> {
64    for t in self.themes().iter() {
65      let v = f(t);
66      if v.is_some() {
67        return v;
68      } else if matches!(t.deref(), Theme::Full(_)) {
69        return None;
70      }
71    }
72    f(AppCtx::app_theme())
73  }
74
75  /// Get the widget back of `id`, panic if not exist.
76  pub(crate) fn assert_get(&self, id: WidgetId) -> Ref<dyn Render> {
77    Ref::map(self.tree.borrow(), |tree| id.assert_get(&tree.arena))
78  }
79
80  pub(crate) fn alloc_widget(&self, widget: Box<dyn Render>) -> WidgetId {
81    new_node(&mut self.tree.borrow_mut().arena, widget)
82  }
83
84  pub(crate) fn append_child(&self, parent: WidgetId, child: Widget) {
85    parent.append(child.consume(), &mut self.tree.borrow_mut().arena);
86  }
87
88  /// Insert `next` after `prev`
89  pub(crate) fn insert_after(&self, prev: WidgetId, next: WidgetId) {
90    prev.insert_after(next, &mut self.tree.borrow_mut().arena);
91  }
92
93  /// After insert new subtree to the widget tree, call this to watch the
94  /// subtree and fire mount events.
95  pub(crate) fn on_subtree_mounted(&self, id: WidgetId) {
96    id.descendants(&self.tree.borrow().arena)
97      .for_each(|w| self.on_widget_mounted(w));
98    self.tree.borrow_mut().mark_dirty(id);
99  }
100
101  /// After insert new widget to the widget tree, call this to watch the widget
102  /// and fire mount events.
103  pub(crate) fn on_widget_mounted(&self, id: WidgetId) {
104    self
105      .window()
106      .add_delay_event(DelayEvent::Mounted(id));
107  }
108
109  /// Dispose the whole subtree of `id`, include `id` itself.
110  pub(crate) fn dispose_subtree(&self, id: WidgetId) {
111    id.dispose_subtree(&mut self.tree.borrow_mut());
112  }
113
114  pub(crate) fn mark_dirty(&self, id: WidgetId) { self.tree.borrow_mut().mark_dirty(id); }
115
116  pub(crate) fn themes(&self) -> &Vec<Sc<Theme>> {
117    self.themes.get_or_init(|| {
118      let mut themes = vec![];
119      let Some(p) = self.ctx_from else {
120        return themes;
121      };
122
123      let arena = &self.tree.borrow().arena;
124      p.ancestors(arena).any(|p| {
125        p.assert_get(arena)
126          .query_type_inside_first(|t: &Sc<Theme>| {
127            themes.push(t.clone());
128            matches!(t.deref(), Theme::Inherit(_))
129          });
130        matches!(themes.last().map(Sc::deref), Some(Theme::Full(_)))
131      });
132      themes
133    })
134  }
135}
136
137impl BuildCtxHandle {
138  /// Acquires a reference to the `BuildCtx` in this handle, maybe not exist if
139  /// the window is closed or widget is removed.
140  pub fn with_ctx<R>(self, f: impl FnOnce(&BuildCtx) -> R) -> Option<R> {
141    AppCtx::get_window(self.wnd_id).map(|wnd| {
142      let mut ctx = BuildCtx::new(self.ctx_from, &wnd.widget_tree);
143      f(&mut ctx)
144    })
145  }
146}
147
148#[cfg(test)]
149mod tests {
150  use super::*;
151  use crate::{reset_test_env, test_helper::*};
152
153  #[test]
154  fn themes() {
155    reset_test_env!();
156
157    let themes: Stateful<Vec<Sc<Theme>>> = Stateful::new(vec![]);
158    let c_themes = themes.clone_writer();
159
160    let light_dark = fn_widget! {
161      let light_palette = Palette {
162        brightness: Brightness::Light,
163        ..Default::default()
164      };
165      @ThemeWidget {
166        theme: Sc::new(Theme::Inherit(InheritTheme {
167          palette: Some(Rc::new(light_palette)),
168          ..<_>::default()
169        })),
170        @ {
171          Box::new(fn_widget!{
172            let c_themes = c_themes.clone_writer();
173            let dark_palette = Palette {
174              brightness: Brightness::Dark,
175              ..Default::default()
176            };
177            @MockBox {
178              size: INFINITY_SIZE,
179              @ThemeWidget {
180                theme: Sc::new(Theme::Inherit(InheritTheme {
181                  palette: Some(Rc::new(dark_palette)),
182                  ..<_>::default()
183                })),
184                @ {
185                  Box::new(fn_widget!{
186                    @MockBox {
187                      size: ZERO_SIZE,
188                      @ {
189                        Clone::clone_from(&mut *$c_themes.write(), ctx!().themes());
190                        Void.build(ctx!())
191                      }
192                    }
193                  })
194                }
195              }
196            }
197          })
198        }
199      }
200    };
201
202    let wnd = TestWindow::new(light_dark);
203    wnd.layout();
204    let themes = themes.read();
205    assert_eq!(themes.len(), 2);
206    let mut iter = themes.iter().filter_map(|t| match t.deref() {
207      Theme::Full(t) => Some(t.palette.brightness),
208      Theme::Inherit(i) => i
209        .palette
210        .as_ref()
211        .map(|palette| palette.brightness),
212    });
213
214    assert_eq!(iter.next(), Some(Brightness::Light));
215    assert_eq!(iter.next(), Some(Brightness::Dark));
216  }
217}