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