Skip to main content

ferroui_controls/
image.rs

1use crate::{Control, ControlImpl};
2use ferroui_base::input::InputElementImpl;
3use ferroui_base::interactivity::InteractiveImpl;
4use ferroui_base::layout::{Layoutable, LayoutableImpl};
5use ferroui_base::media::imaging::BitmapBlendingMode;
6use ferroui_base::media::{
7    DrawingContext, DrawingImage, IImage, MediaExtensions, RenderOptions, Stretch, StretchDirection,
8};
9use ferroui_base::reactive::IDisposable;
10use ferroui_base::{
11    ferro_class, ferro_impl_classes, ferro_property, instantiate, FerroObjectImpl, FerroObjectImplExt, FerroProperty,
12    FerroPropertyChangedEventArgs, Rect, Ref, Size, StyledElementImpl, StyledProperty, Upcast, Visual, VisualImpl,
13    VisualImplExt, VisualTreeAttachmentEventArgs,
14};
15use std::cell::{Cell, RefCell};
16use std::rc::Rc;
17
18/// Displays an [`IImage`].
19#[repr(C)]
20pub struct Image {
21    base: Control,
22    current_drawing_bounds: Cell<Rect>,
23    subscribed_to_drawing_image_source: Cell<bool>,
24    source_invalidated_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
25}
26
27ferro_class!(Image: Control);
28ferroui_base::ferro_class_info!(Image { new: Image::new });
29ferro_impl_classes!(Image: StyledElementImpl, InteractiveImpl, InputElementImpl);
30
31impl ControlImpl for Image {
32    fn on_create_automation_peer(this: &Self) -> Ref<crate::automation::peers::AutomationPeer> {
33        crate::automation::peers::ImageAutomationPeer::new(this).upcast()
34    }
35}
36
37impl FerroObjectImpl for Image {
38    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
39        Self::parent_on_property_changed(this, change);
40
41        if change.property() == Self::source_property().as_property() {
42            let (old_value, new_value) = change.get_old_and_new_value::<Option<Rc<dyn IImage>>>();
43
44            this.current_drawing_bounds.set(Rect::default());
45            if Self::as_drawing_image(old_value.as_ref()).is_some() && this.subscribed_to_drawing_image_source.get() {
46                this.unsubscribe_from_source();
47            }
48
49            if let Some(new_drawing_image) = Self::as_drawing_image(new_value.as_ref()) {
50                if this.is_attached_to_visual_tree() {
51                    this.subscribe_to_source(&new_drawing_image);
52                }
53            }
54        }
55    }
56}
57
58impl VisualImpl for Image {
59    /// Renders the control.
60    fn render(this: &Self, context: &mut DrawingContext) {
61        let source = this.source();
62        let bounds = this.bounds();
63
64        if let Some(source) = source {
65            if bounds.width > 0.0 && bounds.height > 0.0 {
66                let view_port = Rect::from_size(bounds.size());
67                let source_size = source.size();
68
69                let scale = MediaExtensions::calculate_scaling(
70                    this.stretch(),
71                    bounds.size(),
72                    source_size,
73                    this.stretch_direction(),
74                );
75                let scaled_size = source_size * scale;
76                let dest_rect = view_port.center_rect(Rect::from_size(scaled_size)).intersect(view_port);
77                let source_rect =
78                    Rect::from_size(source_size).center_rect(Rect::from_size(dest_rect.size() / scale));
79
80                let visual: &Visual = this.upcast();
81                let render_options = RenderOptions {
82                    text_rendering_mode: RenderOptions::get_text_rendering_mode(visual),
83                    bitmap_interpolation_mode: RenderOptions::get_bitmap_interpolation_mode(visual),
84                    edge_mode: RenderOptions::get_edge_mode(visual),
85                    bitmap_blending_mode: this.blend_mode(),
86                    requires_full_opacity_handling: RenderOptions::get_requires_full_opacity_handling(visual),
87                };
88
89                let state = context.push_render_options(render_options);
90                context.draw_image_with_rects(&*source, source_rect, dest_rect);
91                context.pop(state);
92            }
93        }
94    }
95
96    fn bypass_flow_direction_policies(_this: &Self) -> bool {
97        true
98    }
99
100    fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
101        Self::parent_on_attached_to_visual_tree(this, e);
102
103        if !this.subscribed_to_drawing_image_source.get() {
104            if let Some(drawing_image) = Self::as_drawing_image(this.source().as_ref()) {
105                this.subscribe_to_source(&drawing_image);
106            }
107        }
108    }
109
110    fn on_detached_from_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
111        Self::parent_on_detached_from_visual_tree(this, e);
112
113        if this.subscribed_to_drawing_image_source.get() && Self::as_drawing_image(this.source().as_ref()).is_some() {
114            this.unsubscribe_from_source();
115        }
116    }
117}
118
119impl LayoutableImpl for Image {
120    /// Measures the control.
121    fn measure_override(this: &Self, available_size: Size) -> Size {
122        let source = this.source();
123        let mut result = Size::default();
124
125        if let Some(source) = source {
126            result = MediaExtensions::calculate_size(
127                this.stretch(),
128                available_size,
129                source.size(),
130                this.stretch_direction(),
131            );
132        }
133
134        result
135    }
136
137    fn arrange_override(this: &Self, final_size: Size) -> Size {
138        let source = this.source();
139
140        if let Some(source) = source {
141            let source_size = source.size();
142            MediaExtensions::calculate_size(this.stretch(), final_size, source_size, StretchDirection::Both)
143        } else {
144            Size::default()
145        }
146    }
147}
148
149ferroui_base::ferro_properties! { impl Image {
150    ferro_property!(
151        /// Defines the `Source` property.
152        pub fn source_property() -> StyledProperty<Option<Rc<dyn IImage>>> {
153            FerroProperty::register::<Image, _>("Source", None)
154        }
155    );
156
157    ferro_property!(
158        /// Defines the `BlendMode` property.
159        pub fn blend_mode_property() -> StyledProperty<BitmapBlendingMode> {
160            FerroProperty::register::<Image, _>("BlendMode", BitmapBlendingMode::Unspecified)
161        }
162    );
163
164    ferro_property!(
165        /// Defines the `Stretch` property.
166        pub fn stretch_property() -> StyledProperty<Stretch> {
167            FerroProperty::register::<Image, _>("Stretch", Stretch::Uniform)
168        }
169    );
170
171    ferro_property!(
172        /// Defines the `StretchDirection` property.
173        pub fn stretch_direction_property() -> StyledProperty<StretchDirection> {
174            FerroProperty::register::<Image, _>("StretchDirection", StretchDirection::Both)
175        }
176    );
177} }
178
179impl Image {
180    fn static_constructor() {
181        Visual::affects_render::<Image>(&[
182            Self::source_property().as_property(),
183            Self::stretch_property().as_property(),
184            Self::stretch_direction_property().as_property(),
185            Self::blend_mode_property().as_property(),
186        ]);
187        Layoutable::affects_measure::<Image>(&[
188            Self::source_property().as_property(),
189            Self::stretch_property().as_property(),
190            Self::stretch_direction_property().as_property(),
191        ]);
192        crate::automation::AutomationProperties::control_type_override_property()
193            .override_default_value::<Image>(Some(crate::automation::peers::AutomationControlType::Image));
194    }
195
196    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
197    pub fn construct() -> Self {
198        Self {
199            base: Control::construct(),
200            current_drawing_bounds: Cell::new(Rect::default()),
201            subscribed_to_drawing_image_source: Cell::new(false),
202            source_invalidated_subscription: RefCell::new(None),
203        }
204    }
205
206    pub fn new() -> Ref<Self> {
207        instantiate(Self::construct())
208    }
209
210    /// Gets the image that will be displayed.
211    pub fn source(&self) -> Option<Rc<dyn IImage>> {
212        self.get_value(Self::source_property())
213    }
214
215    /// Sets the image that will be displayed.
216    pub fn set_source(&self, value: Option<Rc<dyn IImage>>) {
217        self.set_value(Self::source_property(), value)
218    }
219
220    /// Gets the blend mode for the image.
221    pub fn blend_mode(&self) -> BitmapBlendingMode {
222        self.get_value(Self::blend_mode_property())
223    }
224
225    /// Sets the blend mode for the image.
226    pub fn set_blend_mode(&self, value: BitmapBlendingMode) {
227        self.set_value(Self::blend_mode_property(), value)
228    }
229
230    /// Gets a value controlling how the image will be stretched.
231    pub fn stretch(&self) -> Stretch {
232        self.get_value(Self::stretch_property())
233    }
234
235    /// Sets a value controlling how the image will be stretched.
236    pub fn set_stretch(&self, value: Stretch) {
237        self.set_value(Self::stretch_property(), value)
238    }
239
240    /// Gets a value controlling in what direction the image will be
241    /// stretched.
242    pub fn stretch_direction(&self) -> StretchDirection {
243        self.get_value(Self::stretch_direction_property())
244    }
245
246    /// Sets a value controlling in what direction the image will be
247    /// stretched.
248    pub fn set_stretch_direction(&self, value: StretchDirection) {
249        self.set_value(Self::stretch_direction_property(), value)
250    }
251
252    /// The image as a drawing image, when it is one.
253    fn as_drawing_image(image: Option<&Rc<dyn IImage>>) -> Option<Ref<DrawingImage>> {
254        image?.as_object()?.downcast_ref::<DrawingImage>().map(DrawingImage::to_ref)
255    }
256
257    fn subscribe_to_source(&self, drawing_image: &Ref<DrawingImage>) {
258        self.subscribed_to_drawing_image_source.set(true);
259
260        let weak = self.to_ref().downgrade();
261        let subscription = drawing_image.invalidated(move || {
262            if let Some(this) = weak.upgrade() {
263                this.on_source_invalidated();
264            }
265        });
266
267        let previous = self.source_invalidated_subscription.replace(Some(subscription));
268        if let Some(previous) = previous {
269            previous.dispose();
270        }
271    }
272
273    fn unsubscribe_from_source(&self) {
274        self.subscribed_to_drawing_image_source.set(false);
275
276        let subscription = self.source_invalidated_subscription.take();
277        if let Some(subscription) = subscription {
278            subscription.dispose();
279        }
280    }
281
282    fn on_source_invalidated(&self) {
283        if !self.is_attached_to_visual_tree() {
284            return;
285        }
286
287        let Some(drawing_image) = Self::as_drawing_image(self.source().as_ref()) else {
288            return;
289        };
290
291        if let Some(drawing) = drawing_image.drawing() {
292            let bounds = drawing.get_bounds();
293            if bounds != self.current_drawing_bounds.get() {
294                self.invalidate_measure();
295            }
296            self.current_drawing_bounds.set(bounds);
297        }
298    }
299}