Skip to main content

ferroui_controls/primitives/
range_base.rs

1use super::{RangeBaseValueChangedEventArgs, TemplatedControl, TemplatedControlImpl};
2use crate::ControlImpl;
3use ferroui_base::data::BindingMode;
4use ferroui_base::input::InputElementImpl;
5use ferroui_base::interactivity::{Interactive, InteractiveImpl, RoutedEvent, RoutedEventHandlerToken, RoutingStrategies};
6use ferroui_base::layout::LayoutableImpl;
7use ferroui_base::utilities::MathUtilities;
8use ferroui_base::{
9    ferro_class, ferro_impl_classes, ferro_property, ferro_routed_event, FerroObject, FerroObjectImpl,
10    FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, StyledElementImpl, StyledElementImplExt,
11    StyledProperty, StyledPropertyOptions, VisualImpl,
12};
13use std::cell::Cell;
14
15/// Base class for controls that display a value within a range.
16///
17/// This class is abstract: only its subclasses can be created.
18#[repr(C)]
19pub struct RangeBase {
20    base: TemplatedControl,
21    is_data_context_changing: Cell<bool>,
22}
23
24ferro_class!(RangeBase: TemplatedControl);
25ferro_impl_classes!(
26    RangeBase: VisualImpl,
27    LayoutableImpl,
28    InteractiveImpl,
29    InputElementImpl,
30    ControlImpl,
31    TemplatedControlImpl
32);
33
34impl FerroObjectImpl for RangeBase {
35    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
36        Self::parent_on_property_changed(this, change);
37
38        if change.property() == Self::minimum_property().as_property() {
39            this.on_minimum_changed();
40        } else if change.property() == Self::maximum_property().as_property() {
41            this.on_maximum_changed();
42        } else if change.property() == Self::value_property().as_property() {
43            let (old_value, new_value) = change.get_old_and_new_value::<f64>();
44            let value_changed_event_args =
45                RangeBaseValueChangedEventArgs::new(old_value, new_value, Some(Self::value_changed_event()));
46            this.raise_event(&value_changed_event_args);
47        }
48    }
49}
50
51impl StyledElementImpl for RangeBase {
52    fn on_initialized(this: &Self) {
53        Self::parent_on_initialized(this);
54
55        this.coerce_value(Self::maximum_property().as_property());
56        this.coerce_value(Self::value_property().as_property());
57    }
58
59    fn on_data_context_begin_update(this: &Self) {
60        this.is_data_context_changing.set(true);
61        Self::parent_on_data_context_begin_update(this);
62    }
63
64    fn on_data_context_end_update(this: &Self) {
65        Self::parent_on_data_context_end_update(this);
66        this.is_data_context_changing.set(false);
67    }
68}
69
70ferroui_base::ferro_properties! { impl RangeBase {
71    ferro_property!(
72        /// Defines the `Minimum` property.
73        pub fn minimum_property() -> StyledProperty<f64> {
74            FerroProperty::register_with::<RangeBase, _>(
75                "Minimum",
76                StyledPropertyOptions::new(0.0).coerce(RangeBase::coerce_minimum),
77            )
78        }
79    );
80
81    ferro_property!(
82        /// Defines the `Maximum` property.
83        pub fn maximum_property() -> StyledProperty<f64> {
84            FerroProperty::register_with::<RangeBase, _>(
85                "Maximum",
86                StyledPropertyOptions::new(100.0).coerce(RangeBase::coerce_maximum),
87            )
88        }
89    );
90
91    ferro_property!(
92        /// Defines the `Value` property.
93        pub fn value_property() -> StyledProperty<f64> {
94            FerroProperty::register_with::<RangeBase, _>(
95                "Value",
96                StyledPropertyOptions::new(0.0)
97                    .default_binding_mode(BindingMode::TwoWay)
98                    .coerce(RangeBase::coerce_value_core),
99            )
100        }
101    );
102
103    ferro_property!(
104        /// Defines the `SmallChange` property.
105        pub fn small_change_property() -> StyledProperty<f64> {
106            FerroProperty::register::<RangeBase, _>("SmallChange", 1.0)
107        }
108    );
109
110    ferro_property!(
111        /// Defines the `LargeChange` property.
112        pub fn large_change_property() -> StyledProperty<f64> {
113            FerroProperty::register::<RangeBase, _>("LargeChange", 10.0)
114        }
115    );
116} }
117
118impl RangeBase {
119    ferro_routed_event!(
120        /// Defines the `ValueChanged` event.
121        pub fn value_changed_event() -> RoutedEvent<RangeBaseValueChangedEventArgs> {
122            RoutedEvent::register::<RangeBase, _>("ValueChanged", RoutingStrategies::BUBBLE)
123        }
124    );
125
126    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
127    pub fn construct() -> Self {
128        Self { base: TemplatedControl::construct(), is_data_context_changing: Cell::new(false) }
129    }
130
131    /// Occurs when the `Value` property changes.
132    pub fn value_changed(
133        &self,
134        handler: impl Fn(&Interactive, &RangeBaseValueChangedEventArgs) + 'static,
135    ) -> RoutedEventHandlerToken {
136        self.add_handler(Self::value_changed_event(), handler)
137    }
138
139    /// The minimum possible value.
140    pub fn minimum(&self) -> f64 {
141        self.get_value(Self::minimum_property())
142    }
143
144    pub fn set_minimum(&self, value: f64) {
145        self.set_value(Self::minimum_property(), value)
146    }
147
148    fn coerce_minimum(sender: &FerroObject, value: f64) -> f64 {
149        if Self::validate_double(value) {
150            value
151        } else {
152            sender.get_value(Self::minimum_property())
153        }
154    }
155
156    fn on_minimum_changed(&self) {
157        if self.is_initialized() && !self.is_data_context_changing.get() {
158            self.coerce_value(Self::maximum_property().as_property());
159            self.coerce_value(Self::value_property().as_property());
160        }
161    }
162
163    /// The maximum possible value.
164    pub fn maximum(&self) -> f64 {
165        self.get_value(Self::maximum_property())
166    }
167
168    pub fn set_maximum(&self, value: f64) {
169        self.set_value(Self::maximum_property(), value)
170    }
171
172    fn coerce_maximum(sender: &FerroObject, value: f64) -> f64 {
173        if Self::validate_double(value) {
174            f64::max(value, sender.get_value(Self::minimum_property()))
175        } else {
176            sender.get_value(Self::maximum_property())
177        }
178    }
179
180    fn on_maximum_changed(&self) {
181        if self.is_initialized() && !self.is_data_context_changing.get() {
182            self.coerce_value(Self::minimum_property().as_property());
183            self.coerce_value(Self::value_property().as_property());
184        }
185    }
186
187    /// The current value.
188    pub fn value(&self) -> f64 {
189        self.get_value(Self::value_property())
190    }
191
192    pub fn set_range_value(&self, value: f64) {
193        self.set_value(Self::value_property(), value)
194    }
195
196    fn coerce_value_core(sender: &FerroObject, value: f64) -> f64 {
197        if Self::validate_double(value) {
198            MathUtilities::clamp(
199                value,
200                sender.get_value(Self::minimum_property()),
201                sender.get_value(Self::maximum_property()),
202            )
203        } else {
204            sender.get_value(Self::value_property())
205        }
206    }
207
208    /// The small increment value added to or subtracted from the value.
209    pub fn small_change(&self) -> f64 {
210        self.get_value(Self::small_change_property())
211    }
212
213    pub fn set_small_change(&self, value: f64) {
214        self.set_value(Self::small_change_property(), value)
215    }
216
217    /// The large increment value added to or subtracted from the value.
218    pub fn large_change(&self) -> f64 {
219        self.get_value(Self::large_change_property())
220    }
221
222    pub fn set_large_change(&self, value: f64) {
223        self.set_value(Self::large_change_property(), value)
224    }
225
226    /// Checks that the value is neither infinity nor NaN.
227    fn validate_double(value: f64) -> bool {
228        !value.is_infinite() && !value.is_nan()
229    }
230}