use crate::{ControlImpl, Control, Decorator};
use ferroui_base::input::InputElementImpl;
use ferroui_base::interactivity::InteractiveImpl;
use ferroui_base::layout::{LayoutableImpl, LayoutableImplExt};
use ferroui_base::media::{ITransform, MatrixTransform};
use ferroui_base::reactive::IDisposable;
use ferroui_base::utilities::math_utilities::min;
use ferroui_base::{
ferro_class, ferro_impl_classes, ferro_property, instantiate, FerroObjectImpl, FerroProperty,
FerroPropertyChangedEventArgs, Matrix, Rect, Ref, RelativePoint, RelativeUnit, Size, StyledElementImpl,
StyledProperty, Visual, VisualImpl, VisualImplExt, VisualTreeAttachmentEventArgs,
};
use std::cell::{Cell, OnceCell, RefCell};
use std::rc::Rc;
const ACCEPTABLE_DELTA: f64 = 0.0001;
const DECIMALS_AFTER_ROUND: i32 = 8;
#[repr(C)]
pub struct LayoutTransformControl {
base: Decorator,
render_transform_changed_event: RefCell<Option<Rc<dyn IDisposable>>>,
child_actual_size: Cell<Size>,
matrix_transform: OnceCell<Ref<MatrixTransform>>,
transformation: Cell<Matrix>,
layout_transform_changed: RefCell<Option<Rc<dyn IDisposable>>>,
}
ferro_class!(LayoutTransformControl: Decorator);
ferroui_base::ferro_class_info!(LayoutTransformControl { new: LayoutTransformControl::new });
ferro_impl_classes!(LayoutTransformControl: StyledElementImpl, InteractiveImpl, InputElementImpl, ControlImpl);
ferroui_base::ferro_impl_classes!(LayoutTransformControl: FerroObjectImpl);
impl VisualImpl for LayoutTransformControl {
fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
Self::parent_on_attached_to_visual_tree(this, e);
this.subscribe_layout_transform(this.layout_transform());
this.apply_layout_transform();
}
fn on_detached_from_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
Self::parent_on_detached_from_visual_tree(this, e);
this.unsubscribe_layout_transform();
}
}
impl LayoutableImpl for LayoutTransformControl {
fn arrange_override(this: &Self, final_size: Size) -> Size {
let (Some(transform_root), Some(_)) = (this.transform_root(), this.layout_transform()) else {
this.set_current_value(Self::layout_transform_property(), this.render_transform());
return Self::parent_arrange_override(this, final_size);
};
let mut final_size_transformed = this.compute_largest_transformed_size(final_size);
if Self::is_size_smaller(final_size_transformed, transform_root.desired_size()) {
final_size_transformed = transform_root.desired_size();
}
let transformed_rect = Rect::new(0.0, 0.0, final_size_transformed.width, final_size_transformed.height)
.transform_to_aabb(this.transformation.get());
let final_rect = Rect::new(
-transformed_rect.x + ((final_size.width - transformed_rect.width) / 2.0),
-transformed_rect.y + ((final_size.height - transformed_rect.height) / 2.0),
final_size_transformed.width,
final_size_transformed.height,
);
transform_root.arrange(final_rect);
let arranged_size = transform_root.bounds().size();
if !(Self::is_size_smaller(final_size_transformed, arranged_size)
&& this.child_actual_size.get() == Size::default())
{
this.child_actual_size.set(Size::default());
}
final_size
}
fn measure_override(this: &Self, available_size: Size) -> Size {
let (Some(transform_root), Some(_)) = (this.transform_root(), this.layout_transform()) else {
return Self::parent_measure_override(this, available_size);
};
let measure_size = if this.child_actual_size.get() == Size::default() {
this.compute_largest_transformed_size(available_size)
} else {
this.child_actual_size.get()
};
transform_root.measure(measure_size);
let desired_size = transform_root.desired_size();
let transformed_desired_rect =
Rect::new(0.0, 0.0, desired_size.width, desired_size.height).transform_to_aabb(this.transformation.get());
Size::new(transformed_desired_rect.width, transformed_desired_rect.height)
}
}
ferroui_base::ferro_properties! { impl LayoutTransformControl {
ferro_property!(
pub fn layout_transform_property() -> StyledProperty<Option<Rc<dyn ITransform>>> {
FerroProperty::register::<LayoutTransformControl, _>("LayoutTransform", None)
}
);
ferro_property!(
pub fn use_render_transform_property() -> StyledProperty<bool> {
FerroProperty::register::<LayoutTransformControl, _>("UseRenderTransform", false)
}
);
} }
impl LayoutTransformControl {
fn static_constructor() {
Visual::clip_to_bounds_property().override_default_value::<LayoutTransformControl>(true);
Self::layout_transform_property()
.changed()
.add_class_handler::<LayoutTransformControl>(|x, e| x.on_layout_transform_changed(e));
Decorator::child_property().changed().add_class_handler::<LayoutTransformControl>(|x, _| x.on_child_changed());
Self::use_render_transform_property()
.changed()
.add_class_handler::<LayoutTransformControl>(|x, e| x.on_use_render_transform_property_changed(e));
}
pub fn construct() -> Self {
Self {
base: Decorator::construct(),
render_transform_changed_event: RefCell::new(None),
child_actual_size: Cell::new(Size::default()),
matrix_transform: OnceCell::new(),
transformation: Cell::new(Matrix::IDENTITY),
layout_transform_changed: RefCell::new(None),
}
}
pub fn new() -> Ref<Self> {
instantiate(Self::construct())
}
pub fn layout_transform(&self) -> Option<Rc<dyn ITransform>> {
self.get_value(Self::layout_transform_property())
}
pub fn set_layout_transform(&self, value: Option<Rc<dyn ITransform>>) {
self.set_value(Self::layout_transform_property(), value)
}
pub fn use_render_transform(&self) -> bool {
self.get_value(Self::use_render_transform_property())
}
pub fn set_use_render_transform(&self, value: bool) {
self.set_value(Self::use_render_transform_property(), value)
}
pub fn transform_root(&self) -> Option<Ref<Control>> {
self.child()
}
fn matrix_transform(&self) -> &Ref<MatrixTransform> {
self.matrix_transform.get_or_init(MatrixTransform::new)
}
fn on_use_render_transform_property_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
let should_use_render_transform = e.get_new_value::<bool>();
if should_use_render_transform {
let subscription = Visual::render_transform_property().changed().subscribe(|x| {
if let Some(target) = x.sender().downcast_ref::<LayoutTransformControl>() {
target.set_layout_transform(target.render_transform());
}
});
*self.render_transform_changed_event.borrow_mut() = Some(subscription);
} else {
let subscription = self.render_transform_changed_event.borrow().clone();
if let Some(subscription) = subscription {
subscription.dispose();
}
self.clear_value(Self::layout_transform_property());
}
}
fn on_child_changed(&self) {
if let Some(transform_root) = self.transform_root() {
transform_root.set_render_transform(Some(self.matrix_transform().into()));
transform_root.set_render_transform_origin(RelativePoint::new(0.0, 0.0, RelativeUnit::Absolute));
}
self.apply_layout_transform();
}
fn is_size_smaller(a: Size, b: Size) -> bool {
(a.width + ACCEPTABLE_DELTA < b.width) || (a.height + ACCEPTABLE_DELTA < b.height)
}
fn round_matrix(matrix: Matrix, decimals: i32) -> Matrix {
Matrix::new(
round(matrix.m11, decimals),
round(matrix.m12, decimals),
round(matrix.m21, decimals),
round(matrix.m22, decimals),
matrix.m31,
matrix.m32,
)
}
fn apply_layout_transform(&self) {
let matrix = match self.layout_transform() {
None => Matrix::IDENTITY,
Some(layout_transform) => Self::round_matrix(layout_transform.value(), DECIMALS_AFTER_ROUND),
};
if self.transformation.get() == matrix {
return;
}
self.transformation.set(matrix);
self.matrix_transform().set_matrix(matrix);
self.invalidate_measure();
}
fn compute_largest_transformed_size(&self, mut arrange_bounds: Size) -> Size {
let mut computed_size = Size::default();
let transformation = self.transformation.get();
let infinite_width = arrange_bounds.width.is_infinite();
if infinite_width {
arrange_bounds = arrange_bounds.with_width(arrange_bounds.height);
}
let infinite_height = arrange_bounds.height.is_infinite();
if infinite_height {
arrange_bounds = arrange_bounds.with_height(arrange_bounds.width);
}
let a = transformation.m11;
let b = transformation.m12;
let c = transformation.m21;
let d = transformation.m22;
let max_width_from_width = (arrange_bounds.width / a).abs();
let max_height_from_width = (arrange_bounds.width / c).abs();
let max_width_from_height = (arrange_bounds.height / b).abs();
let max_height_from_height = (arrange_bounds.height / d).abs();
let ideal_width_from_width = max_width_from_width / 2.0;
let ideal_height_from_width = max_height_from_width / 2.0;
let ideal_width_from_height = max_width_from_height / 2.0;
let ideal_height_from_height = max_height_from_height / 2.0;
let slope_from_width = -(max_height_from_width / max_width_from_width);
let slope_from_height = -(max_height_from_height / max_width_from_height);
if (0.0 == arrange_bounds.width) || (0.0 == arrange_bounds.height) {
computed_size = Size::new(arrange_bounds.width, arrange_bounds.height);
} else if infinite_width && infinite_height {
computed_size = Size::new(f64::INFINITY, f64::INFINITY);
} else if !transformation.has_inverse() {
computed_size = Size::new(0.0, 0.0);
} else if (0.0 == b) || (0.0 == c) {
let max_height = if infinite_height { f64::INFINITY } else { max_height_from_height };
let max_width = if infinite_width { f64::INFINITY } else { max_width_from_width };
if (0.0 == b) && (0.0 == c) {
computed_size = Size::new(max_width, max_height);
} else if 0.0 == b {
let computed_height = min(ideal_height_from_width, max_height);
computed_size = Size::new(max_width - ((c * computed_height) / a).abs(), computed_height);
} else if 0.0 == c {
let computed_width = min(ideal_width_from_height, max_width);
computed_size = Size::new(computed_width, max_height - ((b * computed_width) / d).abs());
}
} else if (0.0 == a) || (0.0 == d) {
let max_width = if infinite_height { f64::INFINITY } else { max_width_from_height };
let max_height = if infinite_width { f64::INFINITY } else { max_height_from_width };
if (0.0 == a) && (0.0 == d) {
computed_size = Size::new(max_width, max_height);
} else if 0.0 == a {
let computed_height = min(ideal_height_from_height, max_height);
computed_size = Size::new(max_width - ((d * computed_height) / b).abs(), computed_height);
} else if 0.0 == d {
let computed_width = min(ideal_width_from_width, max_width);
computed_size = Size::new(computed_width, max_height - ((a * computed_width) / c).abs());
}
} else if ideal_height_from_width <= ((slope_from_height * ideal_width_from_width) + max_height_from_height) {
computed_size = Size::new(ideal_width_from_width, ideal_height_from_width);
} else if ideal_height_from_height <= ((slope_from_width * ideal_width_from_height) + max_height_from_width) {
computed_size = Size::new(ideal_width_from_height, ideal_height_from_height);
} else {
let computed_width =
(max_height_from_height - max_height_from_width) / (slope_from_width - slope_from_height);
computed_size = Size::new(computed_width, (slope_from_width * computed_width) + max_height_from_width);
}
computed_size
}
fn on_layout_transform_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
if self.is_attached_to_visual_tree() {
self.unsubscribe_layout_transform();
self.subscribe_layout_transform(e.get_new_value::<Option<Rc<dyn ITransform>>>());
}
self.apply_layout_transform();
}
fn subscribe_layout_transform(&self, transform: Option<Rc<dyn ITransform>>) {
let Some(transform) = transform else { return };
let Some(transform) = transform.as_mutable_transform() else { return };
let weak = self.to_ref().downgrade();
let subscription = transform.changed(Rc::new(move || {
if let Some(this) = weak.upgrade() {
this.apply_layout_transform();
}
}));
*self.layout_transform_changed.borrow_mut() = Some(subscription);
}
fn unsubscribe_layout_transform(&self) {
let subscription = self.layout_transform_changed.borrow_mut().take();
if let Some(subscription) = subscription {
subscription.dispose();
}
}
}
fn round(value: f64, decimals: i32) -> f64 {
if value.abs() < 1e16 {
let power10 = 10f64.powi(decimals);
(value * power10).round_ties_even() / power10
} else {
value
}
}