use crate::primitives::{TemplatedControl, TemplatedControlImpl};
use crate::{Border, Canvas, Control, ControlImpl, Viewbox};
use ferroui_base::input::InputElementImpl;
use ferroui_base::interactivity::InteractiveImpl;
use ferroui_base::layout::LayoutableImpl;
use ferroui_base::{FerroObjectImpl, StyledElementImpl, VisualImpl};
use ferroui_base::media::{Stretch, StretchDirection};
use ferroui_base::{Matrix, Point, Rect, Ref, Size, StyledElement, Vector};
fn sized<T>(control: Ref<T>, width: f64, height: f64) -> Ref<T>
where
T: ferroui_base::ObjectType + ferroui_base::Upcast<ferroui_base::layout::Layoutable>,
{
{
let layoutable: &ferroui_base::layout::Layoutable = (*control).upcast();
layoutable.set_width(width);
layoutable.set_height(height);
}
control
}
fn viewbox_with_rectangle() -> Ref<Viewbox> {
let target = Viewbox::new();
target.set_child(sized(Border::new(), 100.0, 50.0));
target
}
fn try_get_scale(viewbox: &Viewbox) -> Option<Vector> {
let matrix = viewbox.internal_transform()?.value();
Some(Matrix::try_decompose_transform(matrix).map(|decomposed| decomposed.scale).unwrap_or_default())
}
#[test]
fn viewbox_stretch_uniform_child() {
let target = viewbox_with_rectangle();
target.measure(Size::new(200.0, 200.0));
target.arrange(Rect::from_position_size(Point::new(0.0, 0.0), target.desired_size()));
assert_eq!(target.desired_size(), Size::new(200.0, 100.0));
let scale = try_get_scale(&target).unwrap();
assert_eq!(scale.x, 2.0);
assert_eq!(scale.y, 2.0);
}
#[test]
fn viewbox_stretch_none_child() {
let target = viewbox_with_rectangle();
target.set_stretch(Stretch::None);
target.measure(Size::new(200.0, 200.0));
target.arrange(Rect::from_position_size(Point::new(0.0, 0.0), target.desired_size()));
assert_eq!(target.desired_size(), Size::new(100.0, 50.0));
let scale = try_get_scale(&target).unwrap();
assert_eq!(scale.x, 1.0);
assert_eq!(scale.y, 1.0);
}
#[test]
fn viewbox_stretch_fill_child() {
let target = viewbox_with_rectangle();
target.set_stretch(Stretch::Fill);
target.measure(Size::new(200.0, 200.0));
target.arrange(Rect::from_position_size(Point::new(0.0, 0.0), target.desired_size()));
assert_eq!(target.desired_size(), Size::new(200.0, 200.0));
let scale = try_get_scale(&target).unwrap();
assert_eq!(scale.x, 2.0);
assert_eq!(scale.y, 4.0);
}
#[test]
fn viewbox_stretch_uniform_to_fill_child() {
let target = viewbox_with_rectangle();
target.set_stretch(Stretch::UniformToFill);
target.measure(Size::new(200.0, 200.0));
target.arrange(Rect::from_position_size(Point::new(0.0, 0.0), target.desired_size()));
assert_eq!(target.desired_size(), Size::new(200.0, 200.0));
let scale = try_get_scale(&target).unwrap();
assert_eq!(scale.x, 4.0);
assert_eq!(scale.y, 4.0);
}
#[test]
fn viewbox_stretch_uniform_child_with_unrestricted_width() {
let target = viewbox_with_rectangle();
target.measure(Size::new(f64::INFINITY, 200.0));
target.arrange(Rect::from_position_size(Point::new(0.0, 0.0), target.desired_size()));
assert_eq!(target.desired_size(), Size::new(400.0, 200.0));
let scale = try_get_scale(&target).unwrap();
assert_eq!(scale.x, 4.0);
assert_eq!(scale.y, 4.0);
}
#[test]
fn viewbox_stretch_uniform_child_with_unrestricted_height() {
let target = viewbox_with_rectangle();
target.measure(Size::new(200.0, f64::INFINITY));
target.arrange(Rect::from_position_size(Point::new(0.0, 0.0), target.desired_size()));
assert_eq!(target.desired_size(), Size::new(200.0, 100.0));
let scale = try_get_scale(&target).unwrap();
assert_eq!(scale.x, 2.0);
assert_eq!(scale.y, 2.0);
}
type SizeAndScaleCase = (f64, f64, f64, f64, f64, f64, f64);
fn check_size_and_scale(stretch_direction: StretchDirection, cases: &[SizeAndScaleCase]) {
for &(child_width, child_height, viewbox_width, viewbox_height, expected_width, expected_height, expected_scale) in
cases
{
let target = Viewbox::new();
target.set_child(sized(Control::new(), child_width, child_height));
target.set_stretch_direction(stretch_direction);
target.measure(Size::new(viewbox_width, viewbox_height));
target.arrange(Rect::from_position_size(Point::default(), target.desired_size()));
assert_eq!(target.desired_size(), Size::new(expected_width, expected_height));
let scale = try_get_scale(&target).unwrap();
assert_eq!(scale.x, expected_scale);
assert_eq!(scale.y, expected_scale);
}
}
#[test]
fn viewbox_should_return_correct_size_and_scale_stretch_direction_down_only() {
check_size_and_scale(
StretchDirection::DownOnly,
&[
(50.0, 100.0, 50.0, 100.0, 50.0, 100.0, 1.0),
(50.0, 100.0, 150.0, 150.0, 50.0, 100.0, 1.0),
(50.0, 100.0, 25.0, 50.0, 25.0, 50.0, 0.5),
],
);
}
#[test]
fn viewbox_should_return_correct_size_and_scale_stretch_direction_up_only() {
check_size_and_scale(
StretchDirection::UpOnly,
&[
(50.0, 100.0, 50.0, 100.0, 50.0, 100.0, 1.0),
(50.0, 100.0, 25.0, 50.0, 25.0, 50.0, 1.0),
(50.0, 100.0, 150.0, 150.0, 75.0, 150.0, 1.5),
],
);
}
#[test]
fn child_should_be_logical_child_of_viewbox() {
let target = Viewbox::new();
assert!(target.logical_children().is_empty());
let child = Canvas::new();
target.set_child(&child);
assert_eq!(target.logical_children().to_vec(), vec![child.clone().upcast::<StyledElement>()]);
assert_eq!(child.parent().unwrap(), target);
target.set_child(None);
assert!(target.logical_children().is_empty());
assert!(child.parent().is_none());
}
#[test]
fn changing_child_should_invalidate_layout() {
let target = Viewbox::new();
target.set_child(sized(Canvas::new(), 100.0, 100.0));
target.measure(Size::INFINITY);
target.arrange(Rect::from_size(target.desired_size()));
assert_eq!(target.desired_size(), Size::new(100.0, 100.0));
target.set_child(sized(Canvas::new(), 200.0, 200.0));
target.measure(Size::INFINITY);
target.arrange(Rect::from_size(target.desired_size()));
assert_eq!(target.desired_size(), Size::new(200.0, 200.0));
}
#[test]
fn child_is_hosted_in_a_container_visual() {
let target = Viewbox::new();
let child = Canvas::new();
target.set_child(&child);
assert_eq!(target.visual_children().count(), 1);
let container = target.visual_children().get(0);
assert_eq!(child.visual_parent().unwrap(), container);
assert_eq!(container.visual_parent().unwrap(), target);
target.set_child(None);
assert!(child.visual_parent().is_none());
assert_eq!(target.visual_children().count(), 1);
}
struct FooData {
foo: String,
}
ferroui_base::ferro_model!(FooData, |b| b
.read_only::<ferroui_base::data::core::Value<String>>("Foo", |data| data.foo.clone()));
#[test]
fn child_data_context_binding_works() {
use crate::test_support::string_of;
use ferroui_base::data::model::Model;
use ferroui_base::data::ReflectionBinding;
let data = Model::new_model(FooData { foo: "foo".to_string() });
let child = Canvas::new();
child.bind_binding(StyledElement::data_context_property().as_property(), &*ReflectionBinding::new("Foo"));
let target = Viewbox::new();
target.set_data_context(Some(data));
target.set_child(&child);
assert_eq!(Some("foo".to_string()), target.child().unwrap().data_context().as_ref().and_then(string_of));
}
#[repr(C)]
struct TestTemplatedControl {
base: TemplatedControl,
}
ferroui_base::ferro_class!(TestTemplatedControl: TemplatedControl);
ferroui_base::ferro_impl_classes!(
TestTemplatedControl: FerroObjectImpl,
StyledElementImpl,
VisualImpl,
LayoutableImpl,
InteractiveImpl,
InputElementImpl,
ControlImpl,
TemplatedControlImpl
);
impl TestTemplatedControl {
ferroui_base::ferro_property!(
fn my_control_property() -> ferroui_base::StyledProperty<Option<Ref<Control>>> {
ferroui_base::FerroProperty::register::<TestTemplatedControl, _>("MyControl", None)
}
);
fn new() -> Ref<Self> {
ferroui_base::instantiate(Self { base: TemplatedControl::construct() })
}
fn set_my_control(&self, value: Option<Ref<Control>>) {
self.set_value(Self::my_control_property(), value)
}
}
#[test]
fn content_presented_in_viewbox_should_be_reparented_when_template_changes() {
use crate::presenters::ContentPresenter;
use crate::templates::{FuncControlTemplate, IControlTemplate};
use crate::test_support::{test_scope, TestRoot};
use ferroui_base::data::TemplateBinding;
use ferroui_base::layout::ILayoutManager;
use std::rc::Rc;
let _scope = test_scope();
fn create_template() -> Option<Rc<dyn IControlTemplate>> {
Some(FuncControlTemplate::for_type::<TestTemplatedControl>(|_, _| {
let presenter = ContentPresenter::new();
presenter.bind_binding(
ContentPresenter::content_property().as_property(),
&TemplateBinding::new(TestTemplatedControl::my_control_property().as_property()),
);
let viewbox = Viewbox::new();
viewbox.set_child(presenter);
viewbox.upcast()
}))
}
let child = Canvas::new();
let target = TestTemplatedControl::new();
target.set_my_control(Some(child.clone().upcast()));
target.set_template(create_template());
let root = TestRoot::with_child(&target);
root.execute_initial_layout_pass();
let old_presenter = child.visual_parent().and_then(|parent| parent.cast::<ContentPresenter>());
let old_presenter = old_presenter.expect("a content presenter");
target.set_template(create_template());
target.apply_template();
root.layout_manager().execute_layout_pass();
let new_presenter = child.visual_parent().and_then(|parent| parent.cast::<ContentPresenter>());
let new_presenter = new_presenter.expect("a content presenter");
assert!(!old_presenter.ptr_eq(&new_presenter));
assert!(child.get_visual_root().is_some_and(|visual_root| visual_root.ptr_eq(&root)));
}