Skip to main content

ferroui_base/controls/
resource_dictionary.rs

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/// An indexed dictionary of resources.
32#[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    /// Creates the class data; see [`FerroObject::construct`].
177    pub fn construct() -> Self {
178        Self::construct_from(ResourceProvider::construct())
179    }
180
181    /// Creates the class data of a dictionary that belongs to `owner`.
182    pub fn construct_with_owner(owner: &ResourceHostRef) -> Self {
183        Self::construct_from(ResourceProvider::construct_with_owner(owner))
184    }
185
186    /// Creates an empty resource dictionary.
187    pub fn new() -> Ref<Self> {
188        instantiate(Self::construct())
189    }
190
191    /// Creates an empty resource dictionary that belongs to `owner`.
192    pub fn with_owner(owner: impl Into<ResourceHostRef>) -> Ref<Self> {
193        instantiate(Self::construct_with_owner(&owner.into()))
194    }
195
196    /// The number of resources in the dictionary itself.
197    pub fn count(&self) -> usize {
198        self.inner.borrow().len()
199    }
200
201    /// Gets a resource of the dictionary itself; `None` (a null value) if the
202    /// key is not present.
203    pub fn get(&self, key: &ResourceKey) -> ResourceValue {
204        self.try_get_value(key).flatten()
205    }
206
207    /// Adds or replaces a resource.
208    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    /// The keys of the resources in the dictionary itself.
216    pub fn keys(&self) -> Vec<ResourceKey> {
217        self.inner.borrow().keys().cloned().collect()
218    }
219
220    /// The child resource dictionaries.
221    ///
222    /// The list is returned by handle: clones refer to the same list.
223    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    /// Adds a child resource dictionary.
266    pub fn add_merged_dictionary(&self, provider: impl Into<Rc<dyn IResourceProvider>>) {
267        self.merged_dictionaries().add(provider.into());
268    }
269
270    /// Removes a child resource dictionary. Returns whether it was present.
271    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    /// The resource providers used for specific theme variants.
284    ///
285    /// The dictionary is returned by handle: clones refer to the same
286    /// dictionary. A provider added to it gets the owner of this dictionary;
287    /// a removed one loses it.
288    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    /// A snapshot of the resource providers used for specific theme variants.
327    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    /// The resource provider used for the given theme variant.
335    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    /// Adds a resource provider for a theme variant. Panics if the variant
340    /// already has one.
341    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    /// Adds or replaces the resource provider of a theme variant.
346    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    /// Removes the resource provider of a theme variant. Returns whether it
351    /// was present.
352    pub fn remove_theme_dictionary(&self, variant: &ThemeVariant) -> bool {
353        self.theme_dictionaries.get().is_some_and(|d| d.remove(variant))
354    }
355
356    /// The key that is used in the theme dictionaries of the containing
357    /// resource dictionary.
358    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    /// Adds a resource. Panics if the key is already present.
367    pub fn add(&self, key: impl Into<ResourceKey>, value: ResourceValue) {
368        self.add_item(key.into(), ResourceItem::Value(value));
369    }
370
371    /// Adds a resource value. Panics if the key is already present.
372    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    /// Adds a resource that is created by `factory` the first time it is
389    /// requested.
390    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    /// Adds a resource that is built from `deferred_content` the first time
399    /// it is requested.
400    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    /// Adds a resource that is built from `deferred_content` every time it is
405    /// requested.
406    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    /// Adds or replaces multiple resources, raising a single notification.
411    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    /// Removes all resources of the dictionary itself.
423    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    /// Whether the dictionary itself contains the key.
441    pub fn contains_key(&self, key: &ResourceKey) -> bool {
442        self.inner.borrow().contains_key(key)
443    }
444
445    /// Removes a resource. Returns whether it was present.
446    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    /// Gets a resource of the dictionary itself, building it if it is
457    /// deferred.
458    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        // Avoid simple reentrancy, which could commonly occur on redefining
467        // the resource.
468        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    /// Ensures that the dictionary can hold up to `capacity` resources
489    /// without further expansion of its backing storage.
490    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    /// Whether the key is present and its resource has not been built yet.
497    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
517/// The interfaces of a resource dictionary object.
518struct 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}