GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
src/widget/embedded.rs (6K)
1 //! `Embedded<W>` — a child widget a composite holds (a tree list's search box, a paginator's
2 //! strip), the context's like every other widget once the composite is.
3 //!
4 //! A composite is built before any context exists (`TreeList::new()`), so its children start
5 //! out held by value. When the composite is inserted into a [`UiContext`], the context calls
6 //! its [`Layout::register_embedded_children`](crate::widget::Layout::register_embedded_children)
7 //! hook, which [`attach`](Embedded::attach)es each child: it moves into the context as an
8 //! entry of its own and the cell keeps its [`Handle`]. From then on the composite reaches it
9 //! through the context it is handed (an event's, a tick's, `paint_ui`'s), while the composite
10 //! itself is out on loan and the child is not — so neither can be reached twice. When the
11 //! composite is removed, [`Layout::release_embedded_children`](crate::widget::Layout::release_embedded_children)
12 //! [`detach`](Embedded::detach)es them, and the composite comes back whole, its children in it.
13 //! See `docs/rfc-owning-registry.md`, phase 4.
14
15 use super::{Handle, WidgetHost, WidgetId};
16 use crate::context::UiContext;
17
18 enum Slot<W> {
19 /// Held here: the composite is not in a context (yet, or any more).
20 Here(Box<W>),
21 /// In the context, by handle.
22 There(Handle<W>),
23 }
24
25 pub struct Embedded<W: WidgetHost + 'static> {
26 id: WidgetId,
27 slot: Slot<W>,
28 }
29
30 impl<W: WidgetHost + 'static> Embedded<W> {
31 /// A child held by value until its composite is inserted.
32 pub fn new(widget: W) -> Self {
33 Embedded { id: widget.base().id(), slot: Slot::Here(Box::new(widget)) }
34 }
35
36 /// The child's id: the same before and after it moves into the context.
37 pub fn id(&self) -> WidgetId {
38 self.id
39 }
40
41 /// Its handle, once it is in a context.
42 pub fn handle(&self) -> Option<Handle<W>> {
43 match self.slot {
44 Slot::There(h) => Some(h),
45 Slot::Here(_) => None,
46 }
47 }
48
49 pub fn is_attached(&self) -> bool {
50 matches!(self.slot, Slot::There(_))
51 }
52
53 /// Move the child into `ctx` (nothing if it is there already).
54 pub fn attach(&mut self, ctx: &mut UiContext) {
55 if let Slot::Here(_) = self.slot {
56 let Slot::Here(w) = std::mem::replace(&mut self.slot, Slot::There(Handle::none())) else { unreachable!() };
57 self.slot = Slot::There(ctx.insert(*w));
58 }
59 }
60
61 /// Take the child back out of `ctx` (nothing if it is held here). A child the context no
62 /// longer has, or has out on loan, stays named by its handle.
63 pub fn detach(&mut self, ctx: &mut UiContext) {
64 if let Slot::There(h) = self.slot {
65 if let Some(w) = ctx.remove(h) {
66 self.slot = Slot::Here(Box::new(w));
67 }
68 }
69 }
70
71 /// The child, wherever it is.
72 ///
73 /// # Panics
74 /// If it is in a context that no longer has it, or has it out on loan.
75 pub fn get<'a>(&'a self, ctx: &'a UiContext) -> &'a W {
76 match &self.slot {
77 Slot::Here(w) => w,
78 Slot::There(h) => &ctx[*h],
79 }
80 }
81
82 /// [`get`](Self::get), mutably.
83 pub fn get_mut<'a>(&'a mut self, ctx: &'a mut UiContext) -> &'a mut W {
84 match &mut self.slot {
85 Slot::Here(w) => w,
86 Slot::There(h) => &mut ctx[*h],
87 }
88 }
89
90 /// The child while it is held here (a composite outside any context, a test).
91 pub fn here(&self) -> Option<&W> {
92 match &self.slot {
93 Slot::Here(w) => Some(w),
94 Slot::There(_) => None,
95 }
96 }
97
98 /// [`here`](Self::here), mutably.
99 pub fn here_mut(&mut self) -> Option<&mut W> {
100 match &mut self.slot {
101 Slot::Here(w) => Some(w),
102 Slot::There(_) => None,
103 }
104 }
105
106 /// The child and the context together, for a call that needs both (forwarding an
107 /// event, focusing): it is attached first, and lent for the call. `None` if the context
108 /// has it out on loan already.
109 pub fn lend<R>(&mut self, ctx: &mut UiContext, f: impl FnOnce(&mut W, &mut UiContext) -> R) -> Option<R> {
110 self.attach(ctx);
111 let Slot::There(h) = self.slot else { unreachable!() };
112 ctx.lend_h(h, f)
113 }
114 }
115
116 impl<W: WidgetHost + 'static> std::fmt::Debug for Embedded<W> {
117 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118 match &self.slot {
119 Slot::Here(_) => write!(f, "Embedded::Here({:?})", self.id),
120 Slot::There(h) => write!(f, "Embedded::There({h:?})"),
121 }
122 }
123 }
124
125 #[cfg(test)]
126 mod tests {
127 use super::*;
128 use crate::widget::{Adapted, Slider};
129
130 /// A child held by value moves into the context on attach, keeps its id, is reached
131 /// through the context there, and comes back on detach.
132 #[test]
133 fn a_child_moves_in_and_back_out() {
134 let mut ctx = UiContext::new();
135 let mut child: Embedded<Adapted<Slider>> = Embedded::new(Slider::new());
136 let id = child.id();
137 child.here_mut().unwrap().set_value(0.25);
138 child.attach(&mut ctx);
139 assert!(child.is_attached() && ctx.tree.is_registered(id), "in the context under its own id");
140 assert!(child.here().is_none());
141 assert_eq!(child.get(&ctx).value(), 25);
142 child.get_mut(&mut ctx).set_value(0.5);
143 child.attach(&mut ctx);
144 assert_eq!(child.handle().map(|h| h.id()), Some(id), "a second attach changes nothing");
145 child.detach(&mut ctx);
146 assert!(!ctx.tree.is_registered(id), "out of the context again");
147 assert_eq!(child.here().map(|w| w.value()), Some(50));
148 }
149
150 /// A child lent for a call is out of the context's reach until the call returns.
151 #[test]
152 fn a_lent_child_is_not_reached_twice() {
153 let mut ctx = UiContext::new();
154 let mut child: Embedded<Adapted<Slider>> = Embedded::new(Slider::new());
155 let id = child.id();
156 let inside = child.lend(&mut ctx, |_, ctx| ctx.lend(id, |_, _| ()).is_none());
157 assert_eq!(inside, Some(true), "out on loan while lent");
158 assert!(ctx.lend(id, |_, _| ()).is_some(), "back afterwards");
159 }
160 }