Skip to main content

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, ReadSignal, 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  /// Whether the keyed item this context is in is leaving. A child context
33  /// shares it; outside such an item it is the root's signal that stays
34  /// `false`.
35  leaving: ReadSignal<bool>,
36}
37
38/// Where a widget is: its window's tree and its node. Set once the node
39/// exists, which is after its context is made.
40#[derive(Default)]
41pub(crate) struct Anchor(OnceCell<(Weak<RefCell<Tree>>, NodeId)>);
42
43impl Anchor {
44  pub(crate) fn set(&self, tree: Weak<RefCell<Tree>>, node: NodeId) {
45    // A second set can't happen: each anchor is made for one node.
46    let _ = self.0.set((tree, node));
47  }
48
49  /// The tree, while the window is there, and the node.
50  pub(crate) fn get(&self) -> Option<(Rc<RefCell<Tree>>, NodeId)> {
51    let (tree, node) = self.0.get()?;
52    Some((tree.upgrade()?, *node))
53  }
54}
55
56/// The services of one shell, by type.
57#[derive(Default)]
58struct Services(RefCell<HashMap<TypeId, Rc<dyn Any>>>);
59
60impl std::fmt::Debug for Cx {
61  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62    let services = self.services.0.borrow().len();
63    f.debug_struct("Cx")
64      .field("owner", &self.owner)
65      .field("services", &services)
66      .finish()
67  }
68}
69
70impl Cx {
71  /// A context that creates everything under `owner`, with no services
72  /// yet.
73  pub fn new(owner: Owner) -> Cx {
74    Cx {
75      leaving: owner.signal(false).read_only(),
76      owner,
77      services: Rc::default(),
78      anchor: None,
79    }
80  }
81
82  /// The owner that signals, memos and effects created through this
83  /// context belong to.
84  pub fn owner(&self) -> Owner {
85    self.owner
86  }
87
88  /// A context for a part of the shell inside this one: what it creates
89  /// is freed when this context's owner is. The services are shared with
90  /// the child.
91  pub fn child(&self) -> Cx {
92    Cx {
93      owner: self.owner.child(),
94      services: Rc::clone(&self.services),
95      anchor: self.anchor.clone(),
96      leaving: self.leaving,
97    }
98  }
99
100  /// Whether the keyed item this context is in is leaving: `true` from
101  /// when its key leaves a list with an [`exit`](crate::Row::exit) until
102  /// it is removed, or until the key comes back. Every context made inside
103  /// the item reads the same signal, and outside one it stays `false`.
104  pub fn leaving(&self) -> ReadSignal<bool> {
105    self.leaving
106  }
107
108  /// The context of an item of a list with an exit.
109  pub(crate) fn in_item(self, leaving: ReadSignal<bool>) -> Cx {
110    Cx { leaving, ..self }
111  }
112
113  /// Gives the context's owner a debug name, such as a widget path.
114  pub(crate) fn named(self, name: impl Into<String>) -> Cx {
115    Cx {
116      owner: self.owner.named(name),
117      ..self
118    }
119  }
120
121  /// The context of a widget's own node, at `anchor`.
122  pub(crate) fn at(self, anchor: Rc<Anchor>) -> Cx {
123    Cx {
124      anchor: Some(anchor),
125      ..self
126    }
127  }
128
129  /// Makes `service` reachable from every context of this shell, through
130  /// [`service`](Self::service), and returns it shared. A service of the
131  /// same type provided before is replaced; whoever holds it keeps it.
132  pub fn provide<T: 'static>(&self, service: T) -> Rc<T> {
133    let service = Rc::new(service);
134    let shared: Rc<dyn Any> = Rc::clone(&service) as Rc<dyn Any>;
135    let replaced = self
136      .services
137      .0
138      .borrow_mut()
139      .insert(TypeId::of::<T>(), shared);
140    // Dropped after the map is released: its `Drop` may use a context.
141    drop(replaced);
142    service
143  }
144
145  /// The service of type `T`, if one was provided.
146  pub fn service<T: 'static>(&self) -> Option<Rc<T>> {
147    let service = self.services.0.borrow().get(&TypeId::of::<T>()).cloned()?;
148    service.downcast::<T>().ok()
149  }
150
151  /// Creates a [`Signal`] holding `value`. See [`Owner::signal`].
152  #[track_caller]
153  pub fn signal<T: 'static>(&self, value: T) -> Signal<T> {
154    self.owner.signal(value)
155  }
156
157  /// Creates a [`Memo`] that derives its value with `f`. See
158  /// [`Owner::memo`].
159  #[track_caller]
160  pub fn memo<T: PartialEq + 'static>(&self, f: impl FnMut() -> T + 'static) -> Memo<T> {
161    self.owner.memo(f)
162  }
163
164  /// Creates an [`Effect`] that reruns `f` when something it read changes.
165  /// See [`Owner::effect`].
166  #[track_caller]
167  pub fn effect(&self, f: impl FnMut() + 'static) -> Effect {
168    self.owner.effect(f)
169  }
170}