freya_core/elements/
image.rs

1use std::{
2    any::Any,
3    borrow::Cow,
4    cell::RefCell,
5    collections::HashMap,
6    rc::Rc,
7};
8
9use bytes::Bytes;
10use freya_engine::prelude::{
11    ClipOp,
12    CubicResampler,
13    FilterMode,
14    MipmapMode,
15    Paint,
16    SamplingOptions,
17    SkImage,
18    SkRect,
19};
20use rustc_hash::FxHashMap;
21use torin::prelude::Size2D;
22
23use crate::{
24    data::{
25        AccessibilityData,
26        EffectData,
27        LayoutData,
28        StyleState,
29        TextStyleData,
30    },
31    diff_key::DiffKey,
32    element::{
33        ClipContext,
34        Element,
35        ElementExt,
36        EventHandlerType,
37        LayoutContext,
38        RenderContext,
39    },
40    events::name::EventName,
41    prelude::{
42        AccessibilityExt,
43        ChildrenExt,
44        ContainerExt,
45        ContainerWithContentExt,
46        EventHandlersExt,
47        ImageExt,
48        KeyExt,
49        LayerExt,
50        LayoutExt,
51        MaybeExt,
52    },
53    tree::DiffModifies,
54};
55
56#[derive(Default, Clone, Debug, PartialEq)]
57pub enum ImageCover {
58    #[default]
59    Fill,
60    Center,
61}
62
63#[derive(Default, Clone, Debug, PartialEq)]
64pub enum AspectRatio {
65    #[default]
66    Min,
67    Max,
68    Fit,
69    None,
70}
71
72#[derive(Clone, Debug, PartialEq, Default)]
73pub enum SamplingMode {
74    #[default]
75    Nearest,
76    Bilinear,
77    Trilinear,
78    Mitchell,
79    CatmullRom,
80}
81
82#[derive(Clone)]
83pub struct ImageHolder {
84    pub image: Rc<RefCell<SkImage>>,
85    pub bytes: Bytes,
86}
87
88impl PartialEq for ImageHolder {
89    fn eq(&self, other: &Self) -> bool {
90        Rc::ptr_eq(&self.image, &other.image)
91    }
92}
93
94#[derive(Debug, Default, Clone, PartialEq)]
95pub struct ImageData {
96    pub sampling_mode: SamplingMode,
97    pub aspect_ratio: AspectRatio,
98    pub image_cover: ImageCover,
99}
100
101#[derive(PartialEq, Clone)]
102pub struct ImageElement {
103    pub accessibility: AccessibilityData,
104    pub layout: LayoutData,
105    pub event_handlers: FxHashMap<EventName, EventHandlerType>,
106    pub image_holder: ImageHolder,
107    pub image_data: ImageData,
108    pub relative_layer: i16,
109}
110
111impl ElementExt for ImageElement {
112    fn changed(&self, other: &Rc<dyn ElementExt>) -> bool {
113        let Some(image) = (other.as_ref() as &dyn Any).downcast_ref::<ImageElement>() else {
114            return false;
115        };
116        self != image
117    }
118
119    fn diff(&self, other: &Rc<dyn ElementExt>) -> DiffModifies {
120        let Some(image) = (other.as_ref() as &dyn Any).downcast_ref::<ImageElement>() else {
121            return DiffModifies::all();
122        };
123
124        let mut diff = DiffModifies::empty();
125
126        if self.accessibility != image.accessibility {
127            diff.insert(DiffModifies::ACCESSIBILITY);
128        }
129
130        if self.relative_layer != image.relative_layer {
131            diff.insert(DiffModifies::LAYER);
132        }
133
134        if self.layout != image.layout {
135            diff.insert(DiffModifies::LAYOUT);
136        }
137
138        if self.image_holder != image.image_holder {
139            diff.insert(DiffModifies::LAYOUT);
140            diff.insert(DiffModifies::STYLE);
141        }
142
143        diff
144    }
145
146    fn layout(&'_ self) -> Cow<'_, LayoutData> {
147        Cow::Borrowed(&self.layout)
148    }
149
150    fn effect(&'_ self) -> Option<Cow<'_, EffectData>> {
151        None
152    }
153
154    fn style(&'_ self) -> Cow<'_, StyleState> {
155        Cow::Owned(StyleState::default())
156    }
157
158    fn text_style(&'_ self) -> Cow<'_, TextStyleData> {
159        Cow::Owned(TextStyleData::default())
160    }
161
162    fn accessibility(&'_ self) -> Cow<'_, AccessibilityData> {
163        Cow::Borrowed(&self.accessibility)
164    }
165
166    fn relative_layer(&self) -> i16 {
167        self.relative_layer
168    }
169
170    fn should_measure_inner_children(&self) -> bool {
171        true
172    }
173
174    fn should_hook_measurement(&self) -> bool {
175        true
176    }
177
178    fn measure(&self, context: LayoutContext) -> Option<(Size2D, Rc<dyn Any>)> {
179        let image = self.image_holder.image.borrow();
180
181        let image_width = image.width() as f32;
182        let image_height = image.height() as f32;
183
184        let width_ratio = context.area_size.width / image.width() as f32;
185        let height_ratio = context.area_size.height / image.height() as f32;
186
187        let size = match self.image_data.aspect_ratio {
188            AspectRatio::Max => {
189                let ratio = width_ratio.max(height_ratio);
190
191                Size2D::new(image_width * ratio, image_height * ratio)
192            }
193            AspectRatio::Min => {
194                let ratio = width_ratio.min(height_ratio);
195
196                Size2D::new(image_width * ratio, image_height * ratio)
197            }
198            AspectRatio::Fit => Size2D::new(image_width, image_height),
199            AspectRatio::None => *context.area_size,
200        };
201
202        Some((size, Rc::new(size)))
203    }
204
205    fn clip(&self, context: ClipContext) {
206        let area = context.visible_area;
207        context.canvas.clip_rect(
208            SkRect::new(area.min_x(), area.min_y(), area.max_x(), area.max_y()),
209            ClipOp::Intersect,
210            true,
211        );
212    }
213
214    fn render(&self, context: RenderContext) {
215        let size = context
216            .layout_node
217            .data
218            .as_ref()
219            .unwrap()
220            .downcast_ref::<Size2D>()
221            .unwrap();
222
223        let area = context.layout_node.visible_area();
224        let image = self.image_holder.image.borrow();
225
226        let mut rect = SkRect::new(
227            area.min_x(),
228            area.min_y(),
229            area.min_x() + size.width,
230            area.min_y() + size.height,
231        );
232        let clip_rect = SkRect::new(area.min_x(), area.min_y(), area.max_x(), area.max_y());
233
234        if self.image_data.image_cover == ImageCover::Center {
235            let width_offset = (size.width - area.width()) / 2.;
236            let height_offset = (size.height - area.height()) / 2.;
237
238            rect.left -= width_offset;
239            rect.right -= width_offset;
240            rect.top -= height_offset;
241            rect.bottom -= height_offset;
242        }
243
244        context.canvas.save();
245        context.canvas.clip_rect(clip_rect, ClipOp::Intersect, true);
246
247        let sampling = match self.image_data.sampling_mode {
248            SamplingMode::Nearest => SamplingOptions::new(FilterMode::Nearest, MipmapMode::None),
249            SamplingMode::Bilinear => SamplingOptions::new(FilterMode::Linear, MipmapMode::None),
250            SamplingMode::Trilinear => SamplingOptions::new(FilterMode::Linear, MipmapMode::Linear),
251            SamplingMode::Mitchell => SamplingOptions::from(CubicResampler::mitchell()),
252            SamplingMode::CatmullRom => SamplingOptions::from(CubicResampler::catmull_rom()),
253        };
254
255        let mut paint = Paint::default();
256        paint.set_anti_alias(true);
257
258        context
259            .canvas
260            .draw_image_rect_with_sampling_options(&*image, None, rect, sampling, &paint);
261
262        context.canvas.restore();
263    }
264}
265
266impl From<Image> for Element {
267    fn from(value: Image) -> Self {
268        Element::Element {
269            key: value.key,
270            element: Rc::new(value.element),
271            elements: value.elements,
272        }
273    }
274}
275
276impl KeyExt for Image {
277    fn write_key(&mut self) -> &mut DiffKey {
278        &mut self.key
279    }
280}
281
282impl EventHandlersExt for Image {
283    fn get_event_handlers(&mut self) -> &mut FxHashMap<EventName, EventHandlerType> {
284        &mut self.element.event_handlers
285    }
286}
287
288impl AccessibilityExt for Image {
289    fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
290        &mut self.element.accessibility
291    }
292}
293impl MaybeExt for Image {}
294
295impl LayoutExt for Image {
296    fn get_layout(&mut self) -> &mut LayoutData {
297        &mut self.element.layout
298    }
299}
300
301impl ContainerExt for Image {}
302impl ContainerWithContentExt for Image {}
303
304impl ImageExt for Image {
305    fn get_image_data(&mut self) -> &mut ImageData {
306        &mut self.element.image_data
307    }
308}
309
310impl ChildrenExt for Image {
311    fn get_children(&mut self) -> &mut Vec<Element> {
312        &mut self.elements
313    }
314}
315
316impl LayerExt for Image {
317    fn get_layer(&mut self) -> &mut i16 {
318        &mut self.element.relative_layer
319    }
320}
321
322pub struct Image {
323    key: DiffKey,
324    element: ImageElement,
325    elements: Vec<Element>,
326}
327
328/// [image] makes it possible to render a Skia image into the canvas.
329/// You most likely want to use a higher level than this, like the component `ImageViewer`.
330///
331/// See the available methods in [Image].
332pub fn image(image_holder: ImageHolder) -> Image {
333    let mut accessibility = AccessibilityData::default();
334    accessibility.builder.set_role(accesskit::Role::Image);
335    Image {
336        key: DiffKey::None,
337        element: ImageElement {
338            image_holder,
339            accessibility,
340            layout: LayoutData::default(),
341            event_handlers: HashMap::default(),
342            image_data: ImageData::default(),
343            relative_layer: 0,
344        },
345        elements: Vec::new(),
346    }
347}
348
349impl Image {
350    pub fn try_downcast(element: &dyn ElementExt) -> Option<ImageElement> {
351        (element as &dyn Any)
352            .downcast_ref::<ImageElement>()
353            .cloned()
354    }
355}