Skip to main content

ferroui_controls/
viewbox.rs

1use crate::{Control, ControlImpl, Decorator};
2use ferroui_base::input::InputElementImpl;
3use ferroui_base::interactivity::InteractiveImpl;
4use ferroui_base::layout::{Layoutable, LayoutableImpl};
5use ferroui_base::media::immutable::ImmutableTransform;
6use ferroui_base::media::{ITransform, MediaExtensions, Stretch, StretchDirection};
7use ferroui_base::{
8    ferro_class, ferro_impl_classes, ferro_property, instantiate, FerroObject, FerroObjectImpl, FerroObjectImplExt,
9    FerroProperty, FerroPropertyChangedEventArgs, Matrix, Nullable, Rect, Ref, RelativePoint, Size, StyledElement,
10    StyledElementImpl, StyledProperty, Visual, VisualImpl,
11};
12use std::cell::{OnceCell, RefCell};
13use std::rc::Rc;
14
15/// A control used to scale a single child.
16#[repr(C)]
17pub struct Viewbox {
18    base: Control,
19    container_visual: OnceCell<Ref<ViewboxContainer>>,
20}
21
22ferro_class!(Viewbox: Control);
23ferroui_base::ferro_class_info!(Viewbox { new: Viewbox::new });
24ferro_impl_classes!(Viewbox: StyledElementImpl, VisualImpl, InteractiveImpl, InputElementImpl, ControlImpl);
25
26impl FerroObjectImpl for Viewbox {
27    fn constructed(this: &Self) {
28        Self::parent_constructed(this);
29
30        // The child control is hosted inside a container control so that the
31        // transform can be applied independently of the viewbox and child
32        // transforms.
33        let container_visual = ViewboxContainer::new();
34        container_visual.set_render_transform_origin(RelativePoint::TOP_LEFT);
35        container_visual.set_parent(this.to_ref());
36        this.visual_children().add(container_visual.clone().upcast());
37        let _ = this.container_visual.set(container_visual);
38    }
39
40    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
41        Self::parent_on_property_changed(this, change);
42
43        if change.property() == Self::child_property().as_property() {
44            let (old_child, new_child) = change.get_old_and_new_value::<Option<Ref<Control>>>();
45
46            if let Some(old_child) = old_child {
47                old_child.set_parent(None);
48                this.logical_children().remove(&old_child.upcast());
49            }
50
51            this.container().set_child(new_child.clone());
52
53            if let Some(new_child) = new_child {
54                new_child.set_parent(this.to_ref());
55                this.logical_children().add(new_child.upcast());
56            }
57
58            this.invalidate_measure();
59        } else if change.property() == StyledElement::templated_parent_property().as_property() {
60            // Update the container's templated parent, otherwise its
61            // descendants aren't reachable during the template's teardown and
62            // incorrectly stay attached to the visual tree.
63            if let Some(templated_parent) = change.get_new_value::<Option<Ref<FerroObject>>>() {
64                this.container().set_templated_parent(templated_parent);
65            }
66        }
67    }
68}
69
70impl LayoutableImpl for Viewbox {
71    fn measure_override(this: &Self, available_size: Size) -> Size {
72        let child = this.container();
73
74        child.measure(Size::INFINITY);
75
76        let child_size = child.desired_size();
77
78        MediaExtensions::calculate_size(this.stretch(), available_size, child_size, this.stretch_direction())
79    }
80
81    fn arrange_override(this: &Self, final_size: Size) -> Size {
82        let child = this.container();
83
84        let child_size = child.desired_size();
85        let scale =
86            MediaExtensions::calculate_scaling(this.stretch(), final_size, child_size, this.stretch_direction());
87
88        this.set_internal_transform(Some(Rc::new(ImmutableTransform::new(Matrix::create_scale(scale.x, scale.y)))));
89
90        child.arrange(Rect::from_size(child_size));
91
92        child_size * scale
93    }
94}
95
96ferroui_base::ferro_properties! { impl Viewbox {
97    ferro_property!(
98        /// Defines the `Stretch` property.
99        pub fn stretch_property() -> StyledProperty<Stretch> {
100            FerroProperty::register::<Viewbox, _>("Stretch", Stretch::Uniform)
101        }
102    );
103
104    ferro_property!(
105        /// Defines the `StretchDirection` property.
106        pub fn stretch_direction_property() -> StyledProperty<StretchDirection> {
107            FerroProperty::register::<Viewbox, _>("StretchDirection", StretchDirection::Both)
108        }
109    );
110
111    ferro_property!(
112        /// Defines the `Child` property.
113        pub fn child_property() -> StyledProperty<Option<Ref<Control>>> {
114            Decorator::child_property().add_owner::<Viewbox>()
115        }
116    );
117} }
118
119impl Viewbox {
120    fn static_constructor() {
121        Visual::clip_to_bounds_property().override_default_value::<Viewbox>(true);
122        Layoutable::use_layout_rounding_property().override_default_value::<Viewbox>(true);
123        Layoutable::affects_measure::<Viewbox>(&[
124            Self::stretch_property().as_property(),
125            Self::stretch_direction_property().as_property(),
126        ]);
127    }
128
129    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
130    pub fn construct() -> Self {
131        Self { base: Control::construct(), container_visual: OnceCell::new() }
132    }
133
134    pub fn new() -> Ref<Self> {
135        instantiate(Self::construct())
136    }
137
138    fn container(&self) -> &Ref<ViewboxContainer> {
139        self.container_visual.get().expect("the viewbox has been constructed")
140    }
141
142    /// The stretch mode, which determines how child fits into the available
143    /// space.
144    pub fn stretch(&self) -> Stretch {
145        self.get_value(Self::stretch_property())
146    }
147
148    pub fn set_stretch(&self, value: Stretch) {
149        self.set_value(Self::stretch_property(), value)
150    }
151
152    /// A value controlling in what direction contents will be stretched.
153    pub fn stretch_direction(&self) -> StretchDirection {
154        self.get_value(Self::stretch_direction_property())
155    }
156
157    pub fn set_stretch_direction(&self, value: StretchDirection) {
158        self.set_value(Self::stretch_direction_property(), value)
159    }
160
161    /// The child of the viewbox.
162    pub fn child(&self) -> Option<Ref<Control>> {
163        self.get_value(Self::child_property())
164    }
165
166    pub fn set_child(&self, value: impl Into<Nullable<Control>>) {
167        self.set_value(Self::child_property(), value.into().0)
168    }
169
170    /// The transform applied to the container of the child.
171    #[cfg(test)]
172    pub(crate) fn internal_transform(&self) -> Option<Rc<dyn ITransform>> {
173        self.container().render_transform()
174    }
175
176    pub(crate) fn set_internal_transform(&self, value: Option<Rc<dyn ITransform>>) {
177        self.container().set_render_transform(value)
178    }
179}
180
181/// A simple container control which hosts its child as a visual but not
182/// logical child.
183#[repr(C)]
184pub(crate) struct ViewboxContainer {
185    base: Control,
186    child: RefCell<Option<Ref<Control>>>,
187}
188
189ferro_class!(ViewboxContainer: Control);
190ferro_impl_classes!(
191    ViewboxContainer: FerroObjectImpl,
192    StyledElementImpl,
193    VisualImpl,
194    LayoutableImpl,
195    InteractiveImpl,
196    InputElementImpl,
197    ControlImpl
198);
199
200impl ViewboxContainer {
201    fn new() -> Ref<Self> {
202        instantiate(Self { base: Control::construct(), child: RefCell::new(None) })
203    }
204
205    fn set_child(&self, value: Option<Ref<Control>>) {
206        let old = self.child.borrow().clone();
207        if old != value {
208            if let Some(old) = old {
209                self.visual_children().remove(&old.upcast());
210            }
211
212            *self.child.borrow_mut() = value.clone();
213
214            if let Some(value) = value {
215                self.visual_children().add(value.upcast());
216            }
217
218            self.invalidate_measure();
219        }
220    }
221}