Skip to main content

floem/views/
img.rs

1use std::sync::Arc;
2
3use floem_reactive::create_effect;
4use peniko::Blob;
5use sha2::{Digest, Sha256};
6
7use crate::{id::ViewId, style::Style, unit::UnitExt, view::View, Renderer};
8
9use taffy::tree::NodeId;
10
11pub struct ImageStyle {
12    fit: ObjectFit,
13    position: ObjectPosition,
14}
15
16/// How the content of a replaced element, such as an img or video, should be resized to fit its container.
17/// See <https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit>.
18pub enum ObjectFit {
19    /// The replaced content is sized to fill the element's content box.
20    /// The entire object will completely fill the box.
21    /// If the object's aspect ratio does not match the aspect ratio of its box, then the object will be stretched to fit.
22    Fill,
23    /// The replaced content is scaled to maintain its aspect ratio while fitting within the element's content box.
24    /// The entire object is made to fill the box, while preserving its aspect ratio, so the object will be "letterboxed"
25    /// if its aspect ratio does not match the aspect ratio of the box.
26    Contain,
27    /// The content is sized to maintain its aspect ratio while filling the element's entire content box.
28    /// If the object's aspect ratio does not match the aspect ratio of its box, then the object will be clipped to fit.
29    Cover,
30    /// The content is sized as if none or contain were specified, whichever would result in a smaller concrete object size.
31    ScaleDown,
32    /// The replaced content is not resized.
33    None,
34}
35
36/// Specifies the alignment of the element's contents within the element's box.
37///
38/// Areas of the box which aren't covered by the replaced element's object will show the element's background.
39/// See <https://developer.mozilla.org/en-US/docs/Web/CSS/object-position>.
40pub struct ObjectPosition {
41    #[allow(unused)]
42    horiz: HorizPosition,
43    #[allow(unused)]
44    vert: VertPosition,
45}
46
47pub enum HorizPosition {
48    Top,
49    Center,
50    Bot,
51    Px(f64),
52    Pct(f64),
53}
54
55pub enum VertPosition {
56    Left,
57    Center,
58    Right,
59    Px(f64),
60    Pct(f64),
61}
62
63impl ImageStyle {
64    pub const BASE: Self = ImageStyle {
65        position: ObjectPosition {
66            horiz: HorizPosition::Center,
67            vert: VertPosition::Center,
68        },
69        fit: ObjectFit::Fill,
70    };
71
72    pub fn fit(mut self, fit: ObjectFit) -> Self {
73        self.fit = fit;
74        self
75    }
76
77    pub fn object_pos(mut self, obj_pos: ObjectPosition) -> Self {
78        self.position = obj_pos;
79        self
80    }
81}
82
83pub struct Img {
84    id: ViewId,
85    img: Option<peniko::Image>,
86    img_hash: Option<Vec<u8>>,
87    content_node: Option<NodeId>,
88}
89
90pub fn img(image: impl Fn() -> Vec<u8> + 'static) -> Img {
91    let image = image::load_from_memory(&image()).ok();
92    let width = image.as_ref().map_or(0, |img| img.width());
93    let height = image.as_ref().map_or(0, |img| img.height());
94    let data = Arc::new(image.map_or(Default::default(), |img| img.into_rgba8().into_vec()));
95    let blob = Blob::new(data);
96    let image = peniko::Image::new(blob, peniko::Format::Rgba8, width, height);
97    img_dynamic(move || image.clone())
98}
99
100pub(crate) fn img_dynamic(image: impl Fn() -> peniko::Image + 'static) -> Img {
101    let id = ViewId::new();
102    create_effect(move |_| {
103        id.update_state(image());
104    });
105    Img {
106        id,
107        img: None,
108        img_hash: None,
109        content_node: None,
110    }
111}
112
113impl View for Img {
114    fn id(&self) -> ViewId {
115        self.id
116    }
117
118    fn debug_name(&self) -> std::borrow::Cow<'static, str> {
119        "Img".into()
120    }
121
122    fn update(&mut self, _cx: &mut crate::context::UpdateCx, state: Box<dyn std::any::Any>) {
123        if let Ok(img) = state.downcast::<peniko::Image>() {
124            let mut hasher = Sha256::new();
125            hasher.update(img.data.data());
126            self.img_hash = Some(hasher.finalize().to_vec());
127
128            self.img = Some(*img);
129            self.id.request_layout();
130        }
131    }
132
133    fn layout(&mut self, cx: &mut crate::context::LayoutCx) -> taffy::tree::NodeId {
134        cx.layout_node(self.id(), true, |_cx| {
135            if self.content_node.is_none() {
136                self.content_node = Some(
137                    self.id
138                        .taffy()
139                        .borrow_mut()
140                        .new_leaf(taffy::style::Style::DEFAULT)
141                        .unwrap(),
142                );
143            }
144            let content_node = self.content_node.unwrap();
145
146            let (width, height) = self
147                .img
148                .as_ref()
149                .map(|img| (img.width, img.height))
150                .unwrap_or((0, 0));
151
152            let style = Style::new()
153                .width((width as f64).px())
154                .height((height as f64).px())
155                .to_taffy_style();
156            let _ = self.id.taffy().borrow_mut().set_style(content_node, style);
157
158            vec![content_node]
159        })
160    }
161
162    fn paint(&mut self, cx: &mut crate::context::PaintCx) {
163        if let Some(ref img) = self.img {
164            let rect = self.id.get_content_rect();
165            cx.draw_img(
166                floem_renderer::Img {
167                    img: img.clone(),
168                    hash: self.img_hash.as_ref().unwrap(),
169                },
170                rect,
171            );
172        }
173    }
174}