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}
41
42ferro_class!(ResourceDictionary: ResourceProvider);
43crate::ferro_class_info!(ResourceDictionary {
44    new: ResourceDictionary::new,
45    interfaces: [Rc<dyn IResourceProvider>, Rc<dyn IResourceDictionary>, Rc<dyn IThemeVariantProvider>],
46});
47
48impl FerroObjectImpl for ResourceDictionary {}
49
50impl ResourceProviderImpl for ResourceDictionary {
51    fn has_resources(this: &Self) -> bool {
52        if !this.inner.borrow().is_empty() {
53            return true;
54        }
55        if let Some(merged) = this.merged_dictionaries.get() {
56            if !merged.is_empty() {
57                return merged.snapshot().iter().any(|i| i.has_resources());
58            }
59        }
60        false
61    }
62
63    fn try_get_resource(this: &Self, key: &ResourceKey, theme: Option<&ThemeVariant>) -> Option<ResourceValue> {
64        if let Some(value) = this.try_get_value(key) {
65            return Some(value);
66        }
67
68        if this.theme_dictionaries.get().is_some() {
69            if let Some(theme) = theme {
70                if *theme != ThemeVariant::default() {
71                    if let Some(provider) = this.theme_dictionary(theme) {
72                        if let Some(value) = provider.try_get_resource(key, Some(theme)) {
73                            return Some(value);
74                        }
75                    }
76
77                    let mut theme_inherit = theme.inherit_variant();
78                    while let Some(inherit) = theme_inherit {
79                        if let Some(provider) = this.theme_dictionary(&inherit) {
80                            if let Some(value) = provider.try_get_resource(key, Some(theme)) {
81                                return Some(value);
82                            }
83                        }
84                        theme_inherit = inherit.inherit_variant();
85                    }
86                }
87            }
88
89            if let Some(provider) = this.theme_dictionary(&ThemeVariant::default()) {
90                if let Some(value) = provider.try_get_resource(key, theme) {
91                    return Some(value);
92                }
93            }
94        }
95
96        if let Some(merged) = this.merged_dictionaries.get() {
97            if !merged.is_empty() {
98                for provider in merged.snapshot().iter().rev() {
99                    if let Some(value) = provider.try_get_resource(key, theme) {
100                        return Some(value);
101                    }
102                }
103            }
104        }
105
106        None
107    }
108
109    fn on_add_owner(this: &Self, owner: &ResourceHostRef) {
110        let mut has_resources = !this.inner.borrow().is_empty();
111
112        if let Some(merged) = this.merged_dictionaries.get() {
113            for i in merged.snapshot().iter() {
114                i.add_owner(owner);
115                has_resources |= i.has_resources();
116            }
117        }
118
119        for (_, i) in this.theme_dictionaries_snapshot() {
120            i.add_owner(owner);
121            has_resources |= i.has_resources();
122        }
123
124        if has_resources {
125            owner.notify_hosted_resources_changed(ResourcesChangedEventArgs::create());
126        }
127    }
128
129    fn on_remove_owner(this: &Self, owner: &ResourceHostRef) {
130        let mut has_resources = !this.inner.borrow().is_empty();
131
132        if let Some(merged) = this.merged_dictionaries.get() {
133            for i in merged.snapshot().iter() {
134                i.remove_owner(owner);
135                has_resources |= i.has_resources();
136            }
137        }
138
139        for (_, i) in this.theme_dictionaries_snapshot() {
140            i.remove_owner(owner);
141            has_resources |= i.has_resources();
142        }
143
144        if has_resources {
145            owner.notify_hosted_resources_changed(ResourcesChangedEventArgs::create());
146        }
147    }
148}
149
150impl ResourceDictionary {
151    fn construct_from(base: ResourceProvider) -> Self {
152        Self {
153            base,
154            last_deferred_item_key: RefCell::new(None),
155            inner: RefCell::new(HashMap::default()),
156            merged_dictionaries: OnceCell::new(),
157            theme_dictionaries: OnceCell::new(),
158            key: RefCell::new(None),
159        }
160    }
161
162    /// Creates the class data; see [`FerroObject::construct`].
163    pub fn construct() -> Self {
164        Self::construct_from(ResourceProvider::construct())
165    }
166
167    /// Creates the class data of a dictionary that belongs to `owner`.
168    pub fn construct_with_owner(owner: &ResourceHostRef) -> Self {
169        Self::construct_from(ResourceProvider::construct_with_owner(owner))
170    }
171
172    /// Creates an empty resource dictionary.
173    pub fn new() -> Ref<Self> {
174        instantiate(Self::construct())
175    }
176
177    /// Creates an empty resource dictionary that belongs to `owner`.
178    pub fn with_owner(owner: impl Into<ResourceHostRef>) -> Ref<Self> {
179        instantiate(Self::construct_with_owner(&owner.into()))
180    }
181
182    /// The number of resources in the dictionary itself.
183    pub fn count(&self) -> usize {
184        self.inner.borrow().len()
185    }
186
187    /// Gets a resource of the dictionary itself; `None` (a null value) if the
188    /// key is not present.
189    pub fn get(&self, key: &ResourceKey) -> ResourceValue {
190        self.try_get_value(key).flatten()
191    }
192
193    /// Adds or replaces a resource.
194    pub fn set(&self, key: impl Into<ResourceKey>, value: ResourceValue) {
195        self.inner.borrow_mut().insert(key.into(), ResourceItem::Value(value));
196        self.raise_resources_changed();
197    }
198
199    /// The keys of the resources in the dictionary itself.
200    pub fn keys(&self) -> Vec<ResourceKey> {
201        self.inner.borrow().keys().cloned().collect()
202    }
203
204    /// The child resource dictionaries.
205    ///
206    /// The list is returned by handle: clones refer to the same list.
207    pub fn merged_dictionaries(&self) -> FerroList<Rc<dyn IResourceProvider>> {
208        self.merged_dictionaries_ref().clone()
209    }
210
211    fn merged_dictionaries_ref(&self) -> &FerroList<Rc<dyn IResourceProvider>> {
212        self.merged_dictionaries.get_or_init(|| {
213            let list = FerroList::new();
214            list.set_reset_behavior(ResetBehavior::Remove);
215            let weak = self.to_ref().downgrade();
216            list.add_collection_changed(Rc::new(
217                move |e: &NotifyCollectionChangedEventArgs<'_, Rc<dyn IResourceProvider>>| {
218                    let Some(this) = weak.upgrade() else { return };
219                    let added = |items: &[Rc<dyn IResourceProvider>]| {
220                        for x in items {
221                            if let Some(owner) = this.owner() {
222                                x.add_owner(&owner);
223                            }
224                        }
225                    };
226                    let removed = |items: &[Rc<dyn IResourceProvider>]| {
227                        for x in items {
228                            if let Some(owner) = this.owner() {
229                                x.remove_owner(&owner);
230                            }
231                        }
232                    };
233                    match e.action {
234                        NotifyCollectionChangedAction::Add => added(e.new_items),
235                        NotifyCollectionChangedAction::Remove => removed(e.old_items),
236                        NotifyCollectionChangedAction::Replace => {
237                            removed(e.old_items);
238                            added(e.new_items);
239                        }
240                        NotifyCollectionChangedAction::Reset => panic!("Dictionary reset not supported"),
241                        NotifyCollectionChangedAction::Move => {}
242                    }
243                },
244            ));
245            list
246        })
247    }
248
249    /// Adds a child resource dictionary.
250    pub fn add_merged_dictionary(&self, provider: impl Into<Rc<dyn IResourceProvider>>) {
251        self.merged_dictionaries().add(provider.into());
252    }
253
254    /// Removes a child resource dictionary. Returns whether it was present.
255    pub fn remove_merged_dictionary(&self, provider: &Rc<dyn IResourceProvider>) -> bool {
256        let list = self.merged_dictionaries();
257        let index = list.snapshot().iter().position(|p| resource_provider_ptr_eq(p, provider));
258        match index {
259            Some(index) => {
260                list.remove_at(index);
261                true
262            }
263            None => false,
264        }
265    }
266
267    /// The resource providers used for specific theme variants.
268    ///
269    /// The dictionary is returned by handle: clones refer to the same
270    /// dictionary. A provider added to it gets the owner of this dictionary;
271    /// a removed one loses it.
272    pub fn theme_dictionaries(&self) -> FerroDictionary<ThemeVariant, Rc<dyn IThemeVariantProvider>> {
273        self.theme_dictionaries
274            .get_or_init(|| {
275                let dictionary = FerroDictionary::with_capacity(2);
276                let weak = self.to_ref().downgrade();
277                dictionary.add_collection_changed(Rc::new(
278                    move |e: &NotifyCollectionChangedEventArgs<'_, (ThemeVariant, Rc<dyn IThemeVariantProvider>)>| {
279                        let Some(this) = weak.upgrade() else { return };
280                        let added = |items: &[(ThemeVariant, Rc<dyn IThemeVariantProvider>)]| {
281                            for (_, x) in items {
282                                if let Some(owner) = this.owner() {
283                                    x.add_owner(&owner);
284                                }
285                            }
286                        };
287                        let removed = |items: &[(ThemeVariant, Rc<dyn IThemeVariantProvider>)]| {
288                            for (_, x) in items {
289                                if let Some(owner) = this.owner() {
290                                    x.remove_owner(&owner);
291                                }
292                            }
293                        };
294                        match e.action {
295                            NotifyCollectionChangedAction::Add => added(e.new_items),
296                            NotifyCollectionChangedAction::Remove => removed(e.old_items),
297                            NotifyCollectionChangedAction::Replace | NotifyCollectionChangedAction::Move => {
298                                removed(e.old_items);
299                                added(e.new_items);
300                            }
301                            NotifyCollectionChangedAction::Reset => panic!("Dictionary reset not supported"),
302                        }
303                    },
304                ));
305                dictionary
306            })
307            .clone()
308    }
309
310    /// A snapshot of the resource providers used for specific theme variants.
311    pub fn theme_dictionaries_snapshot(&self) -> Vec<(ThemeVariant, Rc<dyn IThemeVariantProvider>)> {
312        match self.theme_dictionaries.get() {
313            Some(dictionaries) if !dictionaries.is_empty() => dictionaries.to_vec(),
314            _ => Vec::new(),
315        }
316    }
317
318    /// The resource provider used for the given theme variant.
319    pub fn theme_dictionary(&self, variant: &ThemeVariant) -> Option<Rc<dyn IThemeVariantProvider>> {
320        self.theme_dictionaries.get().and_then(|d| d.try_get_value(variant))
321    }
322
323    /// Adds a resource provider for a theme variant. Panics if the variant
324    /// already has one.
325    pub fn add_theme_dictionary(&self, variant: ThemeVariant, provider: impl Into<Rc<dyn IThemeVariantProvider>>) {
326        self.theme_dictionaries().add(variant, provider.into());
327    }
328
329    /// Adds or replaces the resource provider of a theme variant.
330    pub fn set_theme_dictionary(&self, variant: ThemeVariant, provider: impl Into<Rc<dyn IThemeVariantProvider>>) {
331        self.theme_dictionaries().set(variant, provider.into());
332    }
333
334    /// Removes the resource provider of a theme variant. Returns whether it
335    /// was present.
336    pub fn remove_theme_dictionary(&self, variant: &ThemeVariant) -> bool {
337        self.theme_dictionaries.get().is_some_and(|d| d.remove(variant))
338    }
339
340    /// The key that is used in the theme dictionaries of the containing
341    /// resource dictionary.
342    pub fn key(&self) -> Option<ThemeVariant> {
343        self.key.borrow().clone()
344    }
345
346    pub fn set_key(&self, value: Option<ThemeVariant>) {
347        *self.key.borrow_mut() = value;
348    }
349
350    /// Adds a resource. Panics if the key is already present.
351    pub fn add(&self, key: impl Into<ResourceKey>, value: ResourceValue) {
352        self.add_item(key.into(), ResourceItem::Value(value));
353    }
354
355    /// Adds a resource value. Panics if the key is already present.
356    pub fn add_value<T: PartialEq + 'static>(&self, key: impl Into<ResourceKey>, value: T) {
357        self.add(key, Some(Rc::new(value)));
358    }
359
360    fn add_item(&self, key: ResourceKey, item: ResourceItem) {
361        {
362            let mut inner = self.inner.borrow_mut();
363            if inner.contains_key(&key) {
364                panic!("An item with the same key has already been added. Key: {key}");
365            }
366            inner.insert(key, item);
367        }
368        self.raise_resources_changed();
369    }
370
371    /// Adds a resource that is created by `factory` the first time it is
372    /// requested.
373    pub fn add_deferred_fn(
374        &self,
375        key: impl Into<ResourceKey>,
376        factory: impl Fn(Option<&Rc<dyn IServiceProvider>>) -> Option<BoxedValue> + 'static,
377    ) {
378        self.add_item(key.into(), ResourceItem::Deferred(Rc::new(DeferredItem(factory))));
379    }
380
381    /// Adds a resource that is built from `deferred_content` the first time
382    /// it is requested.
383    pub fn add_deferred(&self, key: impl Into<ResourceKey>, deferred_content: Rc<dyn IDeferredContent>) {
384        self.add_item(key.into(), ResourceItem::Deferred(deferred_content));
385    }
386
387    /// Adds a resource that is built from `deferred_content` every time it is
388    /// requested.
389    pub fn add_not_shared_deferred(&self, key: impl Into<ResourceKey>, deferred_content: Rc<dyn IDeferredContent>) {
390        self.add_item(key.into(), ResourceItem::NotSharedDeferred(deferred_content));
391    }
392
393    /// Adds or replaces multiple resources, raising a single notification.
394    pub fn set_items(&self, values: impl IntoIterator<Item = (ResourceKey, ResourceValue)>) {
395        {
396            let mut inner = self.inner.borrow_mut();
397            for (key, value) in values {
398                inner.insert(key, ResourceItem::Value(value));
399            }
400        }
401        self.raise_resources_changed();
402    }
403
404    /// Removes all resources of the dictionary itself.
405    pub fn clear(&self) {
406        let cleared = {
407            let mut inner = self.inner.borrow_mut();
408            if inner.is_empty() {
409                false
410            } else {
411                inner.clear();
412                true
413            }
414        };
415        if cleared {
416            self.raise_resources_changed();
417        }
418    }
419
420    /// Whether the dictionary itself contains the key.
421    pub fn contains_key(&self, key: &ResourceKey) -> bool {
422        self.inner.borrow().contains_key(key)
423    }
424
425    /// Removes a resource. Returns whether it was present.
426    pub fn remove(&self, key: &ResourceKey) -> bool {
427        let removed = self.inner.borrow_mut().remove(key).is_some();
428        if removed {
429            self.raise_resources_changed();
430        }
431        removed
432    }
433
434    /// Gets a resource of the dictionary itself, building it if it is
435    /// deferred.
436    pub fn try_get_value(&self, key: &ResourceKey) -> Option<ResourceValue> {
437        let item = self.inner.borrow().get(key).cloned()?;
438        let (deferred, shared) = match item {
439            ResourceItem::Value(value) => return Some(value),
440            ResourceItem::Deferred(deferred) => (deferred, true),
441            ResourceItem::NotSharedDeferred(deferred) => (deferred, false),
442        };
443
444        // Avoid simple reentrancy, which could commonly occur on redefining
445        // the resource.
446        if self.last_deferred_item_key.borrow().as_ref() == Some(key) {
447            return None;
448        }
449
450        struct Reset<'a>(&'a RefCell<Option<ResourceKey>>);
451        impl Drop for Reset<'_> {
452            fn drop(&mut self) {
453                *self.0.borrow_mut() = None;
454            }
455        }
456
457        *self.last_deferred_item_key.borrow_mut() = Some(key.clone());
458        let _reset = Reset(&self.last_deferred_item_key);
459        let value = deferred.build(None);
460        if shared {
461            self.inner.borrow_mut().insert(key.clone(), ResourceItem::Value(value.clone()));
462        }
463        Some(value)
464    }
465
466    /// Ensures that the dictionary can hold up to `capacity` resources
467    /// without further expansion of its backing storage.
468    pub fn ensure_capacity(&self, capacity: usize) {
469        let mut inner = self.inner.borrow_mut();
470        let additional = capacity.saturating_sub(inner.len());
471        inner.reserve(additional);
472    }
473
474    /// Whether the key is present and its resource has not been built yet.
475    pub fn contains_deferred_key(&self, key: &ResourceKey) -> bool {
476        matches!(
477            self.inner.borrow().get(key),
478            Some(ResourceItem::Deferred(_) | ResourceItem::NotSharedDeferred(_))
479        )
480    }
481}
482
483/// The interfaces of a resource dictionary object.
484struct ResourceDictionaryHandle(Ref<ResourceDictionary>);
485
486impl IResourceNode for ResourceDictionaryHandle {
487    fn reference_id(&self) -> *const () {
488        let object: &FerroObject = (*self.0).upcast();
489        object as *const FerroObject as *const ()
490    }
491
492    fn has_resources(&self) -> bool {
493        self.0.has_resources()
494    }
495
496    fn try_get_resource(&self, key: &ResourceKey, theme: Option<&ThemeVariant>) -> Option<ResourceValue> {
497        self.0.try_get_resource(key, theme)
498    }
499}
500
501impl IResourceProvider for ResourceDictionaryHandle {
502    fn owner(&self) -> Option<ResourceHostRef> {
503        self.0.owner()
504    }
505
506    fn owner_changed(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
507        self.0.subscribe_owner_changed(handler)
508    }
509
510    fn add_owner(&self, owner: &ResourceHostRef) {
511        self.0.add_owner(owner)
512    }
513
514    fn remove_owner(&self, owner: &ResourceHostRef) {
515        self.0.remove_owner(owner)
516    }
517
518    fn as_object(&self) -> Option<&FerroObject> {
519        Some((*self.0).upcast())
520    }
521}
522
523impl IThemeVariantProvider for ResourceDictionaryHandle {
524    fn key(&self) -> Option<ThemeVariant> {
525        self.0.key()
526    }
527
528    fn set_key(&self, value: Option<ThemeVariant>) {
529        self.0.set_key(value)
530    }
531}
532
533impl IResourceDictionary for ResourceDictionaryHandle {
534    fn merged_dictionaries_snapshot(&self) -> Rc<Vec<Rc<dyn IResourceProvider>>> {
535        self.0.merged_dictionaries().snapshot()
536    }
537
538    fn theme_dictionaries_snapshot(&self) -> Vec<(ThemeVariant, Rc<dyn IThemeVariantProvider>)> {
539        self.0.theme_dictionaries_snapshot()
540    }
541
542    fn count(&self) -> usize {
543        self.0.count()
544    }
545
546    fn add(&self, key: ResourceKey, value: ResourceValue) {
547        self.0.add(key, value)
548    }
549
550    fn set(&self, key: ResourceKey, value: ResourceValue) {
551        self.0.set(key, value)
552    }
553
554    fn contains_key(&self, key: &ResourceKey) -> bool {
555        self.0.contains_key(key)
556    }
557
558    fn remove(&self, key: &ResourceKey) -> bool {
559        self.0.remove(key)
560    }
561
562    fn try_get_value(&self, key: &ResourceKey) -> Option<ResourceValue> {
563        self.0.try_get_value(key)
564    }
565
566    fn clear(&self) {
567        self.0.clear()
568    }
569}
570
571impl<T: ObjectType + Upcast<ResourceDictionary>> From<Ref<T>> for Rc<dyn IThemeVariantProvider> {
572    fn from(value: Ref<T>) -> Self {
573        Rc::new(ResourceDictionaryHandle(value.upcast()))
574    }
575}
576
577impl<T: ObjectType + Upcast<ResourceDictionary>> From<&Ref<T>> for Rc<dyn IThemeVariantProvider> {
578    fn from(value: &Ref<T>) -> Self {
579        Rc::new(ResourceDictionaryHandle(value.clone().upcast()))
580    }
581}
582
583impl<T: ObjectType + Upcast<ResourceDictionary>> From<Ref<T>> for Rc<dyn IResourceDictionary> {
584    fn from(value: Ref<T>) -> Self {
585        Rc::new(ResourceDictionaryHandle(value.upcast()))
586    }
587}