tpt_appfront_core/
context.rs1use crate::signal::Signal;
25use std::any::{Any, TypeId};
26use std::cell::RefCell;
27use std::collections::HashMap;
28use std::rc::Rc;
29
30pub struct Context<T> {
36 signal: Signal<T>,
37}
38
39impl<T> Clone for Context<T> {
40 fn clone(&self) -> Self {
41 Context {
42 signal: self.signal.clone(),
43 }
44 }
45}
46
47impl<T: Clone + 'static> Context<T> {
48 pub fn new(value: T) -> Self {
50 Context {
51 signal: Signal::new(value),
52 }
53 }
54
55 pub fn from_signal(signal: Signal<T>) -> Self {
57 Context { signal }
58 }
59
60 pub fn get(&self) -> T
62 where
63 T: Clone,
64 {
65 self.signal.get()
66 }
67
68 pub fn set(&self, value: T) {
70 self.signal.set(value);
71 }
72
73 pub fn signal(&self) -> Signal<T> {
75 self.signal.clone()
76 }
77}
78
79thread_local! {
80 static PROVIDERS: RefCell<HashMap<TypeId, Vec<Rc<dyn Any>>>> =
83 RefCell::new(HashMap::new());
84}
85
86pub fn provide_context<T: 'static>(ctx: &Context<T>, scope: impl FnOnce()) {
90 let key = TypeId::of::<T>();
91 PROVIDERS.with(|p| {
92 p.borrow_mut()
93 .entry(key)
94 .or_insert_with(Vec::new)
95 .push(Rc::new(ctx.clone()) as Rc<dyn Any>);
96 });
97 scope();
98 PROVIDERS.with(|p| {
99 let mut map = p.borrow_mut();
100 if let Some(stack) = map.get_mut(&key) {
101 stack.pop();
102 if stack.is_empty() {
103 map.remove(&key);
104 }
105 }
106 });
107}
108
109pub fn use_context<T: 'static>() -> Option<Context<T>> {
112 let key = TypeId::of::<T>();
113 PROVIDERS.with(|p| {
114 let stack = p.borrow();
115 stack
116 .get(&key)
117 .and_then(|s| s.last())
118 .and_then(|rc| rc.downcast_ref::<Context<T>>())
119 .cloned()
120 })
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[derive(Debug, Clone, PartialEq)]
128 struct Theme {
129 dark: bool,
130 }
131
132 #[derive(Debug, Clone, PartialEq)]
133 struct User {
134 name: String,
135 }
136
137 #[test]
138 fn use_context_returns_none_when_unprovided() {
139 assert!(use_context::<Theme>().is_none());
140 }
141
142 #[test]
143 fn provide_then_use_resolves_nearest() {
144 let outer = Context::new(Theme { dark: false });
145 let mut observed = None;
146 provide_context(&outer, || {
147 observed = use_context::<Theme>().map(|c| c.get());
148 });
149 assert_eq!(observed, Some(Theme { dark: false }));
150 }
151
152 #[test]
153 fn inner_provider_shadows_outer() {
154 let outer = Context::new(Theme { dark: false });
155 let inner = Context::new(Theme { dark: true });
156 let mut outer_before = None;
157 let mut inner_seen = None;
158 let mut outer_after = None;
159 provide_context(&outer, || {
160 outer_before = use_context::<Theme>().map(|c| c.get());
161 provide_context(&inner, || {
162 inner_seen = use_context::<Theme>().map(|c| c.get());
163 });
164 outer_after = use_context::<Theme>().map(|c| c.get());
166 });
167 assert_eq!(inner_seen, Some(Theme { dark: true }));
168 assert_eq!(outer_before, Some(Theme { dark: false }));
169 assert_eq!(outer_after, Some(Theme { dark: false }));
170 }
171
172 #[test]
173 fn different_types_are_independent() {
174 let theme = Context::new(Theme { dark: true });
175 let user = Context::new(User {
176 name: "ada".to_string(),
177 });
178 provide_context(&theme, || {
179 provide_context(&user, || {
180 assert!(use_context::<Theme>().is_some());
181 assert!(use_context::<User>().is_some());
182 assert!(use_context::<i32>().is_none());
183 });
184 });
185 }
186
187 #[test]
188 fn context_updates_are_visible_to_consumers() {
189 let theme = Context::new(Theme { dark: false });
190 let observed = Rc::new(RefCell::new(None));
191 let obs = observed.clone();
192 provide_context(&theme, || {
193 if let Some(c) = use_context::<Theme>() {
194 *obs.borrow_mut() = Some(c.get());
195 c.set(Theme { dark: true });
196 *obs.borrow_mut() = Some(c.get());
197 }
198 });
199 assert_eq!(*observed.borrow(), Some(Theme { dark: true }));
200 }
201}