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#[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 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 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 pub fn source_property() -> StyledProperty<Option<Rc<dyn IImage>>> {
153 FerroProperty::register::<Image, _>("Source", None)
154 }
155 );
156
157 ferro_property!(
158 pub fn blend_mode_property() -> StyledProperty<BitmapBlendingMode> {
160 FerroProperty::register::<Image, _>("BlendMode", BitmapBlendingMode::Unspecified)
161 }
162 );
163
164 ferro_property!(
165 pub fn stretch_property() -> StyledProperty<Stretch> {
167 FerroProperty::register::<Image, _>("Stretch", Stretch::Uniform)
168 }
169 );
170
171 ferro_property!(
172 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 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 pub fn source(&self) -> Option<Rc<dyn IImage>> {
212 self.get_value(Self::source_property())
213 }
214
215 pub fn set_source(&self, value: Option<Rc<dyn IImage>>) {
217 self.set_value(Self::source_property(), value)
218 }
219
220 pub fn blend_mode(&self) -> BitmapBlendingMode {
222 self.get_value(Self::blend_mode_property())
223 }
224
225 pub fn set_blend_mode(&self, value: BitmapBlendingMode) {
227 self.set_value(Self::blend_mode_property(), value)
228 }
229
230 pub fn stretch(&self) -> Stretch {
232 self.get_value(Self::stretch_property())
233 }
234
235 pub fn set_stretch(&self, value: Stretch) {
237 self.set_value(Self::stretch_property(), value)
238 }
239
240 pub fn stretch_direction(&self) -> StretchDirection {
243 self.get_value(Self::stretch_direction_property())
244 }
245
246 pub fn set_stretch_direction(&self, value: StretchDirection) {
249 self.set_value(Self::stretch_direction_property(), value)
250 }
251
252 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}