Skip to main content

ferroui_base/controls/
resource_provider.rs

1use super::{
2    IResourceNode, IResourceProvider, ResourceHostRef, ResourceKey, ResourceValue, ResourcesChangedEventArgs,
3    WeakResourceHost,
4};
5use crate::reactive::{Disposable, IDisposable};
6use crate::styling::ThemeVariant;
7use crate::utilities::HandlerList;
8use crate::{ferro_class, FerroObject, FerroObjectImpl, ObjectType, Ref, Upcast};
9use std::cell::RefCell;
10use std::rc::Rc;
11
12/// Base implementation for resource providers.
13#[repr(C)]
14pub struct ResourceProvider {
15    base: FerroObject,
16    owner: RefCell<Option<WeakResourceHost>>,
17    owner_changed: HandlerList<dyn Fn()>,
18}
19
20ferro_class! {
21    ResourceProvider: FerroObject, virtuals ResourceProviderImpl: FerroObjectImpl {
22        /// Whether the provider has resources.
23        fn has_resources(this) -> bool;
24        /// Tries to find a resource within the provider.
25        fn try_get_resource(this, key: &ResourceKey, theme: Option<&ThemeVariant>) -> Option<ResourceValue>;
26        /// Handles when the owner is added. The base method notifies the
27        /// owner about its resources changing, if the provider has any.
28        fn on_add_owner(this, owner: &ResourceHostRef);
29        /// Handles when the owner is removed. The base method notifies the
30        /// owner about its resources changing, if the provider has any.
31        fn on_remove_owner(this, owner: &ResourceHostRef);
32    }
33}
34
35crate::ferro_class_info!(ResourceProvider { interfaces: [Rc<dyn IResourceProvider>] });
36
37impl FerroObjectImpl for ResourceProvider {}
38
39impl ResourceProviderImpl for ResourceProvider {
40    fn has_resources(_this: &Self) -> bool {
41        false
42    }
43
44    fn try_get_resource(_this: &Self, _key: &ResourceKey, _theme: Option<&ThemeVariant>) -> Option<ResourceValue> {
45        None
46    }
47
48    fn on_add_owner(this: &Self, owner: &ResourceHostRef) {
49        if this.has_resources() {
50            owner.notify_hosted_resources_changed(ResourcesChangedEventArgs::create());
51        }
52    }
53
54    fn on_remove_owner(this: &Self, owner: &ResourceHostRef) {
55        if this.has_resources() {
56            owner.notify_hosted_resources_changed(ResourcesChangedEventArgs::create());
57        }
58    }
59}
60
61impl ResourceProvider {
62    /// Creates the class data of a provider without an owner.
63    pub fn construct() -> Self {
64        Self { base: FerroObject::construct(), owner: RefCell::new(None), owner_changed: HandlerList::new() }
65    }
66
67    /// Creates the class data of a provider that belongs to `owner`.
68    pub fn construct_with_owner(owner: &ResourceHostRef) -> Self {
69        Self {
70            base: FerroObject::construct(),
71            owner: RefCell::new(Some(owner.downgrade())),
72            owner_changed: HandlerList::new(),
73        }
74    }
75
76    /// The owner of the resource provider.
77    pub fn owner(&self) -> Option<ResourceHostRef> {
78        let owner = self.owner.borrow().clone();
79        owner.and_then(|o| o.upgrade())
80    }
81
82    fn set_owner(&self, value: Option<&ResourceHostRef>) {
83        let old = self.owner();
84        if old.as_ref() != value {
85            *self.owner.borrow_mut() = value.map(ResourceHostRef::downgrade);
86            if !self.owner_changed.is_empty() {
87                for (_, handler) in self.owner_changed.snapshot().iter() {
88                    handler();
89                }
90            }
91        }
92    }
93
94    /// Raised when the owner of the resource provider changes.
95    pub fn owner_changed(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
96        self.subscribe_owner_changed(Rc::new(handler))
97    }
98
99    pub(crate) fn subscribe_owner_changed(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
100        let token = self.owner_changed.add(handler);
101        let weak = self.to_ref().downgrade();
102        Disposable::create(move || {
103            if let Some(this) = weak.upgrade() {
104                this.owner_changed.remove(token);
105            }
106        })
107    }
108
109    /// Notifies the owner, if any, that the resources of the provider have
110    /// changed.
111    pub fn raise_resources_changed(&self) {
112        if let Some(owner) = self.owner() {
113            owner.notify_hosted_resources_changed(ResourcesChangedEventArgs::create());
114        }
115    }
116
117    /// Adds an owner to the resource provider.
118    pub fn add_owner(&self, owner: &ResourceHostRef) {
119        if self.owner().is_some() {
120            panic!("The ResourceDictionary already has a parent.");
121        }
122        self.set_owner(Some(owner));
123        self.on_add_owner(owner);
124    }
125
126    /// Removes a resource provider owner.
127    pub fn remove_owner(&self, owner: &ResourceHostRef) {
128        if self.owner().as_ref() == Some(owner) {
129            self.set_owner(None);
130            self.on_remove_owner(owner);
131        }
132    }
133}
134
135/// The resource provider interface of a provider object.
136struct ResourceProviderHandle(Ref<ResourceProvider>);
137
138impl IResourceNode for ResourceProviderHandle {
139    fn reference_id(&self) -> *const () {
140        let object: &FerroObject = (*self.0).upcast();
141        object as *const FerroObject as *const ()
142    }
143
144    fn has_resources(&self) -> bool {
145        self.0.has_resources()
146    }
147
148    fn try_get_resource(&self, key: &ResourceKey, theme: Option<&ThemeVariant>) -> Option<ResourceValue> {
149        self.0.try_get_resource(key, theme)
150    }
151}
152
153impl IResourceProvider for ResourceProviderHandle {
154    fn owner(&self) -> Option<ResourceHostRef> {
155        self.0.owner()
156    }
157
158    fn owner_changed(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
159        self.0.subscribe_owner_changed(handler)
160    }
161
162    fn add_owner(&self, owner: &ResourceHostRef) {
163        self.0.add_owner(owner)
164    }
165
166    fn remove_owner(&self, owner: &ResourceHostRef) {
167        self.0.remove_owner(owner)
168    }
169
170    fn as_object(&self) -> Option<&FerroObject> {
171        Some((*self.0).upcast())
172    }
173}
174
175impl<T: ObjectType + Upcast<ResourceProvider>> From<Ref<T>> for Rc<dyn IResourceProvider> {
176    fn from(value: Ref<T>) -> Self {
177        Rc::new(ResourceProviderHandle(value.upcast()))
178    }
179}
180
181impl<T: ObjectType + Upcast<ResourceProvider>> From<&Ref<T>> for Rc<dyn IResourceProvider> {
182    fn from(value: &Ref<T>) -> Self {
183        Rc::new(ResourceProviderHandle(value.clone().upcast()))
184    }
185}