git.lucas.co / cce-ui
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 }