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