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