freya_components/
image_viewer.rs1use std::{
2 cell::RefCell,
3 fs,
4 hash::{
5 Hash,
6 Hasher,
7 },
8 path::PathBuf,
9 rc::Rc,
10};
11
12use anyhow::Context;
13use bytes::Bytes;
14use freya_core::{
15 elements::image::*,
16 prelude::*,
17};
18use freya_engine::prelude::{
19 SkData,
20 SkImage,
21};
22#[cfg(feature = "remote-asset")]
23use ureq::http::Uri;
24
25use crate::{
26 cache::*,
27 loader::CircularLoader,
28};
29
30#[derive(PartialEq, Clone)]
65pub enum ImageSource {
66 #[cfg(feature = "remote-asset")]
70 Uri(Uri),
71
72 Path(PathBuf),
73
74 Bytes(&'static str, Bytes),
75}
76
77impl From<(&'static str, Bytes)> for ImageSource {
78 fn from((id, bytes): (&'static str, Bytes)) -> Self {
79 Self::Bytes(id, bytes)
80 }
81}
82
83impl From<(&'static str, &'static [u8])> for ImageSource {
84 fn from((id, bytes): (&'static str, &'static [u8])) -> Self {
85 Self::Bytes(id, Bytes::from_static(bytes))
86 }
87}
88
89impl<const N: usize> From<(&'static str, &'static [u8; N])> for ImageSource {
90 fn from((id, bytes): (&'static str, &'static [u8; N])) -> Self {
91 Self::Bytes(id, Bytes::from_static(bytes))
92 }
93}
94
95#[cfg_attr(feature = "docs", doc(cfg(feature = "remote-asset")))]
96#[cfg(feature = "remote-asset")]
97impl From<Uri> for ImageSource {
98 fn from(uri: Uri) -> Self {
99 Self::Uri(uri)
100 }
101}
102
103#[cfg_attr(feature = "docs", doc(cfg(feature = "remote-asset")))]
104#[cfg(feature = "remote-asset")]
105impl From<&'static str> for ImageSource {
106 fn from(src: &'static str) -> Self {
107 Self::Uri(Uri::from_static(src))
108 }
109}
110
111impl From<PathBuf> for ImageSource {
112 fn from(path: PathBuf) -> Self {
113 Self::Path(path)
114 }
115}
116
117impl Hash for ImageSource {
118 fn hash<H: Hasher>(&self, state: &mut H) {
119 match self {
120 #[cfg(feature = "remote-asset")]
121 Self::Uri(uri) => uri.hash(state),
122 Self::Path(path) => path.hash(state),
123 Self::Bytes(id, _) => id.hash(state),
124 }
125 }
126}
127
128impl ImageSource {
129 pub async fn bytes(&self) -> anyhow::Result<(SkImage, Bytes)> {
130 let source = self.clone();
131 blocking::unblock(move || {
132 let bytes = match source {
133 #[cfg(feature = "remote-asset")]
134 Self::Uri(uri) => ureq::get(uri)
135 .call()?
136 .body_mut()
137 .read_to_vec()
138 .map(Bytes::from)?,
139 Self::Path(path) => fs::read(path).map(Bytes::from)?,
140 Self::Bytes(_, bytes) => bytes.clone(),
141 };
142 let image = SkImage::from_encoded(unsafe { SkData::new_bytes(&bytes) })
143 .context("Failed to decode Image.")?;
144 Ok((image, bytes))
145 })
146 .await
147 }
148}
149
150#[cfg_attr(feature = "docs",
174 doc = embed_doc_image::embed_image!("image_viewer", "images/gallery_image_viewer.png")
175)]
176#[derive(PartialEq)]
177pub struct ImageViewer {
178 source: ImageSource,
179
180 layout: LayoutData,
181 image_data: ImageData,
182 accessibility: AccessibilityData,
183
184 children: Vec<Element>,
185
186 key: DiffKey,
187}
188
189impl ImageViewer {
190 pub fn new(source: impl Into<ImageSource>) -> Self {
191 ImageViewer {
192 source: source.into(),
193 layout: LayoutData::default(),
194 image_data: ImageData::default(),
195 accessibility: AccessibilityData::default(),
196 children: Vec::new(),
197 key: DiffKey::None,
198 }
199 }
200}
201
202impl KeyExt for ImageViewer {
203 fn write_key(&mut self) -> &mut DiffKey {
204 &mut self.key
205 }
206}
207
208impl LayoutExt for ImageViewer {
209 fn get_layout(&mut self) -> &mut LayoutData {
210 &mut self.layout
211 }
212}
213
214impl ContainerWithContentExt for ImageViewer {}
215
216impl ImageExt for ImageViewer {
217 fn get_image_data(&mut self) -> &mut ImageData {
218 &mut self.image_data
219 }
220}
221
222impl AccessibilityExt for ImageViewer {
223 fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
224 &mut self.accessibility
225 }
226}
227
228impl ChildrenExt for ImageViewer {
229 fn get_children(&mut self) -> &mut Vec<Element> {
230 &mut self.children
231 }
232}
233
234impl Component for ImageViewer {
235 fn render(&self) -> impl IntoElement {
236 let asset_config = AssetConfiguration::new(&self.source, AssetAge::default());
237 let asset = use_asset(&asset_config);
238 let mut asset_cacher = use_hook(AssetCacher::get);
239 let mut assets_tasks = use_state::<Vec<TaskHandle>>(Vec::new);
240
241 use_side_effect_with_deps(&self.source, move |source| {
242 let source = source.clone();
243
244 for asset_task in assets_tasks.write().drain(..) {
246 asset_task.cancel();
247 }
248
249 if matches!(
251 asset_cacher.read_asset(&asset_config),
252 Some(Asset::Pending) | Some(Asset::Error(_))
253 ) {
254 asset_cacher.update_asset(asset_config.clone(), Asset::Loading);
256
257 let asset_config = asset_config.clone();
258 let asset_task = spawn(async move {
259 match source.bytes().await {
260 Ok((image, bytes)) => {
261 let image_holder = ImageHolder {
263 bytes,
264 image: Rc::new(RefCell::new(image)),
265 };
266 asset_cacher.update_asset(
267 asset_config.clone(),
268 Asset::Cached(Rc::new(image_holder)),
269 );
270 }
271 Err(err) => {
272 asset_cacher.update_asset(asset_config, Asset::Error(err.to_string()));
274 }
275 }
276 });
277
278 assets_tasks.write().push(asset_task);
279 }
280 });
281
282 match asset {
283 Asset::Cached(asset) => {
284 let asset = asset.downcast_ref::<ImageHolder>().unwrap().clone();
285 image(asset)
286 .accessibility(self.accessibility.clone())
287 .a11y_role(AccessibilityRole::Image)
288 .a11y_focusable(true)
289 .layout(self.layout.clone())
290 .image_data(self.image_data.clone())
291 .children(self.children.clone())
292 .into_element()
293 }
294 Asset::Pending | Asset::Loading => rect()
295 .layout(self.layout.clone())
296 .center()
297 .child(CircularLoader::new())
298 .into(),
299 Asset::Error(err) => err.into(),
300 }
301 }
302
303 fn render_key(&self) -> DiffKey {
304 self.key.clone().or(self.default_key())
305 }
306}