ferroui_xaml_interactions_responsive/
adaptive_behavior.rs1use crate::AdaptiveClassSetter;
4use ferroui_base::collections::FerroList;
5use ferroui_base::controls::IPseudoClasses;
6use ferroui_base::data::core::ValueTypes;
7use ferroui_base::reactive::{IDisposable, ObservableExt};
8use ferroui_base::{
9 ferro_class, ferro_class_info, ferro_properties, instantiate, DirectProperty, ElementRef, FerroObject,
10 FerroObjectExtensions, FerroObjectImpl, FerroProperty, Nullable, Rect, Ref, StaticType, StyledElementImpl,
11 StyledProperty, Visual,
12};
13use ferroui_controls::Control;
14use ferroui_xaml_interactivity::{
15 ComparisonConditionType, StyledElementBehaviorImpl, StyledElementBehaviorImplExt, StyledElementBehaviorOfT,
16};
17use std::cell::{OnceCell, RefCell};
18use std::ops::Deref;
19use std::rc::Rc;
20
21#[derive(Clone, PartialEq)]
29pub struct AdaptiveClassSetters(FerroList<Ref<AdaptiveClassSetter>>);
30
31impl AdaptiveClassSetters {
32 pub fn new() -> Self {
34 Self(FerroList::new())
35 }
36
37 pub fn list(&self) -> &FerroList<Ref<AdaptiveClassSetter>> {
39 &self.0
40 }
41}
42
43impl Default for AdaptiveClassSetters {
44 fn default() -> Self {
45 Self::new()
46 }
47}
48
49impl Deref for AdaptiveClassSetters {
50 type Target = FerroList<Ref<AdaptiveClassSetter>>;
51
52 fn deref(&self) -> &FerroList<Ref<AdaptiveClassSetter>> {
53 &self.0
54 }
55}
56
57impl std::fmt::Debug for AdaptiveClassSetters {
58 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 write!(f, "AdaptiveClassSetters({})", self.0.count())
60 }
61}
62
63#[repr(C)]
67pub struct AdaptiveBehavior {
68 base: StyledElementBehaviorOfT,
69 disposable: RefCell<Option<Rc<dyn IDisposable>>>,
70 setters: OnceCell<AdaptiveClassSetters>,
71}
72
73ferro_class!(AdaptiveBehavior: StyledElementBehaviorOfT);
74ferro_class_info!(AdaptiveBehavior {
75 new: AdaptiveBehavior::new,
76 markup: {
77 content: Setters,
78 property_attributes: [
79 SourceControl: [ResolveByName],
80 TargetControl: [ResolveByName],
81 ],
82 },
83});
84ferroui_base::ferro_impl_classes!(AdaptiveBehavior: FerroObjectImpl, StyledElementImpl);
85
86ferro_properties! {
87 impl AdaptiveBehavior {
88 pub fn source_control_property() -> StyledProperty<Option<ElementRef<Control>>> {
93 ValueTypes::register_element_ref::<Control>();
94 FerroProperty::register::<AdaptiveBehavior, _>("SourceControl", None)
95 }
96
97 pub fn target_control_property() -> StyledProperty<Option<ElementRef<Control>>> {
99 ValueTypes::register_element_ref::<Control>();
100 FerroProperty::register::<AdaptiveBehavior, _>("TargetControl", None)
101 }
102
103 pub fn setters_property() -> DirectProperty<AdaptiveBehavior, AdaptiveClassSetters> {
105 FerroProperty::register_direct::<AdaptiveBehavior, _>(
106 "Setters",
107 |behavior| behavior.setters(),
108 None,
109 AdaptiveClassSetters::new(),
110 )
111 }
112 }
113}
114
115ferroui_base::ferro_overrides! {
116 impl StyledElementBehaviorImpl for AdaptiveBehavior {
117 fn on_attached_to_visual_tree(this: &Self) {
118 Self::parent_on_attached_to_visual_tree(this);
119
120 this.stop_observing();
121 this.start_observing();
122 }
123
124 fn on_detached_from_visual_tree(this: &Self) {
125 Self::parent_on_detached_from_visual_tree(this);
126
127 this.stop_observing();
128 }
129 }
130}
131
132impl AdaptiveBehavior {
133 pub fn construct() -> Self {
135 Self {
136 base: StyledElementBehaviorOfT::construct(<Control as StaticType>::TYPE),
137 disposable: RefCell::new(None),
138 setters: OnceCell::new(),
139 }
140 }
141
142 pub fn new() -> Ref<Self> {
144 instantiate(Self::construct())
145 }
146
147 pub fn source_control(&self) -> Option<Ref<Control>> {
151 ElementRef::resolve(&self.get_value(Self::source_control_property()))
152 }
153
154 pub fn set_source_control(&self, value: impl Into<Nullable<Control>>) {
155 self.set_value(Self::source_control_property(), ElementRef::from_nullable(value.into().0))
156 }
157
158 pub fn target_control(&self) -> Option<Ref<Control>> {
164 ElementRef::resolve(&self.get_value(Self::target_control_property()))
165 }
166
167 pub fn set_target_control(&self, value: impl Into<Nullable<Control>>) {
168 self.set_value(Self::target_control_property(), ElementRef::from_nullable(value.into().0))
169 }
170
171 pub fn setters(&self) -> AdaptiveClassSetters {
174 self.setters.get_or_init(AdaptiveClassSetters::new).clone()
175 }
176
177 fn start_observing(&self) {
178 let source_control = match self.source_control() {
179 Some(source_control) => Some(source_control),
180 None => self.associated_object_as::<Control>(),
181 };
182
183 if let Some(source_control) = source_control {
184 let disposable = self.observe_bounds(&source_control);
185 *self.disposable.borrow_mut() = Some(disposable);
186 }
187 }
188
189 fn stop_observing(&self) {
190 let disposable = self.disposable.borrow_mut().take();
191 if let Some(disposable) = disposable {
192 disposable.dispose();
193 }
194 }
195
196 fn observe_bounds(&self, source_control: &Ref<Control>) -> Rc<dyn IDisposable> {
197 self.execute(Some(source_control), Some(&self.setters()), source_control.bounds());
198
199 let behavior = self.to_ref().downgrade();
201 let source = source_control.downgrade();
202 let source_object: &FerroObject = source_control;
203 FerroObjectExtensions::get_observable(source_object, Visual::bounds_property()).subscribe_fn(
204 move |bounds: Rect| {
205 if let (Some(behavior), Some(source)) = (behavior.upgrade(), source.upgrade()) {
206 behavior.execute(Some(&source), Some(&behavior.setters()), bounds);
207 }
208 },
209 )
210 }
211
212 fn execute(&self, source_control: Option<&Ref<Control>>, setters: Option<&AdaptiveClassSetters>, bounds: Rect) {
213 let (Some(_), Some(setters)) = (source_control, setters) else {
214 return;
215 };
216
217 for setter in setters.snapshot().iter() {
218 let is_min_or_max_width_set = setter.is_set(AdaptiveClassSetter::min_width_property().as_property())
219 || setter.is_set(AdaptiveClassSetter::max_width_property().as_property());
220 let width_condition_triggered = Self::get_result(setter.min_width_operator(), bounds.width, setter.min_width())
221 && Self::get_result(setter.max_width_operator(), bounds.width, setter.max_width());
222
223 let is_min_or_max_height_set = setter.is_set(AdaptiveClassSetter::min_height_property().as_property())
224 || setter.is_set(AdaptiveClassSetter::max_height_property().as_property());
225 let height_condition_triggered =
226 Self::get_result(setter.min_height_operator(), bounds.height, setter.min_height())
227 && Self::get_result(setter.max_height_operator(), bounds.height, setter.max_height());
228
229 let is_add_class_triggered = match (is_min_or_max_width_set, is_min_or_max_height_set) {
230 (true, false) => width_condition_triggered,
231 (false, true) => height_condition_triggered,
232 (true, true) => width_condition_triggered && height_condition_triggered,
233 (false, false) => false,
234 };
235
236 let target_control = match setter.target_control() {
237 Some(target_control) => Some(target_control),
238 None => match self.target_control() {
239 Some(target_control) => Some(target_control),
240 None => self.associated_object_as::<Control>(),
241 },
242 };
243
244 let Some(target_control) = target_control else {
245 panic!("Value cannot be null. (Parameter 'targetControl')");
246 };
247
248 let class_name = setter.class_name();
249 let is_pseudo_class = setter.is_pseudo_class();
250
251 if is_add_class_triggered {
252 Self::add(&target_control, class_name.as_deref(), is_pseudo_class);
253 } else {
254 Self::remove(&target_control, class_name.as_deref(), is_pseudo_class);
255 }
256 }
257 }
258
259 fn get_result(comparison_condition_type: ComparisonConditionType, property: f64, value: f64) -> bool {
260 match comparison_condition_type {
261 ComparisonConditionType::Equal => property == value,
262 ComparisonConditionType::NotEqual => property != value,
263 ComparisonConditionType::LessThan => property < value,
264 ComparisonConditionType::LessThanOrEqual => property <= value,
265 ComparisonConditionType::GreaterThan => property > value,
266 ComparisonConditionType::GreaterThanOrEqual => property >= value,
267 }
268 }
269
270 fn add(target_control: &Control, class_name: Option<&str>, is_pseudo_class: bool) {
271 let Some(class_name) = class_name.filter(|class_name| !class_name.is_empty()) else {
272 return;
273 };
274 let classes = target_control.classes();
275 if classes.contains(class_name) {
276 return;
277 }
278
279 if is_pseudo_class {
280 classes.add_pseudo(class_name);
281 } else {
282 classes.add(class_name);
283 }
284 }
285
286 fn remove(target_control: &Control, class_name: Option<&str>, is_pseudo_class: bool) {
287 let Some(class_name) = class_name.filter(|class_name| !class_name.is_empty()) else {
288 return;
289 };
290 let classes = target_control.classes();
291 if !classes.contains(class_name) {
292 return;
293 }
294
295 if is_pseudo_class {
296 classes.remove_pseudo(class_name);
297 } else {
298 classes.remove(class_name);
299 }
300 }
301}