1use super::resource_key::ResourceKeyBuildHasher;
2use super::{
3 resource_provider_ptr_eq, IDeferredContent, IResourceDictionary, IResourceNode, IResourceProvider,
4 IThemeVariantProvider, ResourceHostRef, ResourceKey, ResourceProvider, ResourceProviderImpl, ResourceValue,
5 ResourcesChangedEventArgs,
6};
7use crate::collections::{FerroDictionary, FerroList, NotifyCollectionChangedAction, NotifyCollectionChangedEventArgs, ResetBehavior};
8use crate::metadata::IServiceProvider;
9use crate::reactive::IDisposable;
10use crate::styling::ThemeVariant;
11use crate::{ferro_class, instantiate, BoxedValue, FerroObject, FerroObjectImpl, ObjectType, Ref, Upcast};
12use std::cell::{OnceCell, RefCell};
13use std::collections::HashMap;
14use std::rc::Rc;
15
16#[derive(Clone)]
17enum ResourceItem {
18 Value(ResourceValue),
19 Deferred(Rc<dyn IDeferredContent>),
20 NotSharedDeferred(Rc<dyn IDeferredContent>),
21}
22
23struct DeferredItem<F: Fn(Option<&Rc<dyn IServiceProvider>>) -> Option<BoxedValue>>(F);
24
25impl<F: Fn(Option<&Rc<dyn IServiceProvider>>) -> Option<BoxedValue>> IDeferredContent for DeferredItem<F> {
26 fn build(&self, service_provider: Option<&Rc<dyn IServiceProvider>>) -> Option<BoxedValue> {
27 (self.0)(service_provider)
28 }
29}
30
31#[repr(C)]
33pub struct ResourceDictionary {
34 base: ResourceProvider,
35 last_deferred_item_key: RefCell<Option<ResourceKey>>,
36 inner: RefCell<HashMap<ResourceKey, ResourceItem, ResourceKeyBuildHasher>>,
37 merged_dictionaries: OnceCell<FerroList<Rc<dyn IResourceProvider>>>,
38 theme_dictionaries: OnceCell<FerroDictionary<ThemeVariant, Rc<dyn IThemeVariantProvider>>>,
39 key: RefCell<Option<ThemeVariant>>,
40 #[cfg(all(debug_assertions, feature = "hot-reload"))]
41 writes: RefCell<HashMap<ResourceKey, u64, ResourceKeyBuildHasher>>,
42 #[cfg(all(debug_assertions, feature = "hot-reload"))]
43 next_write: std::cell::Cell<u64>,
44}
45
46#[cfg(all(debug_assertions, feature = "hot-reload"))]
47#[path = "resource_dictionary_reload.rs"]
48mod reload;
49#[cfg(all(debug_assertions, feature = "hot-reload"))]
50pub use reload::{ResourceDictionaryChange, ResourceDictionaryOwnership};
51
52ferro_class!(ResourceDictionary: ResourceProvider);
53crate::ferro_class_info!(ResourceDictionary {
54 new: ResourceDictionary::new,
55 interfaces: [Rc<dyn IResourceProvider>, Rc<dyn IResourceDictionary>, Rc<dyn IThemeVariantProvider>],
56});
57
58impl FerroObjectImpl for ResourceDictionary {}
59
60impl ResourceProviderImpl for ResourceDictionary {
61 fn has_resources(this: &Self) -> bool {
62 if !this.inner.borrow().is_empty() {
63 return true;
64 }
65 if let Some(merged) = this.merged_dictionaries.get() {
66 if !merged.is_empty() {
67 return merged.snapshot().iter().any(|i| i.has_resources());
68 }
69 }
70 false
71 }
72
73 fn try_get_resource(this: &Self, key: &ResourceKey, theme: Option<&ThemeVariant>) -> Option<ResourceValue> {
74 if let Some(value) = this.try_get_value(key) {
75 return Some(value);
76 }
77
78 if this.theme_dictionaries.get().is_some() {
79 if let Some(theme) = theme {
80 if *theme != ThemeVariant::default() {
81 if let Some(provider) = this.theme_dictionary(theme) {
82 if let Some(value) = super::resource_preview::lookup(&*provider, key, Some(theme)) {
83 return Some(value);
84 }
85 }
86
87 let mut theme_inherit = theme.inherit_variant();
88 while let Some(inherit) = theme_inherit {
89 if let Some(provider) = this.theme_dictionary(&inherit) {
90 if let Some(value) = super::resource_preview::lookup(&*provider, key, Some(theme)) {
91 return Some(value);
92 }
93 }
94 theme_inherit = inherit.inherit_variant();
95 }
96 }
97 }
98
99 if let Some(provider) = this.theme_dictionary(&ThemeVariant::default()) {
100 if let Some(value) = super::resource_preview::lookup(&*provider, key, theme) {
101 return Some(value);
102 }
103 }
104 }
105
106 if let Some(merged) = this.merged_dictionaries.get() {
107 if !merged.is_empty() {
108 for provider in merged.snapshot().iter().rev() {
109 if let Some(value) = super::resource_preview::lookup(&**provider, key, theme) {
110 return Some(value);
111 }
112 }
113 }
114 }
115
116 None
117 }
118
119 fn on_add_owner(this: &Self, owner: &ResourceHostRef) {
120 let mut has_resources = !this.inner.borrow().is_empty();
121
122 if let Some(merged) = this.merged_dictionaries.get() {
123 for i in merged.snapshot().iter() {
124 i.add_owner(owner);
125 has_resources |= i.has_resources();
126 }
127 }
128
129 for (_, i) in this.theme_dictionaries_snapshot() {
130 i.add_owner(owner);
131 has_resources |= i.has_resources();
132 }
133
134 if has_resources {
135 owner.notify_hosted_resources_changed(ResourcesChangedEventArgs::create());
136 }
137 }
138
139 fn on_remove_owner(this: &Self, owner: &ResourceHostRef) {
140 let mut has_resources = !this.inner.borrow().is_empty();
141
142 if let Some(merged) = this.merged_dictionaries.get() {
143 for i in merged.snapshot().iter() {
144 i.remove_owner(owner);
145 has_resources |= i.has_resources();
146 }
147 }
148
149 for (_, i) in this.theme_dictionaries_snapshot() {
150 i.remove_owner(owner);
151 has_resources |= i.has_resources();
152 }
153
154 if has_resources {
155 owner.notify_hosted_resources_changed(ResourcesChangedEventArgs::create());
156 }
157 }
158}
159
160impl ResourceDictionary {
161 fn construct_from(base: ResourceProvider) -> Self {
162 Self {
163 base,
164 last_deferred_item_key: RefCell::new(None),
165 inner: RefCell::new(HashMap::default()),
166 merged_dictionaries: OnceCell::new(),
167 theme_dictionaries: OnceCell::new(),
168 key: RefCell::new(None),
169 #[cfg(all(debug_assertions, feature = "hot-reload"))]
170 writes: RefCell::new(HashMap::default()),
171 #[cfg(all(debug_assertions, feature = "hot-reload"))]
172 next_write: std::cell::Cell::new(0),
173 }
174 }
175
176 pub fn construct() -> Self {
178 Self::construct_from(ResourceProvider::construct())
179 }
180
181 pub fn construct_with_owner(owner: &ResourceHostRef) -> Self {
183 Self::construct_from(ResourceProvider::construct_with_owner(owner))
184 }
185
186 pub fn new() -> Ref<Self> {
188 instantiate(Self::construct())
189 }
190
191 pub fn with_owner(owner: impl Into<ResourceHostRef>) -> Ref<Self> {
193 instantiate(Self::construct_with_owner(&owner.into()))
194 }
195
196 pub fn count(&self) -> usize {
198 self.inner.borrow().len()
199 }
200
201 pub fn get(&self, key: &ResourceKey) -> ResourceValue {
204 self.try_get_value(key).flatten()
205 }
206
207 pub fn set(&self, key: impl Into<ResourceKey>, value: ResourceValue) {
209 let key = key.into();
210 self.record_write(&key);
211 self.inner.borrow_mut().insert(key, ResourceItem::Value(value));
212 self.raise_resources_changed();
213 }
214
215 pub fn keys(&self) -> Vec<ResourceKey> {
217 self.inner.borrow().keys().cloned().collect()
218 }
219
220 pub fn merged_dictionaries(&self) -> FerroList<Rc<dyn IResourceProvider>> {
224 self.merged_dictionaries_ref().clone()
225 }
226
227 fn merged_dictionaries_ref(&self) -> &FerroList<Rc<dyn IResourceProvider>> {
228 self.merged_dictionaries.get_or_init(|| {
229 let list = FerroList::new();
230 list.set_reset_behavior(ResetBehavior::Remove);
231 let weak = self.to_ref().downgrade();
232 list.add_collection_changed(Rc::new(
233 move |e: &NotifyCollectionChangedEventArgs<'_, Rc<dyn IResourceProvider>>| {
234 let Some(this) = weak.upgrade() else { return };
235 let added = |items: &[Rc<dyn IResourceProvider>]| {
236 for x in items {
237 if let Some(owner) = this.owner() {
238 x.add_owner(&owner);
239 }
240 }
241 };
242 let removed = |items: &[Rc<dyn IResourceProvider>]| {
243 for x in items {
244 if let Some(owner) = this.owner() {
245 x.remove_owner(&owner);
246 }
247 }
248 };
249 match e.action {
250 NotifyCollectionChangedAction::Add => added(e.new_items),
251 NotifyCollectionChangedAction::Remove => removed(e.old_items),
252 NotifyCollectionChangedAction::Replace => {
253 removed(e.old_items);
254 added(e.new_items);
255 }
256 NotifyCollectionChangedAction::Reset => panic!("Dictionary reset not supported"),
257 NotifyCollectionChangedAction::Move => {}
258 }
259 },
260 ));
261 list
262 })
263 }
264
265 pub fn add_merged_dictionary(&self, provider: impl Into<Rc<dyn IResourceProvider>>) {
267 self.merged_dictionaries().add(provider.into());
268 }
269
270 pub fn remove_merged_dictionary(&self, provider: &Rc<dyn IResourceProvider>) -> bool {
272 let list = self.merged_dictionaries();
273 let index = list.snapshot().iter().position(|p| resource_provider_ptr_eq(p, provider));
274 match index {
275 Some(index) => {
276 list.remove_at(index);
277 true
278 }
279 None => false,
280 }
281 }
282
283 pub fn theme_dictionaries(&self) -> FerroDictionary<ThemeVariant, Rc<dyn IThemeVariantProvider>> {
289 self.theme_dictionaries
290 .get_or_init(|| {
291 let dictionary = FerroDictionary::with_capacity(2);
292 let weak = self.to_ref().downgrade();
293 dictionary.add_collection_changed(Rc::new(
294 move |e: &NotifyCollectionChangedEventArgs<'_, (ThemeVariant, Rc<dyn IThemeVariantProvider>)>| {
295 let Some(this) = weak.upgrade() else { return };
296 let added = |items: &[(ThemeVariant, Rc<dyn IThemeVariantProvider>)]| {
297 for (_, x) in items {
298 if let Some(owner) = this.owner() {
299 x.add_owner(&owner);
300 }
301 }
302 };
303 let removed = |items: &[(ThemeVariant, Rc<dyn IThemeVariantProvider>)]| {
304 for (_, x) in items {
305 if let Some(owner) = this.owner() {
306 x.remove_owner(&owner);
307 }
308 }
309 };
310 match e.action {
311 NotifyCollectionChangedAction::Add => added(e.new_items),
312 NotifyCollectionChangedAction::Remove => removed(e.old_items),
313 NotifyCollectionChangedAction::Replace | NotifyCollectionChangedAction::Move => {
314 removed(e.old_items);
315 added(e.new_items);
316 }
317 NotifyCollectionChangedAction::Reset => panic!("Dictionary reset not supported"),
318 }
319 },
320 ));
321 dictionary
322 })
323 .clone()
324 }
325
326 pub fn theme_dictionaries_snapshot(&self) -> Vec<(ThemeVariant, Rc<dyn IThemeVariantProvider>)> {
328 match self.theme_dictionaries.get() {
329 Some(dictionaries) if !dictionaries.is_empty() => dictionaries.to_vec(),
330 _ => Vec::new(),
331 }
332 }
333
334 pub fn theme_dictionary(&self, variant: &ThemeVariant) -> Option<Rc<dyn IThemeVariantProvider>> {
336 self.theme_dictionaries.get().and_then(|d| d.try_get_value(variant))
337 }
338
339 pub fn add_theme_dictionary(&self, variant: ThemeVariant, provider: impl Into<Rc<dyn IThemeVariantProvider>>) {
342 self.theme_dictionaries().add(variant, provider.into());
343 }
344
345 pub fn set_theme_dictionary(&self, variant: ThemeVariant, provider: impl Into<Rc<dyn IThemeVariantProvider>>) {
347 self.theme_dictionaries().set(variant, provider.into());
348 }
349
350 pub fn remove_theme_dictionary(&self, variant: &ThemeVariant) -> bool {
353 self.theme_dictionaries.get().is_some_and(|d| d.remove(variant))
354 }
355
356 pub fn key(&self) -> Option<ThemeVariant> {
359 self.key.borrow().clone()
360 }
361
362 pub fn set_key(&self, value: Option<ThemeVariant>) {
363 *self.key.borrow_mut() = value;
364 }
365
366 pub fn add(&self, key: impl Into<ResourceKey>, value: ResourceValue) {
368 self.add_item(key.into(), ResourceItem::Value(value));
369 }
370
371 pub fn add_value<T: PartialEq + 'static>(&self, key: impl Into<ResourceKey>, value: T) {
373 self.add(key, Some(Rc::new(value)));
374 }
375
376 fn add_item(&self, key: ResourceKey, item: ResourceItem) {
377 {
378 let mut inner = self.inner.borrow_mut();
379 if inner.contains_key(&key) {
380 panic!("An item with the same key has already been added. Key: {key}");
381 }
382 self.record_write(&key);
383 inner.insert(key, item);
384 }
385 self.raise_resources_changed();
386 }
387
388 pub fn add_deferred_fn(
391 &self,
392 key: impl Into<ResourceKey>,
393 factory: impl Fn(Option<&Rc<dyn IServiceProvider>>) -> Option<BoxedValue> + 'static,
394 ) {
395 self.add_item(key.into(), ResourceItem::Deferred(Rc::new(DeferredItem(factory))));
396 }
397
398 pub fn add_deferred(&self, key: impl Into<ResourceKey>, deferred_content: Rc<dyn IDeferredContent>) {
401 self.add_item(key.into(), ResourceItem::Deferred(deferred_content));
402 }
403
404 pub fn add_not_shared_deferred(&self, key: impl Into<ResourceKey>, deferred_content: Rc<dyn IDeferredContent>) {
407 self.add_item(key.into(), ResourceItem::NotSharedDeferred(deferred_content));
408 }
409
410 pub fn set_items(&self, values: impl IntoIterator<Item = (ResourceKey, ResourceValue)>) {
412 {
413 let mut inner = self.inner.borrow_mut();
414 for (key, value) in values {
415 self.record_write(&key);
416 inner.insert(key, ResourceItem::Value(value));
417 }
418 }
419 self.raise_resources_changed();
420 }
421
422 pub fn clear(&self) {
424 let cleared = {
425 let mut inner = self.inner.borrow_mut();
426 if inner.is_empty() {
427 false
428 } else {
429 inner.clear();
430 #[cfg(all(debug_assertions, feature = "hot-reload"))]
431 self.writes.borrow_mut().clear();
432 true
433 }
434 };
435 if cleared {
436 self.raise_resources_changed();
437 }
438 }
439
440 pub fn contains_key(&self, key: &ResourceKey) -> bool {
442 self.inner.borrow().contains_key(key)
443 }
444
445 pub fn remove(&self, key: &ResourceKey) -> bool {
447 let removed = self.inner.borrow_mut().remove(key).is_some();
448 if removed {
449 #[cfg(all(debug_assertions, feature = "hot-reload"))]
450 self.writes.borrow_mut().remove(key);
451 self.raise_resources_changed();
452 }
453 removed
454 }
455
456 pub fn try_get_value(&self, key: &ResourceKey) -> Option<ResourceValue> {
459 let item = self.inner.borrow().get(key).cloned()?;
460 let (deferred, shared) = match item {
461 ResourceItem::Value(value) => return Some(value),
462 ResourceItem::Deferred(deferred) => (deferred, true),
463 ResourceItem::NotSharedDeferred(deferred) => (deferred, false),
464 };
465
466 if self.last_deferred_item_key.borrow().as_ref() == Some(key) {
469 return None;
470 }
471
472 struct Reset<'a>(&'a RefCell<Option<ResourceKey>>);
473 impl Drop for Reset<'_> {
474 fn drop(&mut self) {
475 *self.0.borrow_mut() = None;
476 }
477 }
478
479 *self.last_deferred_item_key.borrow_mut() = Some(key.clone());
480 let _reset = Reset(&self.last_deferred_item_key);
481 let value = deferred.build(None);
482 if shared {
483 self.inner.borrow_mut().insert(key.clone(), ResourceItem::Value(value.clone()));
484 }
485 Some(value)
486 }
487
488 pub fn ensure_capacity(&self, capacity: usize) {
491 let mut inner = self.inner.borrow_mut();
492 let additional = capacity.saturating_sub(inner.len());
493 inner.reserve(additional);
494 }
495
496 pub fn contains_deferred_key(&self, key: &ResourceKey) -> bool {
498 matches!(
499 self.inner.borrow().get(key),
500 Some(ResourceItem::Deferred(_) | ResourceItem::NotSharedDeferred(_))
501 )
502 }
503
504 #[inline]
505 fn record_write(&self, key: &ResourceKey) {
506 #[cfg(all(debug_assertions, feature = "hot-reload"))]
507 {
508 let version = self.next_write.get().checked_add(1).expect("resource write version overflow");
509 self.next_write.set(version);
510 self.writes.borrow_mut().insert(key.clone(), version);
511 }
512 #[cfg(not(all(debug_assertions, feature = "hot-reload")))]
513 let _ = key;
514 }
515}
516
517struct ResourceDictionaryHandle(Ref<ResourceDictionary>);
519
520impl IResourceNode for ResourceDictionaryHandle {
521 fn reference_id(&self) -> *const () {
522 let object: &FerroObject = (*self.0).upcast();
523 object as *const FerroObject as *const ()
524 }
525
526 fn has_resources(&self) -> bool {
527 self.0.has_resources()
528 }
529
530 fn try_get_resource(&self, key: &ResourceKey, theme: Option<&ThemeVariant>) -> Option<ResourceValue> {
531 self.0.try_get_resource(key, theme)
532 }
533}
534
535impl IResourceProvider for ResourceDictionaryHandle {
536 fn as_any(&self) -> Option<&dyn std::any::Any> {
537 Some(self)
538 }
539
540 fn as_resource_dictionary(&self) -> Option<&dyn IResourceDictionary> {
541 Some(self)
542 }
543
544 fn owner(&self) -> Option<ResourceHostRef> {
545 self.0.owner()
546 }
547
548 fn owner_changed(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
549 self.0.subscribe_owner_changed(handler)
550 }
551
552 fn add_owner(&self, owner: &ResourceHostRef) {
553 self.0.add_owner(owner)
554 }
555
556 fn remove_owner(&self, owner: &ResourceHostRef) {
557 self.0.remove_owner(owner)
558 }
559
560 fn as_object(&self) -> Option<&FerroObject> {
561 Some((*self.0).upcast())
562 }
563}
564
565impl IThemeVariantProvider for ResourceDictionaryHandle {
566 fn key(&self) -> Option<ThemeVariant> {
567 self.0.key()
568 }
569
570 fn set_key(&self, value: Option<ThemeVariant>) {
571 self.0.set_key(value)
572 }
573}
574
575impl IResourceDictionary for ResourceDictionaryHandle {
576 fn merged_dictionaries(&self) -> FerroList<Rc<dyn IResourceProvider>> {
577 self.0.merged_dictionaries()
578 }
579
580 fn merged_dictionaries_snapshot(&self) -> Rc<Vec<Rc<dyn IResourceProvider>>> {
581 self.0.merged_dictionaries().snapshot()
582 }
583
584 fn theme_dictionaries_snapshot(&self) -> Vec<(ThemeVariant, Rc<dyn IThemeVariantProvider>)> {
585 self.0.theme_dictionaries_snapshot()
586 }
587
588 fn count(&self) -> usize {
589 self.0.count()
590 }
591
592 fn add(&self, key: ResourceKey, value: ResourceValue) {
593 self.0.add(key, value)
594 }
595
596 fn set(&self, key: ResourceKey, value: ResourceValue) {
597 self.0.set(key, value)
598 }
599
600 fn contains_key(&self, key: &ResourceKey) -> bool {
601 self.0.contains_key(key)
602 }
603
604 fn remove(&self, key: &ResourceKey) -> bool {
605 self.0.remove(key)
606 }
607
608 fn try_get_value(&self, key: &ResourceKey) -> Option<ResourceValue> {
609 self.0.try_get_value(key)
610 }
611
612 fn clear(&self) {
613 self.0.clear()
614 }
615}
616
617impl<T: ObjectType + Upcast<ResourceDictionary>> From<Ref<T>> for Rc<dyn IThemeVariantProvider> {
618 fn from(value: Ref<T>) -> Self {
619 Rc::new(ResourceDictionaryHandle(value.upcast()))
620 }
621}
622
623impl<T: ObjectType + Upcast<ResourceDictionary>> From<&Ref<T>> for Rc<dyn IThemeVariantProvider> {
624 fn from(value: &Ref<T>) -> Self {
625 Rc::new(ResourceDictionaryHandle(value.clone().upcast()))
626 }
627}
628
629impl<T: ObjectType + Upcast<ResourceDictionary>> From<Ref<T>> for Rc<dyn IResourceDictionary> {
630 fn from(value: Ref<T>) -> Self {
631 Rc::new(ResourceDictionaryHandle(value.upcast()))
632 }
633}