Skip to main content

ferroui_base/styling/
styles.rs

1use super::{style_ptr_eq, ControlTheme, IStyle, SelectorMatchResult, Style, StyleHostRef, ThemeVariant};
2use crate::collections::{
3    CollectionChangedHandler, FerroList, NotifyCollectionChangedAction, NotifyCollectionChangedEventArgs,
4    ResetBehavior,
5};
6use crate::controls::{
7    IResourceNode, IResourceProvider, ResourceDictionary, ResourceHostRef, ResourceKey, ResourceValue,
8    WeakResourceHost,
9};
10use crate::property_store::FrameType;
11use crate::reactive::{Disposable, IDisposable};
12use crate::utilities::HandlerList;
13use crate::{
14    ferro_class, instantiate, FerroObject, FerroObjectImpl, FerroObjectImplExt, ObjectType, Ref, StyledElement,
15    Upcast,
16};
17use std::cell::RefCell;
18use std::rc::Rc;
19
20/// A style that consists of a number of child styles.
21#[repr(C)]
22pub struct Styles {
23    base: FerroObject,
24    styles: FerroList<Rc<dyn IStyle>>,
25    owner: RefCell<Option<WeakResourceHost>>,
26    resources: RefCell<Option<Ref<ResourceDictionary>>>,
27    collection_changed: HandlerList<CollectionChangedHandler<Rc<dyn IStyle>>>,
28    owner_changed: HandlerList<dyn Fn()>,
29}
30
31ferro_class!(Styles: FerroObject);
32crate::ferro_class_info!(Styles {
33    new: Styles::new,
34    interfaces: [Rc<dyn IStyle>, Rc<dyn IResourceProvider>],
35});
36
37impl FerroObjectImpl for Styles {
38    fn constructed(this: &Self) {
39        Self::parent_constructed(this);
40
41        this.styles.set_reset_behavior(ResetBehavior::Remove);
42        let weak = this.to_ref().downgrade();
43        this.styles.add_collection_changed(Rc::new(
44            move |e: &NotifyCollectionChangedEventArgs<'_, Rc<dyn IStyle>>| {
45                if let Some(this) = weak.upgrade() {
46                    this.on_collection_changed(e);
47                }
48            },
49        ));
50        this.styles.set_validate(Some(Rc::new(|item: &Rc<dyn IStyle>| {
51            if let Some(theme) = item.as_object().and_then(|o| o.downcast_ref::<ControlTheme>()) {
52                panic!(
53                    "ControlTheme (for {}) cannot be added to a Styles collection.",
54                    theme.target_type().map(|t| t.name()).unwrap_or_default()
55                );
56            }
57        })));
58    }
59}
60
61impl Styles {
62    /// Creates the class data; see [`FerroObject::construct`].
63    pub fn construct() -> Self {
64        Self {
65            base: FerroObject::construct(),
66            styles: FerroList::new(),
67            owner: RefCell::new(None),
68            resources: RefCell::new(None),
69            collection_changed: HandlerList::new(),
70            owner_changed: HandlerList::new(),
71        }
72    }
73
74    /// Creates an empty styles collection.
75    pub fn new() -> Ref<Self> {
76        instantiate(Self::construct())
77    }
78
79    /// Creates an empty styles collection that belongs to `owner`.
80    pub fn with_owner(owner: impl Into<ResourceHostRef>) -> Ref<Self> {
81        let this = Self::construct();
82        *this.owner.borrow_mut() = Some(owner.into().downgrade());
83        instantiate(this)
84    }
85
86    /// Raised when the collection changes. Disposing the returned handle
87    /// unsubscribes.
88    pub fn collection_changed(
89        &self,
90        handler: impl for<'a> Fn(&NotifyCollectionChangedEventArgs<'a, Rc<dyn IStyle>>) + 'static,
91    ) -> Rc<dyn IDisposable> {
92        let token = self.collection_changed.add(Rc::new(handler));
93        let weak = self.to_ref().downgrade();
94        Disposable::create(move || {
95            if let Some(this) = weak.upgrade() {
96                this.collection_changed.remove(token);
97            }
98        })
99    }
100
101    /// Raised when the owner of the collection changes.
102    pub fn owner_changed(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
103        self.subscribe_owner_changed(Rc::new(handler))
104    }
105
106    fn subscribe_owner_changed(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
107        let token = self.owner_changed.add(handler);
108        let weak = self.to_ref().downgrade();
109        Disposable::create(move || {
110            if let Some(this) = weak.upgrade() {
111                this.owner_changed.remove(token);
112            }
113        })
114    }
115
116    /// The number of styles.
117    #[inline]
118    pub fn count(&self) -> usize {
119        self.styles.count()
120    }
121
122    /// The owner of the collection.
123    pub fn owner(&self) -> Option<ResourceHostRef> {
124        let owner = self.owner.borrow().clone();
125        owner.and_then(|o| o.upgrade())
126    }
127
128    fn set_owner(&self, value: Option<&ResourceHostRef>) {
129        let old = self.owner();
130        if old.as_ref() != value {
131            *self.owner.borrow_mut() = value.map(ResourceHostRef::downgrade);
132            if !self.owner_changed.is_empty() {
133                for (_, handler) in self.owner_changed.snapshot().iter() {
134                    handler();
135                }
136            }
137        }
138    }
139
140    /// The dictionary of style resources.
141    pub fn resources(&self) -> Ref<ResourceDictionary> {
142        if let Some(resources) = &*self.resources.borrow() {
143            return resources.clone();
144        }
145        let resources = ResourceDictionary::new();
146        self.set_resources(resources.clone());
147        resources
148    }
149
150    pub fn set_resources(&self, value: Ref<ResourceDictionary>) {
151        let current_owner = self.owner();
152
153        if let Some(owner) = &current_owner {
154            let old = self.resources.borrow().clone();
155            if let Some(old) = old {
156                old.remove_owner(owner);
157            }
158        }
159
160        *self.resources.borrow_mut() = Some(value.clone());
161
162        if let Some(owner) = &current_owner {
163            value.add_owner(owner);
164        }
165    }
166
167    /// Whether the collection or any of its styles has resources.
168    pub fn has_resources(&self) -> bool {
169        if self.resources.borrow().as_ref().is_some_and(|r| r.count() > 0) {
170            return true;
171        }
172
173        if self.styles.is_empty() {
174            return false;
175        }
176
177        self.styles
178            .snapshot()
179            .iter()
180            .any(|i| i.as_resource_provider().is_some_and(|provider| provider.has_resources()))
181    }
182
183    /// The style at `index`. Panics if out of range.
184    pub fn get(&self, index: usize) -> Rc<dyn IStyle> {
185        self.styles.get(index)
186    }
187
188    /// Replaces the style at `index`.
189    ///
190    /// The replacement is notified as a removal followed by an insertion.
191    pub fn set(&self, index: usize, value: impl Into<Rc<dyn IStyle>>) {
192        self.styles.remove_at(index);
193        self.styles.insert(index, value.into());
194    }
195
196    /// A snapshot of the styles.
197    #[inline]
198    pub fn snapshot(&self) -> Rc<Vec<Rc<dyn IStyle>>> {
199        self.styles.snapshot()
200    }
201
202    /// Tries to find a resource in the collection's resources and then in
203    /// its styles, last style first.
204    pub fn try_get_resource(&self, key: &ResourceKey, theme: Option<&ThemeVariant>) -> Option<ResourceValue> {
205        let resources = self.resources.borrow().clone();
206        if let Some(resources) = resources {
207            if let Some(value) = resources.try_get_resource(key, theme) {
208                return Some(value);
209            }
210        }
211
212        if !self.styles.is_empty() {
213            for style in self.styles.snapshot().iter().rev() {
214                if let Some(value) = style.try_get_resource(key, theme) {
215                    return Some(value);
216                }
217            }
218        }
219
220        None
221    }
222
223    pub fn add_range(&self, items: impl IntoIterator<Item = Rc<dyn IStyle>>) {
224        self.styles.add_range(items)
225    }
226
227    pub fn insert_range(&self, index: usize, items: impl IntoIterator<Item = Rc<dyn IStyle>>) {
228        self.styles.insert_range(index, items)
229    }
230
231    pub fn move_item(&self, old_index: usize, new_index: usize) {
232        self.styles.move_item(old_index, new_index)
233    }
234
235    pub fn move_range(&self, old_index: usize, count: usize, new_index: usize) {
236        self.styles.move_range(old_index, count, new_index)
237    }
238
239    pub fn remove_all(&self, items: impl IntoIterator<Item = Rc<dyn IStyle>>) {
240        for item in items {
241            self.remove(item);
242        }
243    }
244
245    pub fn remove_range(&self, index: usize, count: usize) {
246        self.styles.remove_range(index, count)
247    }
248
249    /// The index of a style, if it is in the collection.
250    pub fn index_of(&self, item: impl Into<Rc<dyn IStyle>>) -> Option<usize> {
251        let item = item.into();
252        self.styles.snapshot().iter().position(|s| style_ptr_eq(s, &item))
253    }
254
255    pub fn insert(&self, index: usize, item: impl Into<Rc<dyn IStyle>>) {
256        self.styles.insert(index, item.into())
257    }
258
259    pub fn remove_at(&self, index: usize) {
260        self.styles.remove_at(index)
261    }
262
263    /// Adds a style to the collection.
264    pub fn add(&self, item: impl Into<Rc<dyn IStyle>>) {
265        self.styles.add(item.into())
266    }
267
268    /// Removes all styles from the collection.
269    pub fn clear(&self) {
270        self.styles.clear()
271    }
272
273    pub fn contains(&self, item: impl Into<Rc<dyn IStyle>>) -> bool {
274        self.index_of(item).is_some()
275    }
276
277    /// Removes a style from the collection. Returns whether it was present.
278    pub fn remove(&self, item: impl Into<Rc<dyn IStyle>>) -> bool {
279        match self.index_of(item) {
280            Some(index) => {
281                self.styles.remove_at(index);
282                true
283            }
284            None => false,
285        }
286    }
287
288    /// Adds an owner to the collection and the resource providers in it.
289    pub fn add_owner(&self, owner: &ResourceHostRef) {
290        if self.owner().is_some() {
291            panic!("The Styles already has a owner.");
292        }
293
294        self.set_owner(Some(owner));
295        let resources = self.resources.borrow().clone();
296        if let Some(resources) = resources {
297            resources.add_owner(owner);
298        }
299
300        for child in self.styles.snapshot().iter() {
301            if let Some(r) = child.as_resource_provider() {
302                r.add_owner(owner);
303            }
304        }
305    }
306
307    /// Removes the owner of the collection and the resource providers in it.
308    pub fn remove_owner(&self, owner: &ResourceHostRef) {
309        if self.owner().as_ref() == Some(owner) {
310            self.set_owner(None);
311            let resources = self.resources.borrow().clone();
312            if let Some(resources) = resources {
313                resources.remove_owner(owner);
314            }
315
316            for child in self.styles.snapshot().iter() {
317                if let Some(r) = child.as_resource_provider() {
318                    r.remove_owner(owner);
319                }
320            }
321        }
322    }
323
324    /// Attaches the styles of the collection (but not of nested collections)
325    /// that match `target`.
326    #[allow(dead_code)]
327    pub(crate) fn try_attach(&self, target: &StyledElement, host: Option<&StyleHostRef>) -> SelectorMatchResult {
328        let mut result = SelectorMatchResult::NeverThisType;
329
330        for s in self.styles.snapshot().iter() {
331            let Some(style) = s.as_object().and_then(|o| o.downcast_ref::<Style>()) else { continue };
332
333            let r = style.try_attach(target, host, FrameType::Style);
334            if r > result {
335                result = r;
336            }
337        }
338
339        result
340    }
341
342    fn internal_add(items: &[Rc<dyn IStyle>], owner: Option<&ResourceHostRef>) {
343        if let Some(owner) = owner {
344            for item in items {
345                if let Some(provider) = item.as_resource_provider() {
346                    provider.add_owner(owner);
347                }
348            }
349
350            if let Some(host) = owner.as_style_host() {
351                host.styles_added(items);
352            }
353        }
354    }
355
356    fn internal_remove(items: &[Rc<dyn IStyle>], owner: Option<&ResourceHostRef>) {
357        if let Some(owner) = owner {
358            for item in items {
359                if let Some(provider) = item.as_resource_provider() {
360                    provider.remove_owner(owner);
361                }
362            }
363
364            if let Some(host) = owner.as_style_host() {
365                host.styles_removed(items);
366            }
367        }
368    }
369
370    fn on_collection_changed(&self, e: &NotifyCollectionChangedEventArgs<'_, Rc<dyn IStyle>>) {
371        if e.action == NotifyCollectionChangedAction::Reset {
372            panic!("Reset should not be called on Styles.");
373        }
374
375        let current_owner = self.owner();
376
377        match e.action {
378            NotifyCollectionChangedAction::Add => Self::internal_add(e.new_items, current_owner.as_ref()),
379            NotifyCollectionChangedAction::Remove => Self::internal_remove(e.old_items, current_owner.as_ref()),
380            NotifyCollectionChangedAction::Replace => {
381                Self::internal_remove(e.old_items, current_owner.as_ref());
382                Self::internal_add(e.new_items, current_owner.as_ref());
383            }
384            _ => {}
385        }
386
387        if !self.collection_changed.is_empty() {
388            for (_, handler) in self.collection_changed.snapshot().iter() {
389                handler(e);
390            }
391        }
392    }
393}
394
395/// The style and resource provider interfaces of a styles collection object.
396struct StylesHandle(Ref<Styles>);
397
398impl IResourceNode for StylesHandle {
399    fn reference_id(&self) -> *const () {
400        let object: &FerroObject = (*self.0).upcast();
401        object as *const FerroObject as *const ()
402    }
403
404    fn has_resources(&self) -> bool {
405        self.0.has_resources()
406    }
407
408    fn try_get_resource(&self, key: &ResourceKey, theme: Option<&ThemeVariant>) -> Option<ResourceValue> {
409        self.0.try_get_resource(key, theme)
410    }
411}
412
413impl IResourceProvider for StylesHandle {
414    fn owner(&self) -> Option<ResourceHostRef> {
415        self.0.owner()
416    }
417
418    fn owner_changed(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
419        self.0.subscribe_owner_changed(handler)
420    }
421
422    fn add_owner(&self, owner: &ResourceHostRef) {
423        self.0.add_owner(owner)
424    }
425
426    fn remove_owner(&self, owner: &ResourceHostRef) {
427        self.0.remove_owner(owner)
428    }
429
430    fn as_object(&self) -> Option<&FerroObject> {
431        Some((*self.0).upcast())
432    }
433}
434
435impl IStyle for StylesHandle {
436    fn children(&self) -> Rc<Vec<Rc<dyn IStyle>>> {
437        self.0.styles.snapshot()
438    }
439
440    fn as_object(&self) -> Option<&FerroObject> {
441        Some((*self.0).upcast())
442    }
443
444    fn as_resource_provider(&self) -> Option<&dyn IResourceProvider> {
445        Some(self)
446    }
447}
448
449/// Converts a handle of a styles collection (or a class derived from it)
450/// into a style handle.
451pub fn styles_as_style<T: ObjectType + Upcast<Styles>>(styles: &Ref<T>) -> Rc<dyn IStyle> {
452    Rc::new(StylesHandle(styles.clone().upcast()))
453}
454
455impl From<Ref<Styles>> for Rc<dyn IStyle> {
456    fn from(value: Ref<Styles>) -> Self {
457        Rc::new(StylesHandle(value))
458    }
459}
460
461impl From<Ref<Styles>> for Rc<dyn IResourceProvider> {
462    fn from(value: Ref<Styles>) -> Self {
463        Rc::new(StylesHandle(value))
464    }
465}
466
467impl From<&Ref<Styles>> for Rc<dyn IResourceProvider> {
468    fn from(value: &Ref<Styles>) -> Self {
469        Rc::new(StylesHandle(value.clone()))
470    }
471}
472
473impl From<&Ref<Styles>> for Rc<dyn IStyle> {
474    fn from(value: &Ref<Styles>) -> Self {
475        Rc::new(StylesHandle(value.clone()))
476    }
477}