1use std::{
2 cell::RefCell,
3 collections::hash_map::DefaultHasher,
4 fs,
5 hash::{
6 Hash,
7 Hasher,
8 },
9 path::PathBuf,
10 rc::Rc,
11};
12
13use anyhow::Context;
14use bytes::Bytes;
15use freya_core::{
16 elements::image::*,
17 prelude::*,
18};
19use freya_engine::prelude::{
20 SkData,
21 SkImage,
22};
23#[cfg(feature = "remote-asset")]
24use ureq::http::Uri;
25
26use crate::{
27 cache::*,
28 loader::CircularLoader,
29};
30
31#[derive(PartialEq, Clone)]
82pub enum ImageSource {
83 #[cfg(feature = "remote-asset")]
87 Uri(Uri),
88
89 Path(PathBuf),
90
91 Bytes(u64, Bytes),
92}
93
94impl<H: Hash> From<(H, Bytes)> for ImageSource {
95 fn from((id, bytes): (H, Bytes)) -> Self {
96 let mut hasher = DefaultHasher::default();
97 id.hash(&mut hasher);
98 Self::Bytes(hasher.finish(), bytes)
99 }
100}
101
102impl<H: Hash> From<(H, &'static [u8])> for ImageSource {
103 fn from((id, bytes): (H, &'static [u8])) -> Self {
104 let mut hasher = DefaultHasher::default();
105 id.hash(&mut hasher);
106 Self::Bytes(hasher.finish(), Bytes::from_static(bytes))
107 }
108}
109
110impl<const N: usize, H: Hash> From<(H, &'static [u8; N])> for ImageSource {
111 fn from((id, bytes): (H, &'static [u8; N])) -> Self {
112 let mut hasher = DefaultHasher::default();
113 id.hash(&mut hasher);
114 Self::Bytes(hasher.finish(), Bytes::from_static(bytes))
115 }
116}
117
118#[cfg_attr(feature = "docs", doc(cfg(feature = "remote-asset")))]
119#[cfg(feature = "remote-asset")]
120impl From<Uri> for ImageSource {
121 fn from(uri: Uri) -> Self {
122 Self::Uri(uri)
123 }
124}
125
126#[cfg_attr(feature = "docs", doc(cfg(feature = "remote-asset")))]
127#[cfg(feature = "remote-asset")]
128impl From<&'static str> for ImageSource {
129 fn from(src: &'static str) -> Self {
130 Self::Uri(Uri::from_static(src))
131 }
132}
133
134impl From<PathBuf> for ImageSource {
135 fn from(path: PathBuf) -> Self {
136 Self::Path(path)
137 }
138}
139
140impl Hash for ImageSource {
141 fn hash<H: Hasher>(&self, state: &mut H) {
142 match self {
143 #[cfg(feature = "remote-asset")]
144 Self::Uri(uri) => uri.hash(state),
145 Self::Path(path) => path.hash(state),
146 Self::Bytes(id, _) => id.hash(state),
147 }
148 }
149}
150
151impl ImageSource {
152 pub async fn bytes(&self) -> anyhow::Result<(SkImage, Bytes)> {
153 let source = self.clone();
154 blocking::unblock(move || {
155 let bytes = match source {
156 #[cfg(feature = "remote-asset")]
157 Self::Uri(uri) => ureq::get(uri)
158 .call()?
159 .body_mut()
160 .read_to_vec()
161 .map(Bytes::from)?,
162 Self::Path(path) => fs::read(path).map(Bytes::from)?,
163 Self::Bytes(_, bytes) => bytes,
164 };
165 let image = SkImage::from_encoded(unsafe { SkData::new_bytes(&bytes) })
166 .context("Failed to decode Image.")?;
167 let image = image.make_raster_image(None, None).unwrap_or(image);
168 Ok((image, bytes))
169 })
170 .await
171 }
172}
173
174#[cfg_attr(feature = "docs",
203 doc = embed_doc_image::embed_image!("image_viewer", "images/gallery_image_viewer.png")
204)]
205#[derive(PartialEq)]
206pub struct ImageViewer {
207 source: ImageSource,
208 asset_age: AssetAge,
209
210 layout: LayoutData,
211 image_data: ImageData,
212 accessibility: AccessibilityData,
213 effect: EffectData,
214 corner_radius: Option<CornerRadius>,
215
216 children: Vec<Element>,
217 loading_placeholder: Option<Element>,
218 error_renderer: Option<Callback<String, Element>>,
219
220 key: DiffKey,
221}
222
223impl ImageViewer {
224 pub fn new(source: impl Into<ImageSource>) -> Self {
225 ImageViewer {
226 source: source.into(),
227 asset_age: AssetAge::default(),
228 layout: LayoutData::default(),
229 image_data: ImageData::default(),
230 accessibility: AccessibilityData::default(),
231 effect: EffectData::default(),
232 corner_radius: None,
233 children: Vec::new(),
234 loading_placeholder: None,
235 error_renderer: None,
236 key: DiffKey::None,
237 }
238 }
239}
240
241impl KeyExt for ImageViewer {
242 fn write_key(&mut self) -> &mut DiffKey {
243 &mut self.key
244 }
245}
246
247impl LayoutExt for ImageViewer {
248 fn get_layout(&mut self) -> &mut LayoutData {
249 &mut self.layout
250 }
251}
252
253impl ContainerSizeExt for ImageViewer {}
254impl ContainerWithContentExt for ImageViewer {}
255
256impl ImageExt for ImageViewer {
257 fn get_image_data(&mut self) -> &mut ImageData {
258 &mut self.image_data
259 }
260}
261
262impl AccessibilityExt for ImageViewer {
263 fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
264 &mut self.accessibility
265 }
266}
267
268impl ChildrenExt for ImageViewer {
269 fn get_children(&mut self) -> &mut Vec<Element> {
270 &mut self.children
271 }
272}
273
274impl EffectExt for ImageViewer {
275 fn get_effect(&mut self) -> &mut EffectData {
276 &mut self.effect
277 }
278}
279
280impl ImageViewer {
281 pub fn corner_radius(mut self, corner_radius: impl Into<CornerRadius>) -> Self {
282 self.corner_radius = Some(corner_radius.into());
283 self
284 }
285
286 pub fn loading_placeholder(mut self, placeholder: impl Into<Element>) -> Self {
288 self.loading_placeholder = Some(placeholder.into());
289 self
290 }
291
292 pub fn asset_age(mut self, asset_age: impl Into<AssetAge>) -> Self {
296 self.asset_age = asset_age.into();
297 self
298 }
299
300 pub fn error_renderer(mut self, renderer: impl Into<Callback<String, Element>>) -> Self {
302 self.error_renderer = Some(renderer.into());
303 self
304 }
305}
306
307impl Component for ImageViewer {
308 fn render(&self) -> impl IntoElement {
309 let asset_config = AssetConfiguration::new(&self.source, self.asset_age.clone());
310 let asset = use_asset(&asset_config);
311 let mut asset_cacher = use_hook(AssetCacher::get);
312
313 use_side_effect_with_deps(
314 &(self.source.clone(), asset_config),
315 move |(source, asset_config): &(ImageSource, AssetConfiguration)| {
316 if matches!(
319 asset_cacher.read_asset(asset_config),
320 Some(Asset::Pending) | Some(Asset::Error(_))
321 ) {
322 asset_cacher.update_asset(asset_config.clone(), Asset::Loading);
323
324 let source = source.clone();
325 let asset_config = asset_config.clone();
326 spawn_forever(async move {
327 match source.bytes().await {
328 Ok((image, bytes)) => {
329 let image_holder = ImageHolder {
331 bytes,
332 image: Rc::new(RefCell::new(image)),
333 };
334 asset_cacher.update_asset(
335 asset_config,
336 Asset::Cached(Rc::new(image_holder)),
337 );
338 }
339 Err(err) => {
340 asset_cacher
342 .update_asset(asset_config, Asset::Error(err.to_string()));
343 }
344 }
345 });
346 }
347 },
348 );
349
350 match asset {
351 Asset::Cached(asset) => {
352 let asset = asset.downcast_ref::<ImageHolder>().unwrap().clone();
353 image(asset)
354 .accessibility(self.accessibility.clone())
355 .a11y_role(AccessibilityRole::Image)
356 .layout(self.layout.clone())
357 .image_data(self.image_data.clone())
358 .effect(self.effect.clone())
359 .children(self.children.clone())
360 .map(self.corner_radius, |img, corner_radius| {
361 img.corner_radius(corner_radius)
362 })
363 .into_element()
364 }
365 Asset::Pending | Asset::Loading => rect()
366 .layout(self.layout.clone())
367 .center()
368 .child(
369 self.loading_placeholder
370 .clone()
371 .unwrap_or_else(|| CircularLoader::new().into_element()),
372 )
373 .into(),
374 Asset::Error(err) => match &self.error_renderer {
375 Some(renderer) => renderer.call(err),
376 None => err.into(),
377 },
378 }
379 }
380
381 fn render_key(&self) -> DiffKey {
382 self.key.clone().or(self.default_key())
383 }
384}