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
16pub enum ObjectFit {
19 Fill,
23 Contain,
27 Cover,
30 ScaleDown,
32 None,
34}
35
36pub 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}