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
15pub struct StyledProperty<T: PropertyValue> {
19 base: FerroProperty,
20 metadata: MetadataTable<StyledPropertyMetadata<T>>,
21 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 #[inline]
86 pub fn as_property(&'static self) -> &'static FerroProperty {
87 &self.base
88 }
89
90 #[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 pub fn add_owner<TOwner: ObjectType>(&'static self) -> &'static Self {
106 FerroPropertyRegistry::instance().register(TOwner::TYPE, self);
107 self
108 }
109
110 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 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 #[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 #[inline]
138 pub fn get_default_value_for(&self, owner: &FerroObject) -> T {
139 self.get_default_value(owner.get_type())
140 }
141
142 #[inline]
144 pub fn get_metadata(&self, type_: &'static TypeInfo) -> Rc<StyledPropertyMetadata<T>> {
145 self.metadata.get(type_)
146 }
147
148 #[inline]
150 pub fn get_metadata_for(&self, owner: &FerroObject) -> Rc<StyledPropertyMetadata<T>> {
151 self.metadata.get(owner.get_type())
152 }
153
154 pub fn override_default_value<TOwner: ObjectType>(&self, default_value: T) {
156 self.override_default_value_for(TOwner::TYPE, default_value);
157 }
158
159 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 pub fn override_metadata<TOwner: ObjectType>(&self, metadata: StyledPropertyMetadata<T>) {
166 self.override_metadata_for(TOwner::TYPE, metadata)
167 }
168
169 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 pub fn unregister(&self, type_: &'static TypeInfo) {
186 self.metadata.unregister(type_);
187 }
188
189 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 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
352pub 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 pub fn add_owner<TOwner: ObjectType>(&'static self) -> &'static Self {
385 FerroPropertyRegistry::instance().register(TOwner::TYPE, &self.base);
386 self
387 }
388
389 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}