ferroui_base/controls/
resource_node_extensions.rs1use super::{
2 IResourceHost, IResourceProvider, ResourceHostRef, ResourceKey, ResourceValue, ResourcesChangedEventArgs,
3 WeakResourceHost,
4};
5use crate::reactive::{Disposable, IDisposable, IObservable, IObserver};
6use crate::styling::ThemeVariant;
7use crate::{BoxedValue, FerroProperty};
8use std::cell::{Cell, RefCell};
9use std::rc::{Rc, Weak};
10
11pub type ResourceConverter = Rc<dyn Fn(Option<BoxedValue>) -> Option<BoxedValue>>;
14
15impl dyn IResourceHost + '_ {
16 pub fn find_resource(&self, key: &ResourceKey) -> ResourceValue {
21 match self.try_find_resource(key, None) {
22 Some(value) => value,
23 None => Some(FerroProperty::unset_value()),
24 }
25 }
26
27 pub fn find_resource_for_theme(&self, theme: Option<&ThemeVariant>, key: &ResourceKey) -> ResourceValue {
32 match self.try_find_resource(key, theme) {
33 Some(value) => value,
34 None => Some(FerroProperty::unset_value()),
35 }
36 }
37
38 pub fn try_find_resource(&self, key: &ResourceKey, theme: Option<&ThemeVariant>) -> Option<ResourceValue> {
41 crate::perf_count!(ResourceLookups);
42 crate::perf_count!(ResourceHostsProbed);
43 if let Some(value) = self.try_get_resource(key, theme) {
44 return Some(value);
45 }
46
47 let mut current = self.as_style_host().and_then(|h| h.styling_parent());
48 while let Some(host) = current {
49 crate::perf_count!(ResourceHostsProbed);
50 if let Some(value) = host.try_get_resource(key, theme) {
51 return Some(value);
52 }
53 current = host.styling_parent();
54 }
55
56 None
57 }
58}
59
60impl ResourceHostRef {
61 pub fn resource_observable(
68 &self,
69 key: impl Into<ResourceKey>,
70 converter: Option<ResourceConverter>,
71 ) -> Rc<dyn IObservable<Option<BoxedValue>>> {
72 ResourceObservable::create(Target::Host(self.downgrade()), key.into(), None, converter)
73 }
74}
75
76pub fn get_floating_resource_observable(
86 resource_provider: Rc<dyn IResourceProvider>,
87 key: impl Into<ResourceKey>,
88 default_theme_variant: Option<ThemeVariant>,
89 converter: Option<ResourceConverter>,
90) -> Rc<dyn IObservable<Option<BoxedValue>>> {
91 ResourceObservable::create(Target::Provider(resource_provider), key.into(), default_theme_variant, converter)
92}
93
94type ResourceObserver = Rc<dyn IObserver<Option<BoxedValue>>>;
95
96enum Target {
97 Host(WeakResourceHost),
98 Provider(Rc<dyn IResourceProvider>),
99}
100
101struct ResourceObservable {
102 this: Weak<ResourceObservable>,
103 target: Target,
104 key: ResourceKey,
105 override_theme_variant: Option<ThemeVariant>,
106 converter: Option<ResourceConverter>,
107 observers: RefCell<Vec<(u64, ResourceObserver)>>,
108 next_id: Cell<u64>,
109 owner_changed_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
110 host_subscriptions: RefCell<Vec<Rc<dyn IDisposable>>>,
111}
112
113impl ResourceObservable {
114 fn create(
115 target: Target,
116 key: ResourceKey,
117 override_theme_variant: Option<ThemeVariant>,
118 converter: Option<ResourceConverter>,
119 ) -> Rc<dyn IObservable<Option<BoxedValue>>> {
120 Rc::new_cyclic(|this| ResourceObservable {
121 this: this.clone(),
122 target,
123 key,
124 override_theme_variant,
125 converter,
126 observers: RefCell::new(Vec::new()),
127 next_id: Cell::new(0),
128 owner_changed_subscription: RefCell::new(None),
129 host_subscriptions: RefCell::new(Vec::new()),
130 })
131 }
132
133 fn host(&self) -> Option<ResourceHostRef> {
134 match &self.target {
135 Target::Host(host) => host.upgrade(),
136 Target::Provider(provider) => provider.owner(),
137 }
138 }
139
140 fn has_host(&self) -> bool {
141 match &self.target {
142 Target::Host(_) => true,
143 Target::Provider(provider) => provider.owner().is_some(),
144 }
145 }
146
147 fn initialize(&self) {
148 if let Target::Provider(provider) = &self.target {
149 let Some(this) = self.this.upgrade() else { return };
153 let subscription = provider.owner_changed(Rc::new(move || {
154 this.unsubscribe_host();
155 this.subscribe_host();
156 this.publish_next();
157 }));
158 *self.owner_changed_subscription.borrow_mut() = Some(subscription);
159 }
160 self.subscribe_host();
161 }
162
163 fn deinitialize(&self) {
164 let subscription = self.owner_changed_subscription.borrow_mut().take();
165 if let Some(subscription) = subscription {
166 subscription.dispose();
167 }
168 self.unsubscribe_host();
169 }
170
171 fn subscribe_host(&self) {
172 let Some(host) = self.host() else { return };
173 let mut subscriptions = Vec::with_capacity(2);
174
175 let Some(this) = self.this.upgrade() else { return };
176 let observable = this.clone();
177 subscriptions.push(host.resources_changed(Rc::new(move |_: &ResourcesChangedEventArgs| {
178 observable.publish_next();
179 })));
180
181 if self.override_theme_variant.is_none() {
182 if let Some(theme_host) = host.as_theme_variant_host() {
183 subscriptions.push(theme_host.actual_theme_variant_changed(Rc::new(move || {
184 this.publish_next();
185 })));
186 }
187 }
188
189 *self.host_subscriptions.borrow_mut() = subscriptions;
190 }
191
192 fn unsubscribe_host(&self) {
193 let subscriptions = std::mem::take(&mut *self.host_subscriptions.borrow_mut());
194 for subscription in subscriptions {
195 subscription.dispose();
196 }
197 }
198
199 fn publish_next(&self) {
200 if !self.has_host() {
201 return;
202 }
203 let value = self.get_value();
204 let observers: Vec<_> = self.observers.borrow().iter().map(|(_, o)| o.clone()).collect();
205 for observer in observers {
206 observer.on_next(value.clone());
207 }
208 }
209
210 fn get_value(&self) -> Option<BoxedValue> {
211 let value = match self.host() {
212 Some(host) => {
213 let theme = self
214 .override_theme_variant
215 .clone()
216 .or_else(|| host.as_theme_variant_host().and_then(|h| h.actual_theme_variant()));
217 host.find_resource_for_theme(theme.as_ref(), &self.key)
218 }
219 None => Some(FerroProperty::unset_value()),
220 };
221 let value = value.or_else(|| Some(FerroProperty::unset_value()));
223 match &self.converter {
224 Some(converter) => converter(value.clone()).or(value),
225 None => value,
226 }
227 }
228}
229
230impl IObservable<Option<BoxedValue>> for ResourceObservable {
231 fn subscribe(&self, observer: Rc<dyn IObserver<Option<BoxedValue>>>) -> Rc<dyn IDisposable> {
232 let first = self.observers.borrow().is_empty();
233 let id = self.next_id.get();
234 self.next_id.set(id + 1);
235 self.observers.borrow_mut().push((id, observer.clone()));
236
237 if first {
238 self.initialize();
239 }
240
241 if self.has_host() {
242 observer.on_next(self.get_value());
243 }
244
245 let weak = self.this.clone();
246 Disposable::create(move || {
247 if let Some(this) = weak.upgrade() {
248 let empty = {
249 let mut observers = this.observers.borrow_mut();
250 observers.retain(|(i, _)| *i != id);
251 observers.is_empty()
252 };
253 if empty {
254 this.deinitialize();
255 }
256 }
257 })
258 }
259}