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",
169 doc = embed_doc_image::embed_image!("image_viewer", "images/gallery_image_viewer.png")
170)]
171#[derive(PartialEq)]
172pub struct ImageViewer {
173 source: ImageSource,
174
175 layout: LayoutData,
176 image_data: ImageData,
177 accessibility: AccessibilityData,
178
179 children: Vec<Element>,
180
181 key: DiffKey,
182}
183
184impl ImageViewer {
185 pub fn new(source: impl Into<ImageSource>) -> Self {
186 ImageViewer {
187 source: source.into(),
188 layout: LayoutData::default(),
189 image_data: ImageData::default(),
190 accessibility: AccessibilityData::default(),
191 children: Vec::new(),
192 key: DiffKey::None,
193 }
194 }
195}
196
197impl KeyExt for ImageViewer {
198 fn write_key(&mut self) -> &mut DiffKey {
199 &mut self.key
200 }
201}
202
203impl LayoutExt for ImageViewer {
204 fn get_layout(&mut self) -> &mut LayoutData {
205 &mut self.layout
206 }
207}
208
209impl ContainerWithContentExt for ImageViewer {}
210
211impl ImageExt for ImageViewer {
212 fn get_image_data(&mut self) -> &mut ImageData {
213 &mut self.image_data
214 }
215}
216
217impl AccessibilityExt for ImageViewer {
218 fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
219 &mut self.accessibility
220 }
221}
222
223impl ChildrenExt for ImageViewer {
224 fn get_children(&mut self) -> &mut Vec<Element> {
225 &mut self.children
226 }
227}
228
229impl Render for ImageViewer {
230 fn render(&self) -> impl IntoElement {
231 let asset_config = AssetConfiguration::new(&self.source, AssetAge::default());
232 let asset = use_asset(&asset_config);
233 let mut asset_cacher = use_hook(AssetCacher::get);
234 let mut assets_tasks = use_state::<Vec<TaskHandle>>(Vec::new);
235
236 use_side_effect_with_deps(&self.source, move |source| {
237 let source = source.clone();
238
239 for asset_task in assets_tasks.write().drain(..) {
241 asset_task.cancel();
242 }
243
244 if matches!(
246 asset_cacher.read_asset(&asset_config),
247 Some(Asset::Pending) | Some(Asset::Error(_))
248 ) {
249 asset_cacher.update_asset(asset_config.clone(), Asset::Loading);
251
252 let asset_config = asset_config.clone();
253 let asset_task = spawn(async move {
254 match source.bytes().await {
255 Ok((image, bytes)) => {
256 let image_holder = ImageHolder {
258 bytes,
259 image: Rc::new(RefCell::new(image)),
260 };
261 asset_cacher.update_asset(
262 asset_config.clone(),
263 Asset::Cached(Rc::new(image_holder)),
264 );
265 }
266 Err(err) => {
267 asset_cacher.update_asset(asset_config, Asset::Error(err.to_string()));
269 }
270 }
271 });
272
273 assets_tasks.write().push(asset_task);
274 }
275 });
276
277 match asset {
278 Asset::Cached(asset) => {
279 let asset = asset.downcast_ref::<ImageHolder>().unwrap().clone();
280 image(asset)
281 .accessibility(self.accessibility.clone())
282 .a11y_role(AccessibilityRole::Image)
283 .a11y_focusable(true)
284 .layout(self.layout.clone())
285 .image_data(self.image_data.clone())
286 .children(self.children.clone())
287 .into_element()
288 }
289 Asset::Pending | Asset::Loading => rect()
290 .layout(self.layout.clone())
291 .center()
292 .child(CircularLoader::new())
293 .into(),
294 Asset::Error(err) => err.into(),
295 }
296 }
297
298 fn render_key(&self) -> DiffKey {
299 self.key.clone().or(self.default_key())
300 }
301}