Skip to main content

ferroui_base/
styled_property.rs

1use crate::data::{BindingPriority, BindingValue};
2use crate::ferro_property::{FerroPropertyInit, NotifyingCallback, PropertyRoutes};
3use crate::ferro_property_metadata::MetadataTable;
4use crate::property_store::{EffectiveValueDyn, IValueEntry};
5use crate::reactive::{IDisposable, IObservable};
6use crate::{
7    BoxedValue, ObjectType, DoNothingType, FerroObject, FerroProperty, FerroPropertyMetadata,
8    FerroPropertyRegistry, PropertyValue, StyledPropertyMetadata, TypeInfo, UnsetValueType,
9};
10use std::any::Any;
11use std::cell::RefCell;
12use std::ops::Deref;
13use std::rc::Rc;
14
15/// A styled property: a property whose value is held by the property store
16/// and can come from local values, styles, templates, animations, bindings and
17/// inheritance, resolved by priority.
18pub struct StyledProperty<T: PropertyValue> {
19    base: FerroProperty,
20    metadata: MetadataTable<StyledPropertyMetadata<T>>,
21    /// For performance, the default value is cached while there is only one,
22    /// avoiding a metadata lookup that may walk the class hierarchy.
23    single_default_value: RefCell<Option<T>>,
24    validate: Option<Box<dyn Fn(&T) -> bool>>,
25}
26
27impl<T: PropertyValue> Deref for StyledProperty<T> {
28    type Target = FerroProperty;
29    #[inline]
30    fn deref(&self) -> &FerroProperty {
31        &self.base
32    }
33}
34
35impl<T: PropertyValue> StyledProperty<T> {
36    #[allow(clippy::too_many_arguments)]
37    pub(crate) fn new(
38        name: &str,
39        owner_type: &'static TypeInfo,
40        host_type: &'static TypeInfo,
41        metadata: StyledPropertyMetadata<T>,
42        inherits: bool,
43        validate: Option<Box<dyn Fn(&T) -> bool>>,
44        notifying: Option<NotifyingCallback>,
45        is_attached: bool,
46    ) -> Self {
47        let default_value = metadata.default_value().clone();
48        if let Some(validate) = &validate {
49            if !validate(&default_value) {
50                panic!("default value is not valid for property '{name}'");
51            }
52        }
53        Self {
54            base: FerroProperty::new::<T>(
55                name,
56                owner_type,
57                FerroPropertyInit { inherits, is_attached, is_direct: false, is_read_only: false },
58                notifying,
59            ),
60            metadata: MetadataTable::new(host_type, metadata),
61            single_default_value: RefCell::new(Some(default_value)),
62            validate,
63        }
64    }
65
66    #[allow(clippy::too_many_arguments)]
67    pub(crate) fn create(
68        name: &str,
69        owner_type: &'static TypeInfo,
70        host_type: &'static TypeInfo,
71        metadata: StyledPropertyMetadata<T>,
72        inherits: bool,
73        validate: Option<Box<dyn Fn(&T) -> bool>>,
74        notifying: Option<NotifyingCallback>,
75        is_attached: bool,
76    ) -> &'static Self {
77        let property: &'static Self = Box::leak(Box::new(Self::new(
78            name, owner_type, host_type, metadata, inherits, validate, notifying, is_attached,
79        )));
80        property.base.set_routes(property);
81        property
82    }
83
84    /// The untyped base of the property.
85    #[inline]
86    pub fn as_property(&'static self) -> &'static FerroProperty {
87        &self.base
88    }
89
90    /// The value validation callback for the property.
91    #[inline]
92    pub fn validate_value(&self) -> Option<&dyn Fn(&T) -> bool> {
93        self.validate.as_deref()
94    }
95
96    #[inline]
97    pub(crate) fn is_valid(&self, value: &T) -> bool {
98        match &self.validate {
99            Some(validate) => validate(value),
100            None => true,
101        }
102    }
103
104    /// Registers the property on another class.
105    pub fn add_owner<TOwner: ObjectType>(&'static self) -> &'static Self {
106        FerroPropertyRegistry::instance().register(TOwner::TYPE, self);
107        self
108    }
109
110    /// Registers the property on another class, with metadata for that class.
111    pub fn add_owner_with<TOwner: ObjectType>(&'static self, metadata: StyledPropertyMetadata<T>) -> &'static Self {
112        FerroPropertyRegistry::instance().register(TOwner::TYPE, self);
113        self.override_metadata::<TOwner>(metadata);
114        self
115    }
116
117    /// Coerces `base_value` with the coercion callback registered for the
118    /// type of `instance`, if any.
119    pub fn coerce_value(&self, instance: &FerroObject, base_value: T) -> T {
120        let metadata = self.get_metadata_for(instance);
121        match metadata.coerce_value() {
122            Some(coerce) => coerce(instance, base_value),
123            None => base_value,
124        }
125    }
126
127    /// Gets the default value for the property on the specified type.
128    #[inline]
129    pub fn get_default_value(&self, type_: &'static TypeInfo) -> T {
130        if let Some(value) = &*self.single_default_value.borrow() {
131            return value.clone();
132        }
133        self.get_metadata(type_).default_value().clone()
134    }
135
136    /// Gets the default value for the property on the type of `owner`.
137    #[inline]
138    pub fn get_default_value_for(&self, owner: &FerroObject) -> T {
139        self.get_default_value(owner.get_type())
140    }
141
142    /// Gets the property metadata for the specified type.
143    #[inline]
144    pub fn get_metadata(&self, type_: &'static TypeInfo) -> Rc<StyledPropertyMetadata<T>> {
145        self.metadata.get(type_)
146    }
147
148    /// Gets the property metadata for the type of `owner`.
149    #[inline]
150    pub fn get_metadata_for(&self, owner: &FerroObject) -> Rc<StyledPropertyMetadata<T>> {
151        self.metadata.get(owner.get_type())
152    }
153
154    /// Overrides the default value for the property on class `TOwner`.
155    pub fn override_default_value<TOwner: ObjectType>(&self, default_value: T) {
156        self.override_default_value_for(TOwner::TYPE, default_value);
157    }
158
159    /// Overrides the default value for the property on the specified type.
160    pub fn override_default_value_for(&self, type_: &'static TypeInfo, default_value: T) {
161        self.override_metadata_for(type_, StyledPropertyMetadata::new(Some(default_value)));
162    }
163
164    /// Overrides the metadata for the property on class `TOwner`.
165    pub fn override_metadata<TOwner: ObjectType>(&self, metadata: StyledPropertyMetadata<T>) {
166        self.override_metadata_for(TOwner::TYPE, metadata)
167    }
168
169    /// Overrides the metadata for the property on the specified type.
170    pub fn override_metadata_for(&self, type_: &'static TypeInfo, metadata: StyledPropertyMetadata<T>) {
171        if metadata.has_default_value() && !self.is_valid(metadata.default_value()) {
172            panic!("default value is not valid for property '{}'", self.name());
173        }
174        let new_default = metadata.has_default_value().then(|| metadata.default_value().clone());
175        self.metadata.override_metadata(self.name(), type_, metadata);
176        let mut single = self.single_default_value.borrow_mut();
177        if let Some(new_default) = new_default {
178            if single.as_ref() != Some(&new_default) {
179                *single = None;
180            }
181        }
182    }
183
184    /// Removes the metadata registered for the specified type.
185    pub fn unregister(&self, type_: &'static TypeInfo) {
186        self.metadata.unregister(type_);
187    }
188
189    /// Converts an untyped value of another type to the value type of the
190    /// property with the implicit conversions of the managed original
191    /// (the `TryConvert` of the property, see
192    /// [`ValueTypes::try_convert_implicit`](crate::data::core::ValueTypes::try_convert_implicit)).
193    fn convert_untyped(value: &dyn Any) -> Option<T> {
194        let converted = crate::data::core::ValueTypes::try_convert_implicit(value, std::any::TypeId::of::<T>())?;
195        let converted: &dyn crate::AnyValue = &*converted;
196        converted.downcast_ref::<T>().cloned()
197    }
198
199    /// Interprets an untyped value: a value of type `T`, or one of the unset /
200    /// do-nothing markers.
201    pub(crate) fn from_untyped(&self, value: &dyn Any) -> BindingValue<T> {
202        if let Some(v) = value.downcast_ref::<T>() {
203            BindingValue::new(v.clone())
204        } else if value.is::<UnsetValueType>() {
205            BindingValue::unset()
206        } else if value.is::<DoNothingType>() {
207            BindingValue::do_nothing()
208        } else if let Some(v) = value.downcast_ref::<BindingValue<T>>() {
209            v.clone()
210        } else if let Some(n) = value.downcast_ref::<crate::data::BindingNotification>() {
211            n.to_binding_value(self.property_type_name())
212        } else if let Some(v) = Self::convert_untyped(value) {
213            BindingValue::new(v)
214        } else {
215            BindingValue::binding_error(crate::data::BindingError::message(self.invalid_value_message()))
216        }
217    }
218}
219
220impl<T: PropertyValue> PropertyRoutes for StyledProperty<T> {
221    fn create_effective_value(&'static self, o: &FerroObject) -> Rc<dyn EffectiveValueDyn> {
222        let this = self;
223        o.values().create_effective_value(o, this)
224    }
225
226    fn route_clear_value(&'static self, o: &FerroObject) {
227        o.clear_value(self);
228    }
229
230    fn route_coerce_default_value(&'static self, o: &FerroObject) {
231        o.values().coerce_default_value(o, self);
232    }
233
234    fn route_get_value(&'static self, o: &FerroObject) -> BoxedValue {
235        Rc::new(o.get_value(self))
236    }
237
238    fn route_get_base_value(&'static self, o: &FerroObject) -> BoxedValue {
239        match o.get_base_value(self) {
240            Some(v) => Rc::new(v),
241            None => FerroProperty::unset_value(),
242        }
243    }
244
245    fn route_get_default_value(&'static self, type_: &'static TypeInfo) -> BoxedValue {
246        Rc::new(self.get_default_value(type_))
247    }
248
249    fn route_set_value(
250        &'static self,
251        o: &FerroObject,
252        value: &dyn Any,
253        priority: BindingPriority,
254    ) -> Option<Rc<dyn IDisposable>> {
255        let this = self;
256        if value.is::<UnsetValueType>() {
257            if priority == BindingPriority::LocalValue {
258                o.clear_value(this);
259            }
260            None
261        } else if value.is::<DoNothingType>() {
262            None
263        } else if let Some(v) = value.downcast_ref::<T>() {
264            o.set_value_with_priority(this, v.clone(), priority)
265        } else if let Some(v) = Self::convert_untyped(value) {
266            o.set_value_with_priority(this, v, priority)
267        } else {
268            self.invalid_value_type();
269        }
270    }
271
272    fn route_set_current_value(&'static self, o: &FerroObject, value: &dyn Any) {
273        let this = self;
274        if value.is::<UnsetValueType>() {
275            o.clear_value(this);
276        } else if value.is::<DoNothingType>() {
277        } else if let Some(v) = value.downcast_ref::<T>() {
278            o.set_current_value(this, v.clone());
279        } else if let Some(v) = Self::convert_untyped(value) {
280            o.set_current_value(this, v);
281        } else {
282            self.invalid_value_type();
283        }
284    }
285
286    fn route_set_direct_value_unchecked(&'static self, _o: &FerroObject, _entry: &dyn IValueEntry) {
287        panic!("not supported for styled properties");
288    }
289
290    fn route_bind(
291        &'static self,
292        o: &FerroObject,
293        source: Rc<dyn IObservable<BoxedValue>>,
294        priority: BindingPriority,
295    ) -> Rc<dyn IDisposable> {
296        o.bind_untyped(self, source, priority)
297    }
298
299    fn route_get_metadata(&'static self, type_: &'static TypeInfo) -> FerroPropertyMetadata {
300        let metadata = self.get_metadata(type_);
301        let base: &FerroPropertyMetadata = &metadata;
302        base.clone()
303    }
304
305    fn route_unregister(&'static self, type_: &'static TypeInfo) {
306        self.unregister(type_)
307    }
308
309    fn route_is_valid_value(&'static self, value: &dyn Any) -> bool {
310        match value.downcast_ref::<T>() {
311            Some(v) => self.is_valid(v),
312            None => false,
313        }
314    }
315
316    fn as_any(&'static self) -> &'static dyn Any {
317        self
318    }
319
320    fn route_copy_value(&'static self, value: &dyn Any, out: &mut dyn Any) -> bool {
321        match (value.downcast_ref::<T>(), out.downcast_mut::<Option<T>>()) {
322            (Some(value), Some(out)) => {
323                *out = Some(value.clone());
324                true
325            }
326            _ => false,
327        }
328    }
329
330    fn route_box_value(&'static self, value: &dyn Any) -> Option<BoxedValue> {
331        value.downcast_ref::<T>().map(|value| Rc::new(value.clone()) as BoxedValue)
332    }
333
334    fn route_create_binding_entry(
335        &'static self,
336        o: &FerroObject,
337        frame: std::rc::Weak<dyn crate::property_store::ValueFrame>,
338        source: Rc<dyn IObservable<BoxedValue>>,
339    ) -> Rc<dyn IValueEntry> {
340        crate::property_store::BindingEntry::new(
341            o,
342            frame,
343            self,
344            crate::property_store::BindingSource::Untyped(source),
345        )
346    }
347}
348
349impl<T: PropertyValue> StyledProperty<T> {
350}
351
352/// An attached property: a styled property that is defined by one class and
353/// set on objects of another.
354pub struct AttachedProperty<T: PropertyValue> {
355    base: StyledProperty<T>,
356}
357
358impl<T: PropertyValue> Deref for AttachedProperty<T> {
359    type Target = StyledProperty<T>;
360    #[inline]
361    fn deref(&self) -> &StyledProperty<T> {
362        &self.base
363    }
364}
365
366impl<T: PropertyValue> AttachedProperty<T> {
367    pub(crate) fn create(
368        name: &str,
369        owner_type: &'static TypeInfo,
370        host_type: &'static TypeInfo,
371        metadata: StyledPropertyMetadata<T>,
372        inherits: bool,
373        validate: Option<Box<dyn Fn(&T) -> bool>>,
374        notifying: Option<NotifyingCallback>,
375    ) -> &'static Self {
376        let property: &'static Self = Box::leak(Box::new(Self {
377            base: StyledProperty::new(name, owner_type, host_type, metadata, inherits, validate, notifying, true),
378        }));
379        property.base.base.set_routes(&property.base);
380        property
381    }
382
383    /// Attaches the property as a non-attached property on class `TOwner`.
384    pub fn add_owner<TOwner: ObjectType>(&'static self) -> &'static Self {
385        FerroPropertyRegistry::instance().register(TOwner::TYPE, &self.base);
386        self
387    }
388
389    /// Attaches the property as a non-attached property on class `TOwner`,
390    /// with metadata for that class.
391    pub fn add_owner_with<TOwner: ObjectType>(&'static self, metadata: StyledPropertyMetadata<T>) -> &'static Self {
392        FerroPropertyRegistry::instance().register(TOwner::TYPE, &self.base);
393        self.base.override_metadata::<TOwner>(metadata);
394        self
395    }
396}