crabshell_ui/cx.rs
1//! [`Cx`], the context that widget code, event handlers and list views get.
2
3use std::any::{Any, TypeId};
4use std::cell::{OnceCell, RefCell};
5use std::collections::HashMap;
6use std::rc::{Rc, Weak};
7
8use crabshell_reactive::{Effect, Memo, Owner, Signal};
9
10use crate::tree::{NodeId, Tree};
11
12/// The context widget code runs in.
13///
14/// A `Cx` wraps the reactive [`Owner`] of one part of the shell: a window,
15/// a widget or one item of a keyed list. Signals, memos and effects created
16/// through it belong to that owner, so they are freed when that part of the
17/// shell goes away. Event handlers receive the `Cx` of their button, and
18/// [`children_keyed`](crate::Row::children_keyed) views receive the `Cx` of
19/// their item.
20///
21/// A context also carries the shell's services, keyed by type: the
22/// platform, and later the clock, Sway, audio and the tray. Every context
23/// made from it shares the same map, so a service provided once at the
24/// root reaches every widget and handler.
25#[derive(Clone)]
26pub struct Cx {
27 owner: Owner,
28 services: Rc<Services>,
29 /// The widget this context belongs to, if any, for
30 /// [`open_popup`](Self::open_popup). A child context shares it.
31 pub(crate) anchor: Option<Rc<Anchor>>,
32}
33
34/// Where a widget is: its window's tree and its node. Set once the node
35/// exists, which is after its context is made.
36#[derive(Default)]
37pub(crate) struct Anchor(OnceCell<(Weak<RefCell<Tree>>, NodeId)>);
38
39impl Anchor {
40 pub(crate) fn set(&self, tree: Weak<RefCell<Tree>>, node: NodeId) {
41 // A second set can't happen: each anchor is made for one node.
42 let _ = self.0.set((tree, node));
43 }
44
45 /// The tree, while the window is there, and the node.
46 pub(crate) fn get(&self) -> Option<(Rc<RefCell<Tree>>, NodeId)> {
47 let (tree, node) = self.0.get()?;
48 Some((tree.upgrade()?, *node))
49 }
50}
51
52/// The services of one shell, by type.
53#[derive(Default)]
54struct Services(RefCell<HashMap<TypeId, Rc<dyn Any>>>);
55
56impl std::fmt::Debug for Cx {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 let services = self.services.0.borrow().len();
59 f.debug_struct("Cx")
60 .field("owner", &self.owner)
61 .field("services", &services)
62 .finish()
63 }
64}
65
66impl Cx {
67 /// A context that creates everything under `owner`, with no services
68 /// yet.
69 pub fn new(owner: Owner) -> Cx {
70 Cx {
71 owner,
72 services: Rc::default(),
73 anchor: None,
74 }
75 }
76
77 /// The owner that signals, memos and effects created through this
78 /// context belong to.
79 pub fn owner(&self) -> Owner {
80 self.owner
81 }
82
83 /// A context for a part of the shell inside this one: what it creates
84 /// is freed when this context's owner is. The services are shared with
85 /// the child.
86 pub fn child(&self) -> Cx {
87 Cx {
88 owner: self.owner.child(),
89 services: Rc::clone(&self.services),
90 anchor: self.anchor.clone(),
91 }
92 }
93
94 /// Gives the context's owner a debug name, such as a widget path.
95 pub(crate) fn named(self, name: impl Into<String>) -> Cx {
96 Cx {
97 owner: self.owner.named(name),
98 ..self
99 }
100 }
101
102 /// The context of a widget's own node, at `anchor`.
103 pub(crate) fn at(self, anchor: Rc<Anchor>) -> Cx {
104 Cx {
105 anchor: Some(anchor),
106 ..self
107 }
108 }
109
110 /// Makes `service` reachable from every context of this shell, through
111 /// [`service`](Self::service), and returns it shared. A service of the
112 /// same type provided before is replaced; whoever holds it keeps it.
113 pub fn provide<T: 'static>(&self, service: T) -> Rc<T> {
114 let service = Rc::new(service);
115 let shared: Rc<dyn Any> = Rc::clone(&service) as Rc<dyn Any>;
116 let replaced = self
117 .services
118 .0
119 .borrow_mut()
120 .insert(TypeId::of::<T>(), shared);
121 // Dropped after the map is released: its `Drop` may use a context.
122 drop(replaced);
123 service
124 }
125
126 /// The service of type `T`, if one was provided.
127 pub fn service<T: 'static>(&self) -> Option<Rc<T>> {
128 let service = self.services.0.borrow().get(&TypeId::of::<T>()).cloned()?;
129 service.downcast::<T>().ok()
130 }
131
132 /// Creates a [`Signal`] holding `value`. See [`Owner::signal`].
133 #[track_caller]
134 pub fn signal<T: 'static>(&self, value: T) -> Signal<T> {
135 self.owner.signal(value)
136 }
137
138 /// Creates a [`Memo`] that derives its value with `f`. See
139 /// [`Owner::memo`].
140 #[track_caller]
141 pub fn memo<T: PartialEq + 'static>(&self, f: impl FnMut() -> T + 'static) -> Memo<T> {
142 self.owner.memo(f)
143 }
144
145 /// Creates an [`Effect`] that reruns `f` when something it read changes.
146 /// See [`Owner::effect`].
147 #[track_caller]
148 pub fn effect(&self, f: impl FnMut() + 'static) -> Effect {
149 self.owner.effect(f)
150 }
151}