1use crate::{ControlImpl, Control, Decorator};
5use ferroui_base::input::InputElementImpl;
6use ferroui_base::interactivity::InteractiveImpl;
7use ferroui_base::layout::{LayoutableImpl, LayoutableImplExt};
8use ferroui_base::media::{ITransform, MatrixTransform};
9use ferroui_base::reactive::IDisposable;
10use ferroui_base::utilities::math_utilities::min;
11use ferroui_base::{
12 ferro_class, ferro_impl_classes, ferro_property, instantiate, FerroObjectImpl, FerroProperty,
13 FerroPropertyChangedEventArgs, Matrix, Rect, Ref, RelativePoint, RelativeUnit, Size, StyledElementImpl,
14 StyledProperty, Visual, VisualImpl, VisualImplExt, VisualTreeAttachmentEventArgs,
15};
16use std::cell::{Cell, OnceCell, RefCell};
17use std::rc::Rc;
18
19const ACCEPTABLE_DELTA: f64 = 0.0001;
28
29const DECIMALS_AFTER_ROUND: i32 = 8;
30
31#[repr(C)]
34pub struct LayoutTransformControl {
35 base: Decorator,
36 render_transform_changed_event: RefCell<Option<Rc<dyn IDisposable>>>,
37 child_actual_size: Cell<Size>,
39 matrix_transform: OnceCell<Ref<MatrixTransform>>,
41 transformation: Cell<Matrix>,
43 layout_transform_changed: RefCell<Option<Rc<dyn IDisposable>>>,
45}
46
47ferro_class!(LayoutTransformControl: Decorator);
48ferroui_base::ferro_class_info!(LayoutTransformControl { new: LayoutTransformControl::new });
49ferro_impl_classes!(LayoutTransformControl: StyledElementImpl, InteractiveImpl, InputElementImpl, ControlImpl);
50
51ferroui_base::ferro_impl_classes!(LayoutTransformControl: FerroObjectImpl);
52
53impl VisualImpl for LayoutTransformControl {
54 fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
55 Self::parent_on_attached_to_visual_tree(this, e);
56 this.subscribe_layout_transform(this.layout_transform());
57 this.apply_layout_transform();
58 }
59
60 fn on_detached_from_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
61 Self::parent_on_detached_from_visual_tree(this, e);
62 this.unsubscribe_layout_transform();
63 }
64}
65
66impl LayoutableImpl for LayoutTransformControl {
67 fn arrange_override(this: &Self, final_size: Size) -> Size {
69 let (Some(transform_root), Some(_)) = (this.transform_root(), this.layout_transform()) else {
70 this.set_current_value(Self::layout_transform_property(), this.render_transform());
71 return Self::parent_arrange_override(this, final_size);
72 };
73
74 let mut final_size_transformed = this.compute_largest_transformed_size(final_size);
76 if Self::is_size_smaller(final_size_transformed, transform_root.desired_size()) {
77 final_size_transformed = transform_root.desired_size();
81 }
82
83 let transformed_rect = Rect::new(0.0, 0.0, final_size_transformed.width, final_size_transformed.height)
85 .transform_to_aabb(this.transformation.get());
86 let final_rect = Rect::new(
88 -transformed_rect.x + ((final_size.width - transformed_rect.width) / 2.0),
89 -transformed_rect.y + ((final_size.height - transformed_rect.height) / 2.0),
90 final_size_transformed.width,
91 final_size_transformed.height,
92 );
93
94 transform_root.arrange(final_rect);
96 let arranged_size = transform_root.bounds().size();
97
98 if !(Self::is_size_smaller(final_size_transformed, arranged_size)
102 && this.child_actual_size.get() == Size::default())
103 {
104 this.child_actual_size.set(Size::default());
106 }
107
108 final_size
110 }
111
112 fn measure_override(this: &Self, available_size: Size) -> Size {
114 let (Some(transform_root), Some(_)) = (this.transform_root(), this.layout_transform()) else {
115 return Self::parent_measure_override(this, available_size);
116 };
117
118 let measure_size = if this.child_actual_size.get() == Size::default() {
119 this.compute_largest_transformed_size(available_size)
121 } else {
122 this.child_actual_size.get()
125 };
126
127 transform_root.measure(measure_size);
129
130 let desired_size = transform_root.desired_size();
131
132 let transformed_desired_rect =
134 Rect::new(0.0, 0.0, desired_size.width, desired_size.height).transform_to_aabb(this.transformation.get());
135
136 Size::new(transformed_desired_rect.width, transformed_desired_rect.height)
138 }
139}
140
141ferroui_base::ferro_properties! { impl LayoutTransformControl {
142 ferro_property!(
143 pub fn layout_transform_property() -> StyledProperty<Option<Rc<dyn ITransform>>> {
145 FerroProperty::register::<LayoutTransformControl, _>("LayoutTransform", None)
146 }
147 );
148
149 ferro_property!(
150 pub fn use_render_transform_property() -> StyledProperty<bool> {
152 FerroProperty::register::<LayoutTransformControl, _>("UseRenderTransform", false)
153 }
154 );
155} }
156
157impl LayoutTransformControl {
158 fn static_constructor() {
159 Visual::clip_to_bounds_property().override_default_value::<LayoutTransformControl>(true);
160
161 Self::layout_transform_property()
162 .changed()
163 .add_class_handler::<LayoutTransformControl>(|x, e| x.on_layout_transform_changed(e));
164
165 Decorator::child_property().changed().add_class_handler::<LayoutTransformControl>(|x, _| x.on_child_changed());
166
167 Self::use_render_transform_property()
168 .changed()
169 .add_class_handler::<LayoutTransformControl>(|x, e| x.on_use_render_transform_property_changed(e));
170 }
171
172 pub fn construct() -> Self {
174 Self {
175 base: Decorator::construct(),
176 render_transform_changed_event: RefCell::new(None),
177 child_actual_size: Cell::new(Size::default()),
178 matrix_transform: OnceCell::new(),
179 transformation: Cell::new(Matrix::IDENTITY),
180 layout_transform_changed: RefCell::new(None),
181 }
182 }
183
184 pub fn new() -> Ref<Self> {
185 instantiate(Self::construct())
186 }
187
188 pub fn layout_transform(&self) -> Option<Rc<dyn ITransform>> {
191 self.get_value(Self::layout_transform_property())
192 }
193
194 pub fn set_layout_transform(&self, value: Option<Rc<dyn ITransform>>) {
195 self.set_value(Self::layout_transform_property(), value)
196 }
197
198 pub fn use_render_transform(&self) -> bool {
201 self.get_value(Self::use_render_transform_property())
202 }
203
204 pub fn set_use_render_transform(&self, value: bool) {
205 self.set_value(Self::use_render_transform_property(), value)
206 }
207
208 pub fn transform_root(&self) -> Option<Ref<Control>> {
210 self.child()
211 }
212
213 fn matrix_transform(&self) -> &Ref<MatrixTransform> {
214 self.matrix_transform.get_or_init(MatrixTransform::new)
215 }
216
217 fn on_use_render_transform_property_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
218 let should_use_render_transform = e.get_new_value::<bool>();
226 if should_use_render_transform {
227 let subscription = Visual::render_transform_property().changed().subscribe(|x| {
228 if let Some(target) = x.sender().downcast_ref::<LayoutTransformControl>() {
229 target.set_layout_transform(target.render_transform());
230 }
231 });
232 *self.render_transform_changed_event.borrow_mut() = Some(subscription);
233 } else {
234 let subscription = self.render_transform_changed_event.borrow().clone();
235 if let Some(subscription) = subscription {
236 subscription.dispose();
237 }
238 self.clear_value(Self::layout_transform_property());
239 }
240 }
241
242 fn on_child_changed(&self) {
243 if let Some(transform_root) = self.transform_root() {
244 transform_root.set_render_transform(Some(self.matrix_transform().into()));
245 transform_root.set_render_transform_origin(RelativePoint::new(0.0, 0.0, RelativeUnit::Absolute));
246 }
247
248 self.apply_layout_transform();
249 }
250
251 fn is_size_smaller(a: Size, b: Size) -> bool {
253 (a.width + ACCEPTABLE_DELTA < b.width) || (a.height + ACCEPTABLE_DELTA < b.height)
254 }
255
256 fn round_matrix(matrix: Matrix, decimals: i32) -> Matrix {
259 Matrix::new(
260 round(matrix.m11, decimals),
261 round(matrix.m12, decimals),
262 round(matrix.m21, decimals),
263 round(matrix.m22, decimals),
264 matrix.m31,
265 matrix.m32,
266 )
267 }
268
269 fn apply_layout_transform(&self) {
271 let matrix = match self.layout_transform() {
273 None => Matrix::IDENTITY,
274 Some(layout_transform) => Self::round_matrix(layout_transform.value(), DECIMALS_AFTER_ROUND),
275 };
276
277 if self.transformation.get() == matrix {
278 return;
279 }
280
281 self.transformation.set(matrix);
282 self.matrix_transform().set_matrix(matrix);
283
284 self.invalidate_measure();
286 }
287
288 fn compute_largest_transformed_size(&self, mut arrange_bounds: Size) -> Size {
291 let mut computed_size = Size::default();
293 let transformation = self.transformation.get();
294
295 let infinite_width = arrange_bounds.width.is_infinite();
297 if infinite_width {
298 arrange_bounds = arrange_bounds.with_width(arrange_bounds.height);
299 }
300 let infinite_height = arrange_bounds.height.is_infinite();
301 if infinite_height {
302 arrange_bounds = arrange_bounds.with_height(arrange_bounds.width);
303 }
304
305 let a = transformation.m11;
307 let b = transformation.m12;
308 let c = transformation.m21;
309 let d = transformation.m22;
310
311 let max_width_from_width = (arrange_bounds.width / a).abs();
315 let max_height_from_width = (arrange_bounds.width / c).abs();
316 let max_width_from_height = (arrange_bounds.height / b).abs();
317 let max_height_from_height = (arrange_bounds.height / d).abs();
318
319 let ideal_width_from_width = max_width_from_width / 2.0;
323 let ideal_height_from_width = max_height_from_width / 2.0;
324 let ideal_width_from_height = max_width_from_height / 2.0;
325 let ideal_height_from_height = max_height_from_height / 2.0;
326
327 let slope_from_width = -(max_height_from_width / max_width_from_width);
329 let slope_from_height = -(max_height_from_height / max_width_from_height);
330
331 if (0.0 == arrange_bounds.width) || (0.0 == arrange_bounds.height) {
332 computed_size = Size::new(arrange_bounds.width, arrange_bounds.height);
334 } else if infinite_width && infinite_height {
335 computed_size = Size::new(f64::INFINITY, f64::INFINITY);
337 } else if !transformation.has_inverse() {
338 computed_size = Size::new(0.0, 0.0);
340 } else if (0.0 == b) || (0.0 == c) {
341 let max_height = if infinite_height { f64::INFINITY } else { max_height_from_height };
343 let max_width = if infinite_width { f64::INFINITY } else { max_width_from_width };
344 if (0.0 == b) && (0.0 == c) {
345 computed_size = Size::new(max_width, max_height);
347 } else if 0.0 == b {
348 let computed_height = min(ideal_height_from_width, max_height);
350 computed_size = Size::new(max_width - ((c * computed_height) / a).abs(), computed_height);
351 } else if 0.0 == c {
352 let computed_width = min(ideal_width_from_height, max_width);
354 computed_size = Size::new(computed_width, max_height - ((b * computed_width) / d).abs());
355 }
356 } else if (0.0 == a) || (0.0 == d) {
357 let max_width = if infinite_height { f64::INFINITY } else { max_width_from_height };
359 let max_height = if infinite_width { f64::INFINITY } else { max_height_from_width };
360 if (0.0 == a) && (0.0 == d) {
361 computed_size = Size::new(max_width, max_height);
363 } else if 0.0 == a {
364 let computed_height = min(ideal_height_from_height, max_height);
366 computed_size = Size::new(max_width - ((d * computed_height) / b).abs(), computed_height);
367 } else if 0.0 == d {
368 let computed_width = min(ideal_width_from_width, max_width);
370 computed_size = Size::new(computed_width, max_height - ((a * computed_width) / c).abs());
371 }
372 } else if ideal_height_from_width <= ((slope_from_height * ideal_width_from_width) + max_height_from_height) {
373 computed_size = Size::new(ideal_width_from_width, ideal_height_from_width);
376 } else if ideal_height_from_height <= ((slope_from_width * ideal_width_from_height) + max_height_from_width) {
377 computed_size = Size::new(ideal_width_from_height, ideal_height_from_height);
380 } else {
381 let computed_width =
385 (max_height_from_height - max_height_from_width) / (slope_from_width - slope_from_height);
386 computed_size = Size::new(computed_width, (slope_from_width * computed_width) + max_height_from_width);
389 }
390
391 computed_size
393 }
394
395 fn on_layout_transform_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
396 if self.is_attached_to_visual_tree() {
397 self.unsubscribe_layout_transform();
398 self.subscribe_layout_transform(e.get_new_value::<Option<Rc<dyn ITransform>>>());
399 }
400
401 self.apply_layout_transform();
402 }
403
404 fn subscribe_layout_transform(&self, transform: Option<Rc<dyn ITransform>>) {
406 let Some(transform) = transform else { return };
407 let Some(transform) = transform.as_mutable_transform() else { return };
408
409 let weak = self.to_ref().downgrade();
410 let subscription = transform.changed(Rc::new(move || {
411 if let Some(this) = weak.upgrade() {
412 this.apply_layout_transform();
413 }
414 }));
415 *self.layout_transform_changed.borrow_mut() = Some(subscription);
416 }
417
418 fn unsubscribe_layout_transform(&self) {
420 let subscription = self.layout_transform_changed.borrow_mut().take();
421 if let Some(subscription) = subscription {
422 subscription.dispose();
423 }
424 }
425}
426
427fn round(value: f64, decimals: i32) -> f64 {
430 if value.abs() < 1e16 {
431 let power10 = 10f64.powi(decimals);
432 (value * power10).round_ties_even() / power10
433 } else {
434 value
435 }
436}