Skip to main content

ferroui_base/styling/
setter.rs

1use super::{
2    DirectPropertySetterBindingInstance, DirectPropertySetterInstance, IStyleInstance, ISetterInstance,
3    ISetterValue, ITemplate, PropertySetterTemplateInstance, SetterBase, SetterInstance, SetterInstanceKind,
4    StyleInstance, TargetPropertyPath,
5};
6use crate::data::core::SinkRef;
7use crate::data::{BindingBase, BindingPriority};
8use crate::property_store::{IValueEntry, ValueFrame};
9use crate::reactive::IObservable;
10use crate::{AnyValue, BoxedValue, FerroProperty, PropertyValue, StyledElement, StyledProperty};
11use std::any::Any;
12use std::cell::{Cell, OnceCell, RefCell};
13use std::fmt;
14use std::rc::Rc;
15
16/// The value of a [`Setter`].
17#[derive(Clone)]
18pub enum SetterValue {
19    /// A plain value, which must hold exactly the value type of the setter's
20    /// property.
21    Value(BoxedValue),
22    /// A source of values: the setter binds the property to it on every
23    /// control the style is applied to.
24    ///
25    /// This is the seam for binding descriptions: a binding class will
26    /// create its per-control source where this variant uses the same
27    /// observable for every control.
28    Binding(Rc<dyn IObservable<BoxedValue>>),
29    /// A binding description: the setter instantiates the binding on every
30    /// control the style is applied to.
31    BindingBase(Rc<dyn BindingBase>),
32    /// A template: the setter sets the property to the object the template
33    /// builds, building it lazily once per control.
34    Template(Rc<dyn ITemplate>),
35}
36
37/// A setter for a [`Style`](super::Style).
38///
39/// A setter describes what will take place when the requirements for a style
40/// are met: it sets a property to a value, binds it to a source of values or
41/// sets it to the result of a template.
42pub struct Setter {
43    property: Cell<Option<&'static FerroProperty>>,
44    value: RefCell<Option<SetterValue>>,
45    direct: OnceCell<Rc<DirectPropertySetterInstance>>,
46    // WinUI's `Setter.Target` (an opt-in addition, DEVIATIONS.md, WinUI additions to upstream
47    // controls): `None`, the default, is the upstream setter.
48    target: RefCell<Option<Rc<TargetPropertyPath>>>,
49}
50
51
52/// Compares by identity (reference equality), as the reference type this
53/// mirrors.
54impl PartialEq for Setter {
55    #[inline]
56    fn eq(&self, other: &Self) -> bool {
57        std::ptr::eq(self, other)
58    }
59}
60
61impl Default for Setter {
62    fn default() -> Self {
63        Self {
64            property: Cell::new(None),
65            value: RefCell::new(None),
66            direct: OnceCell::new(),
67            target: RefCell::new(None),
68        }
69    }
70}
71
72impl Setter {
73    /// Creates a setter without a property or value.
74    pub fn empty() -> Rc<Self> {
75        Rc::new(Self::default())
76    }
77
78    /// Creates a setter that sets a styled property to a value.
79    pub fn new<T: PropertyValue>(property: &'static StyledProperty<T>, value: T) -> Rc<Self> {
80        Self::with_value(property, SetterValue::Value(Rc::new(value)))
81    }
82
83    /// Creates a setter that sets a property to an untyped value, which must
84    /// hold exactly the property's value type.
85    pub fn new_untyped(property: &'static FerroProperty, value: BoxedValue) -> Rc<Self> {
86        Self::with_value(property, SetterValue::Value(value))
87    }
88
89    /// Creates a setter that binds a property to a source of values.
90    pub fn new_binding(property: &'static FerroProperty, source: Rc<dyn IObservable<BoxedValue>>) -> Rc<Self> {
91        Self::with_value(property, SetterValue::Binding(source))
92    }
93
94    /// Creates a setter that binds a property with a binding (a compiled,
95    /// string-path, template or multi binding).
96    pub fn new_binding_base(property: &'static FerroProperty, binding: Rc<dyn BindingBase>) -> Rc<Self> {
97        Self::with_value(property, SetterValue::BindingBase(binding))
98    }
99
100    /// Creates a setter that sets a property to the object built by a
101    /// template.
102    pub fn new_template(property: &'static FerroProperty, template: Rc<dyn ITemplate>) -> Rc<Self> {
103        Self::with_value(property, SetterValue::Template(template))
104    }
105
106    /// Creates a setter for a property with the given kind of value.
107    pub fn with_value(property: &'static FerroProperty, value: SetterValue) -> Rc<Self> {
108        let setter = Self::default();
109        setter.property.set(Some(property));
110        *setter.value.borrow_mut() = Some(value);
111        Rc::new(setter)
112    }
113
114    /// The property to set.
115    pub fn property(&self) -> Option<&'static FerroProperty> {
116        self.property.get()
117    }
118
119    pub fn set_property(&self, value: Option<&'static FerroProperty>) {
120        self.property.set(value)
121    }
122
123    /// The target of the setter in a visual state (WinUI's `Setter.Target`): the object whose
124    /// [`property`](Self::property) the setter sets. `None` (the default) for the setters of
125    /// styles, which set the property of the element the style applies to.
126    ///
127    /// An addition (docs/porting/winui-calculator-gaps.md, section 5.2). A style with a setter
128    /// that has a target is an error, as `Setter.Property` in a visual state is in WinUI.
129    pub fn target(&self) -> Option<Rc<TargetPropertyPath>> {
130        self.target.borrow().clone()
131    }
132
133    pub fn set_target(&self, value: Option<Rc<TargetPropertyPath>>) {
134        *self.target.borrow_mut() = value;
135    }
136
137    /// The property value.
138    pub fn value(&self) -> Option<SetterValue> {
139        self.value.borrow().clone()
140    }
141
142    pub fn set_value(&self, value: Option<SetterValue>) {
143        *self.value.borrow_mut() = value;
144    }
145
146    /// Sets a plain value that wants to be notified that it has become a
147    /// setter value. `boxed` is the value as it will be set on the property.
148    pub fn set_setter_value(&self, value: &dyn ISetterValue, boxed: BoxedValue) {
149        value.initialize(self);
150        *self.value.borrow_mut() = Some(SetterValue::Value(boxed));
151    }
152
153    fn ensure_property(&self) -> &'static FerroProperty {
154        match self.property.get() {
155            Some(property) => property,
156            None => panic!("Setter.Property must be set."),
157        }
158    }
159
160    fn plain_value(&self) -> Option<BoxedValue> {
161        match &*self.value.borrow() {
162            Some(SetterValue::Value(value)) => Some(value.clone()),
163            // A setter without a value sets null, where the property takes it.
164            None => self.property.get().and_then(Self::null_value),
165            _ => None,
166        }
167    }
168
169    /// The null of the value type of `property` (`None` of an optional
170    /// type), if the type has one: what a setter whose value is null sets.
171    fn null_value(property: &'static FerroProperty) -> Option<BoxedValue> {
172        crate::data::core::ValueTypes::null_value(crate::data::core::ValueType::new(
173            property.property_type(),
174            property.property_type_name(),
175        ))
176    }
177
178    fn set_binding(
179        &self,
180        property: &'static FerroProperty,
181        instance: &Rc<StyleInstance>,
182        target: &StyledElement,
183        source: Rc<dyn IObservable<BoxedValue>>,
184    ) -> SetterInstance {
185        if !property.is_direct() {
186            let frame: Rc<dyn ValueFrame> = instance.clone();
187            let entry = property.routes().route_create_binding_entry(target, Rc::downgrade(&frame), source);
188            SetterInstance { kind: SetterInstanceKind::Entry(entry), is_setter: false }
189        } else {
190            target.bind_property_untyped(property, source, BindingPriority::LocalValue);
191            SetterInstance {
192                kind: SetterInstanceKind::Other(Rc::new(DirectPropertySetterBindingInstance)),
193                is_setter: false,
194            }
195        }
196    }
197
198    fn set_binding_base(
199        &self,
200        property: &'static FerroProperty,
201        instance: &Rc<StyleInstance>,
202        target: &StyledElement,
203        binding: Rc<dyn BindingBase>,
204    ) -> SetterInstance {
205        if !property.is_direct() {
206            // The expression is an entry of the style instance's frame: it is
207            // started when the frame's values are evaluated and stopped when
208            // the style is deactivated or detached.
209            let expression = binding.create_instance(target, Some(property), None);
210            let owner: &crate::FerroObject = target;
211            let frame: &dyn ValueFrame = &**instance;
212            expression.attach(SinkRef::Store(owner.to_weak()), None, owner, property, frame.base().priority());
213            let entry: Rc<dyn IValueEntry> = expression;
214            SetterInstance { kind: SetterInstanceKind::Entry(entry), is_setter: false }
215        } else {
216            target.bind_binding(property, &*binding);
217            SetterInstance {
218                kind: SetterInstanceKind::Other(Rc::new(DirectPropertySetterBindingInstance)),
219                is_setter: false,
220            }
221        }
222    }
223
224    fn set_direct_value(&self, property: &'static FerroProperty, target: &StyledElement, value: &BoxedValue) -> SetterInstance {
225        let value: &dyn AnyValue = &**value;
226        target.set_value_untyped(property, value.as_any(), BindingPriority::LocalValue);
227        let direct: Rc<dyn ISetterInstance> =
228            self.direct.get_or_init(|| Rc::new(DirectPropertySetterInstance)).clone();
229        // The value is set on the control itself, so the style instance
230        // cannot be shared: every control needs its own attach.
231        SetterInstance { kind: SetterInstanceKind::Other(direct), is_setter: false }
232    }
233}
234
235impl ISetterInstance for Setter {}
236
237impl SetterBase for Setter {
238    fn as_any(&self) -> Option<&dyn Any> {
239        Some(self)
240    }
241
242    fn instance(self: Rc<Self>, instance: &Rc<StyleInstance>, target: &StyledElement) -> SetterInstance {
243        let Some(property) = self.property.get() else {
244            panic!("Setter.Property must be set.");
245        };
246
247        if self.target.borrow().is_some() {
248            panic!(
249                "Setter.Target is only valid in a visual state, not in '{}'.",
250                instance.source_display()
251            );
252        }
253
254        if property.is_direct() && instance.has_activator() {
255            panic!(
256                "Cannot set direct property '{}' in '{}' because the style has an activator.",
257                property,
258                instance.source_display()
259            );
260        }
261
262        if instance.has_activator() {
263            if let Some(class_property_name) = crate::ClassBindingManager::is_classes_binding_property(property) {
264                panic!(
265                    "Cannot set Class Binding property '(Classes.{})' in '{}' because the style has an activator.",
266                    class_property_name,
267                    instance.source_display()
268                );
269            }
270        }
271
272        let value = self.value.borrow().clone();
273        match value {
274            Some(SetterValue::Binding(source)) => self.set_binding(property, instance, target, source),
275            Some(SetterValue::BindingBase(binding)) => self.set_binding_base(property, instance, target, binding),
276            Some(SetterValue::Template(template)) => SetterInstance {
277                kind: SetterInstanceKind::Entry(Rc::new(PropertySetterTemplateInstance::new(property, template))),
278                is_setter: false,
279            },
280            Some(SetterValue::Value(value)) => {
281                let inner: &dyn AnyValue = &*value;
282                if !property.is_valid_value(inner.as_any()) {
283                    panic!("Setter value '{:?}' is not a valid value for property '{}'.", inner, property);
284                }
285                if property.is_direct() {
286                    self.set_direct_value(property, target, &value)
287                } else {
288                    SetterInstance { kind: SetterInstanceKind::Entry(self), is_setter: true }
289                }
290            }
291            // Null is a value of a property whose type admits it (a reference or
292            // nullable type in the managed original); of a value type it is not.
293            None => match Self::null_value(property) {
294                Some(null) if property.is_direct() => self.set_direct_value(property, target, &null),
295                Some(_) => SetterInstance { kind: SetterInstanceKind::Entry(self), is_setter: true },
296                None => panic!("Setter value '(null)' is not a valid value for property '{}'.", property),
297            },
298        }
299    }
300}
301
302impl IValueEntry for Setter {
303    fn property(&self) -> &'static FerroProperty {
304        self.ensure_property()
305    }
306
307    fn has_value(&self) -> bool {
308        true
309    }
310
311    fn try_get_value(&self, out: &mut dyn Any) -> bool {
312        match self.plain_value() {
313            Some(value) => {
314                let value: &dyn AnyValue = &*value;
315                self.ensure_property().routes().route_copy_value(value.as_any(), out)
316            }
317            None => false,
318        }
319    }
320
321    fn get_value_boxed(&self) -> Option<BoxedValue> {
322        self.plain_value()
323    }
324
325    fn unsubscribe(&self) {}
326}
327
328impl fmt::Display for Setter {
329    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
330        let property = self.property.get().map(|p| p.name().to_string()).unwrap_or_default();
331        match &*self.value.borrow() {
332            Some(SetterValue::Value(value)) => write!(f, "Setter: {property} = {:?}", &**value),
333            Some(SetterValue::Binding(_)) | Some(SetterValue::BindingBase(_)) => {
334                write!(f, "Setter: {property} = (binding)")
335            }
336            Some(SetterValue::Template(_)) => write!(f, "Setter: {property} = (template)"),
337            None => write!(f, "Setter: {property} = "),
338        }
339    }
340}