use crate::{ px, swap_rgba_pa_to_bgra, AbsoluteLength, AnyElement, App, Asset, AssetLogger, Bounds, DefiniteLength, Element, ElementId, GlobalElementId, Hitbox, Image, InteractiveElement, Interactivity, IntoElement, LayoutId, Length, ObjectFit, Pixels, RenderImage, Resource, SharedString, SharedUri, StyleRefinement, Styled, SvgSize, Task, Window, SMOOTH_SVG_SCALE_FACTOR,};use anyhow::{anyhow, Result}; use futures::{AsyncReadExt, Future};use image::{ codecs::{gif::GifDecoder, webp::WebPDecoder}, AnimationDecoder, DynamicImage, Frame, ImageBuffer, ImageError, ImageFormat, Rgba,};use smallvec::SmallVec;use std::{ fs, io::{self, Cursor}, ops::{Deref, DerefMut}, path::{Path, PathBuf}, str::FromStr, sync::Arc, time::{Duration, Instant},};use thiserror::Error;use util::ResultExt; use super::{FocusableElement, Stateful, StatefulInteractiveElement}; /// The delay before showing the loading state.pub const LOADING_DELAY: Duration = Duration::from_millis(200); /// A type alias to the resource loader that the `img()` element uses.////// Note: that this is only for Resources, like URLs or file paths./// Custom loaders, or external images will not use this asset loaderpub type ImgResourceLoader = AssetLogger<ImageAssetLoader>; /// A source of image content.#[derive(Clone)]pub enum ImageSource { /// The image content will be loaded from some resource location Resource(Resource), /// Cached image data Render(Arc<RenderImage>), /// Cached image data Image(Arc<Image>), /// A custom loading function to use Custom(Arc<dyn Fn(&mut Window, &mut App) -> Option<Result<Arc<RenderImage>, ImageCacheError>>>),} fn is_uri(uri: &str) -> bool { http_client::Uri::from_str(uri).is_ok()} impl From<SharedUri> for ImageSource { fn from(value: SharedUri) -> Self { Self::Resource(Resource::Uri(value)) }} impl<'a> From<&'a str> for ImageSource { fn from(s: &'a str) -> Self { if is_uri(s) { Self::Resource(Resource::Uri(s.to_string().into())) } else { Self::Resource(Resource::Embedded(s.to_string().into())) } }} impl From<String> for ImageSource { fn from(s: String) -> Self { if is_uri(&s) { Self::Resource(Resource::Uri(s.into())) } else { Self::Resource(Resource::Embedded(s.into())) } }} impl From<SharedString> for ImageSource { fn from(s: SharedString) -> Self { s.as_ref().into() }} impl From<&Path> for ImageSource { fn from(value: &Path) -> Self { Self::Resource(value.to_path_buf().into()) }} impl From<Arc<Path>> for ImageSource { fn from(value: Arc<Path>) -> Self { Self::Resource(value.into()) }} impl From<PathBuf> for ImageSource { fn from(value: PathBuf) -> Self { Self::Resource(value.into()) }} impl From<Arc<RenderImage>> for ImageSource { fn from(value: Arc<RenderImage>) -> Self { Self::Render(value) }} impl From<Arc<Image>> for ImageSource { fn from(value: Arc<Image>) -> Self { Self::Image(value) }} impl<F> From<F> for ImageSourcewhere F: Fn(&mut Window, &mut App) -> Option<Result<Arc<RenderImage>, ImageCacheError>> + 'static,{ fn from(value: F) -> Self { Self::Custom(Arc::new(value)) }} /// The style of an image element.pub struct ImageStyle { grayscale: bool, object_fit: ObjectFit, loading: Option<Box<dyn Fn() -> AnyElement>>, fallback: Option<Box<dyn Fn() -> AnyElement>>,} impl Default for ImageStyle { fn default() -> Self { Self { grayscale: false, object_fit: ObjectFit::Contain, loading: None, fallback: None, } }} /// Style an image element.pub trait StyledImage: Sized { /// Get a mutable [ImageStyle] from the element. fn image_style(&mut self) -> &mut ImageStyle; /// Set the image to be displayed in grayscale. fn grayscale(mut self, grayscale: bool) -> Self { self.image_style().grayscale = grayscale; self } /// Set the object fit for the image. fn object_fit(mut self, object_fit: ObjectFit) -> Self { self.image_style().object_fit = object_fit; self } /// Set the object fit for the image. fn with_fallback(mut self, fallback: impl Fn() -> AnyElement + 'static) -> Self { self.image_style().fallback = Some(Box::new(fallback)); self } /// Set the object fit for the image. fn with_loading(mut self, loading: impl Fn() -> AnyElement + 'static) -> Self { self.image_style().loading = Some(Box::new(loading)); self }} impl StyledImage for Img { fn image_style(&mut self) -> &mut ImageStyle { &mut self.style }} impl StyledImage for Stateful<Img> { fn image_style(&mut self) -> &mut ImageStyle { &mut self.element.style }} /// An image element.pub struct Img { interactivity: Interactivity, source: ImageSource, style: ImageStyle,} /// Create a new image element.pub fn img(source: impl Into<ImageSource>) -> Img { Img { interactivity: Interactivity::default(), source: source.into(), style: ImageStyle::default(), }} impl Img { /// A list of all format extensions currently supported by this img element pub fn extensions() -> &'static [&'static str] { // This is the list in [image::ImageFormat::from_extension] + `svg` &[ "avif", "jpg", "jpeg", "png", "gif", "webp", "tif", "tiff", "tga", "dds", "bmp", "ico", "hdr", "exr", "pbm", "pam", "ppm", "pgm", "ff", "farbfeld", "qoi", "svg", ] }} impl Deref for Stateful<Img> { type Target = Img; fn deref(&self) -> &Self::Target { &self.element }} impl DerefMut for Stateful<Img> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.element }} /// The image state between framesstruct ImgState { frame_index: usize, last_frame_time: Option<Instant>, started_loading: Option<(Instant, Task<()>)>,} /// The image layout state between framespub struct ImgLayoutState { frame_index: usize, replacement: Option<AnyElement>,} impl Element for Img { type RequestLayoutState = ImgLayoutState; type PrepaintState = Option<Hitbox>; fn id(&self) -> Option<ElementId> { self.interactivity.element_id.clone() } fn request_layout( &mut self, global_id: Option<&GlobalElementId>, window: &mut Window, cx: &mut App, ) -> (LayoutId, Self::RequestLayoutState) { let mut layout_state = ImgLayoutState { frame_index: 0, replacement: None, }; window.with_optional_element_state(global_id, |state, window| { let mut state = state.map(|state| { state.unwrap_or(ImgState { frame_index: 0, last_frame_time: None, started_loading: None, }) }); let frame_index = state.as_ref().map(|state| state.frame_index).unwrap_or(0); let layout_id = self.interactivity.request_layout( global_id, window, cx, |mut style, window, cx| { let mut replacement_id = None; match self.source.use_data(window, cx) { Some(Ok(data)) => { if let Some(state) = &mut state { let frame_count = data.frame_count(); if frame_count > 1 { let current_time = Instant::now(); if let Some(last_frame_time) = state.last_frame_time { let elapsed = current_time - last_frame_time; let frame_duration = Duration::from(data.delay(state.frame_index)); if elapsed >= frame_duration { state.frame_index = (state.frame_index + 1) % frame_count; state.last_frame_time = Some(current_time - (elapsed - frame_duration)); } } else { state.last_frame_time = Some(current_time); } } state.started_loading = None; } let image_size = data.size(frame_index); if let Length::Auto = style.size.width { style.size.width = match style.size.height { Length::Definite(DefiniteLength::Absolute( AbsoluteLength::Pixels(height), )) => Length::Definite( px(image_size.width.0 as f32 * height.0 / image_size.height.0 as f32) .into(), ), _ => Length::Definite(px(image_size.width.0 as f32).into()), }; } if let Length::Auto = style.size.height { style.size.height = match style.size.width { Length::Definite(DefiniteLength::Absolute( AbsoluteLength::Pixels(width), )) => Length::Definite( px(image_size.height.0 as f32 * width.0 / image_size.width.0 as f32) .into(), ), _ => Length::Definite(px(image_size.height.0 as f32).into()), }; } if global_id.is_some() && data.frame_count() > 1 { window.request_animation_frame(); } } Some(_err) => { if let Some(fallback) = self.style.fallback.as_ref() { let mut element = fallback(); replacement_id = Some(element.request_layout(window, cx)); layout_state.replacement = Some(element); } if let Some(state) = &mut state { state.started_loading = None; } } None => { if let Some(state) = &mut state { if let Some((started_loading, _)) = state.started_loading { if started_loading.elapsed() > LOADING_DELAY { if let Some(loading) = self.style.loading.as_ref() { let mut element = loading(); replacement_id = Some(element.request_layout(window, cx)); layout_state.replacement = Some(element); } } } else { let current_view = window.current_view(); let task = window.spawn(cx, |mut cx| async move { cx.background_executor().timer(LOADING_DELAY).await; cx.update(move |_, cx| { cx.notify(current_view); }) .ok(); }); state.started_loading = Some((Instant::now(), task)); } } } } window.request_layout(style, replacement_id, cx) }, ); layout_state.frame_index = frame_index; ((layout_id, layout_state), state) }) } fn prepaint( &mut self, global_id: Option<&GlobalElementId>, bounds: Bounds<Pixels>, request_layout: &mut Self::RequestLayoutState, window: &mut Window, cx: &mut App, ) -> Self::PrepaintState { self.interactivity.prepaint( global_id, bounds, bounds.size, window, cx, |_, _, hitbox, window, cx| { if let Some(replacement) = &mut request_layout.replacement { replacement.prepaint(window, cx); } hitbox }, ) } fn paint( &mut self, global_id: Option<&GlobalElementId>, bounds: Bounds<Pixels>, layout_state: &mut Self::RequestLayoutState, hitbox: &mut Self::PrepaintState, window: &mut Window, cx: &mut App, ) { let source = self.source.clone(); self.interactivity.paint( global_id, bounds, hitbox.as_ref(), window, cx, |style, window, cx| { let corner_radii = style.corner_radii.to_pixels(bounds.size, window.rem_size()); if let Some(Ok(data)) = source.use_data(window, cx) { let new_bounds = self .style .object_fit .get_bounds(bounds, data.size(layout_state.frame_index)); window .paint_image( new_bounds, corner_radii, data.clone(), layout_state.frame_index, self.style.grayscale, ) .log_err(); } else if let Some(replacement) = &mut layout_state.replacement { replacement.paint(window, cx); } }, ) }} impl Styled for Img { fn style(&mut self) -> &mut StyleRefinement { &mut self.interactivity.base_style }} impl InteractiveElement for Img { fn interactivity(&mut self) -> &mut Interactivity { &mut self.interactivity }} impl IntoElement for Img { type Element = Self; fn into_element(self) -> Self::Element { self }} impl FocusableElement for Img {} impl StatefulInteractiveElement for Img {} impl ImageSource { pub(crate) fn use_data( &self, window: &mut Window, cx: &mut App, ) -> Option<Result<Arc<RenderImage>, ImageCacheError>> { match self { ImageSource::Resource(resource) => window.use_asset::<ImgResourceLoader>(&resource, cx), ImageSource::Custom(loading_fn) => loading_fn(window, cx), ImageSource::Render(data) => Some(Ok(data.to_owned())), ImageSource::Image(data) => window.use_asset::<AssetLogger<ImageDecoder>>(data, cx), } }} #[derive(Clone)]enum ImageDecoder {} impl Asset for ImageDecoder { type Source = Arc<Image>; type Output = Result<Arc<RenderImage>, ImageCacheError>; fn load( source: Self::Source, cx: &mut App, ) -> impl Future<Output = Self::Output> + Send + 'static { let renderer = cx.svg_renderer(); async move { source.to_image_data(renderer).map_err(Into::into) } }} Lines 1–500 of 683. Load more
use crate::{ px, swap_rgba_pa_to_bgra, AbsoluteLength, AnyElement, App, Asset, AssetLogger, Bounds, DefiniteLength, Element, ElementId, GlobalElementId, Hitbox, Image, InteractiveElement, Interactivity, IntoElement, LayoutId, Length, ObjectFit, Pixels, RenderImage, Resource, SharedString, SharedUri, StyleRefinement, Styled, SvgSize, Task, Window, SMOOTH_SVG_SCALE_FACTOR, }; use anyhow::{anyhow, Result}; use futures::{AsyncReadExt, Future}; use image::{ codecs::{gif::GifDecoder, webp::WebPDecoder}, AnimationDecoder, DynamicImage, Frame, ImageBuffer, ImageError, ImageFormat, Rgba, }; use smallvec::SmallVec; use std::{ fs, io::{self, Cursor}, ops::{Deref, DerefMut}, path::{Path, PathBuf}, str::FromStr, sync::Arc, time::{Duration, Instant}, }; use thiserror::Error; use util::ResultExt; use super::{FocusableElement, Stateful, StatefulInteractiveElement}; /// The delay before showing the loading state. pub const LOADING_DELAY: Duration = Duration::from_millis(200); /// A type alias to the resource loader that the `img()` element uses. /// /// Note: that this is only for Resources, like URLs or file paths. /// Custom loaders, or external images will not use this asset loader pub type ImgResourceLoader = AssetLogger<ImageAssetLoader>; /// A source of image content. #[derive(Clone)] pub enum ImageSource { /// The image content will be loaded from some resource location Resource(Resource), /// Cached image data Render(Arc<RenderImage>), /// Cached image data Image(Arc<Image>), /// A custom loading function to use Custom(Arc<dyn Fn(&mut Window, &mut App) -> Option<Result<Arc<RenderImage>, ImageCacheError>>>), } fn is_uri(uri: &str) -> bool { http_client::Uri::from_str(uri).is_ok() } impl From<SharedUri> for ImageSource { fn from(value: SharedUri) -> Self { Self::Resource(Resource::Uri(value)) } } impl<'a> From<&'a str> for ImageSource { fn from(s: &'a str) -> Self { if is_uri(s) { Self::Resource(Resource::Uri(s.to_string().into())) } else { Self::Resource(Resource::Embedded(s.to_string().into())) } } } impl From<String> for ImageSource { fn from(s: String) -> Self { if is_uri(&s) { Self::Resource(Resource::Uri(s.into())) } else { Self::Resource(Resource::Embedded(s.into())) } } } impl From<SharedString> for ImageSource { fn from(s: SharedString) -> Self { s.as_ref().into() } } impl From<&Path> for ImageSource { fn from(value: &Path) -> Self { Self::Resource(value.to_path_buf().into()) } } impl From<Arc<Path>> for ImageSource { fn from(value: Arc<Path>) -> Self { Self::Resource(value.into()) } } impl From<PathBuf> for ImageSource { fn from(value: PathBuf) -> Self { Self::Resource(value.into()) } } impl From<Arc<RenderImage>> for ImageSource { fn from(value: Arc<RenderImage>) -> Self { Self::Render(value) } } impl From<Arc<Image>> for ImageSource { fn from(value: Arc<Image>) -> Self { Self::Image(value) } } impl<F> From<F> for ImageSource where F: Fn(&mut Window, &mut App) -> Option<Result<Arc<RenderImage>, ImageCacheError>> + 'static, { fn from(value: F) -> Self { Self::Custom(Arc::new(value)) } } /// The style of an image element. pub struct ImageStyle { grayscale: bool, object_fit: ObjectFit, loading: Option<Box<dyn Fn() -> AnyElement>>, fallback: Option<Box<dyn Fn() -> AnyElement>>, } impl Default for ImageStyle { fn default() -> Self { Self { grayscale: false, object_fit: ObjectFit::Contain, loading: None, fallback: None, } } } /// Style an image element. pub trait StyledImage: Sized { /// Get a mutable [ImageStyle] from the element. fn image_style(&mut self) -> &mut ImageStyle; /// Set the image to be displayed in grayscale. fn grayscale(mut self, grayscale: bool) -> Self { self.image_style().grayscale = grayscale; self } /// Set the object fit for the image. fn object_fit(mut self, object_fit: ObjectFit) -> Self { self.image_style().object_fit = object_fit; self } /// Set the object fit for the image. fn with_fallback(mut self, fallback: impl Fn() -> AnyElement + 'static) -> Self { self.image_style().fallback = Some(Box::new(fallback)); self } /// Set the object fit for the image. fn with_loading(mut self, loading: impl Fn() -> AnyElement + 'static) -> Self { self.image_style().loading = Some(Box::new(loading)); self } } impl StyledImage for Img { fn image_style(&mut self) -> &mut ImageStyle { &mut self.style } } impl StyledImage for Stateful<Img> { fn image_style(&mut self) -> &mut ImageStyle { &mut self.element.style } } /// An image element. pub struct Img { interactivity: Interactivity, source: ImageSource, style: ImageStyle, } /// Create a new image element. pub fn img(source: impl Into<ImageSource>) -> Img { Img { interactivity: Interactivity::default(), source: source.into(), style: ImageStyle::default(), } } impl Img { /// A list of all format extensions currently supported by this img element pub fn extensions() -> &'static [&'static str] { // This is the list in [image::ImageFormat::from_extension] + `svg` &[ "avif", "jpg", "jpeg", "png", "gif", "webp", "tif", "tiff", "tga", "dds", "bmp", "ico", "hdr", "exr", "pbm", "pam", "ppm", "pgm", "ff", "farbfeld", "qoi", "svg", ] } } impl Deref for Stateful<Img> { type Target = Img; fn deref(&self) -> &Self::Target { &self.element } } impl DerefMut for Stateful<Img> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.element } } /// The image state between frames struct ImgState { frame_index: usize, last_frame_time: Option<Instant>, started_loading: Option<(Instant, Task<()>)>, } /// The image layout state between frames pub struct ImgLayoutState { frame_index: usize, replacement: Option<AnyElement>, } impl Element for Img { type RequestLayoutState = ImgLayoutState; type PrepaintState = Option<Hitbox>; fn id(&self) -> Option<ElementId> { self.interactivity.element_id.clone() } fn request_layout( &mut self, global_id: Option<&GlobalElementId>, window: &mut Window, cx: &mut App, ) -> (LayoutId, Self::RequestLayoutState) { let mut layout_state = ImgLayoutState { frame_index: 0, replacement: None, }; window.with_optional_element_state(global_id, |state, window| { let mut state = state.map(|state| { state.unwrap_or(ImgState { frame_index: 0, last_frame_time: None, started_loading: None, }) }); let frame_index = state.as_ref().map(|state| state.frame_index).unwrap_or(0); let layout_id = self.interactivity.request_layout( global_id, window, cx, |mut style, window, cx| { let mut replacement_id = None; match self.source.use_data(window, cx) { Some(Ok(data)) => { if let Some(state) = &mut state { let frame_count = data.frame_count(); if frame_count > 1 { let current_time = Instant::now(); if let Some(last_frame_time) = state.last_frame_time { let elapsed = current_time - last_frame_time; let frame_duration = Duration::from(data.delay(state.frame_index)); if elapsed >= frame_duration { state.frame_index = (state.frame_index + 1) % frame_count; state.last_frame_time = Some(current_time - (elapsed - frame_duration)); } } else { state.last_frame_time = Some(current_time); } } state.started_loading = None; } let image_size = data.size(frame_index); if let Length::Auto = style.size.width { style.size.width = match style.size.height { Length::Definite(DefiniteLength::Absolute( AbsoluteLength::Pixels(height), )) => Length::Definite( px(image_size.width.0 as f32 * height.0 / image_size.height.0 as f32) .into(), ), _ => Length::Definite(px(image_size.width.0 as f32).into()), }; } if let Length::Auto = style.size.height { style.size.height = match style.size.width { Length::Definite(DefiniteLength::Absolute( AbsoluteLength::Pixels(width), )) => Length::Definite( px(image_size.height.0 as f32 * width.0 / image_size.width.0 as f32) .into(), ), _ => Length::Definite(px(image_size.height.0 as f32).into()), }; } if global_id.is_some() && data.frame_count() > 1 { window.request_animation_frame(); } } Some(_err) => { if let Some(fallback) = self.style.fallback.as_ref() { let mut element = fallback(); replacement_id = Some(element.request_layout(window, cx)); layout_state.replacement = Some(element); } if let Some(state) = &mut state { state.started_loading = None; } } None => { if let Some(state) = &mut state { if let Some((started_loading, _)) = state.started_loading { if started_loading.elapsed() > LOADING_DELAY { if let Some(loading) = self.style.loading.as_ref() { let mut element = loading(); replacement_id = Some(element.request_layout(window, cx)); layout_state.replacement = Some(element); } } } else { let current_view = window.current_view(); let task = window.spawn(cx, |mut cx| async move { cx.background_executor().timer(LOADING_DELAY).await; cx.update(move |_, cx| { cx.notify(current_view); }) .ok(); }); state.started_loading = Some((Instant::now(), task)); } } } } window.request_layout(style, replacement_id, cx) }, ); layout_state.frame_index = frame_index; ((layout_id, layout_state), state) }) } fn prepaint( &mut self, global_id: Option<&GlobalElementId>, bounds: Bounds<Pixels>, request_layout: &mut Self::RequestLayoutState, window: &mut Window, cx: &mut App, ) -> Self::PrepaintState { self.interactivity.prepaint( global_id, bounds, bounds.size, window, cx, |_, _, hitbox, window, cx| { if let Some(replacement) = &mut request_layout.replacement { replacement.prepaint(window, cx); } hitbox }, ) } fn paint( &mut self, global_id: Option<&GlobalElementId>, bounds: Bounds<Pixels>, layout_state: &mut Self::RequestLayoutState, hitbox: &mut Self::PrepaintState, window: &mut Window, cx: &mut App, ) { let source = self.source.clone(); self.interactivity.paint( global_id, bounds, hitbox.as_ref(), window, cx, |style, window, cx| { let corner_radii = style.corner_radii.to_pixels(bounds.size, window.rem_size()); if let Some(Ok(data)) = source.use_data(window, cx) { let new_bounds = self .style .object_fit .get_bounds(bounds, data.size(layout_state.frame_index)); window .paint_image( new_bounds, corner_radii, data.clone(), layout_state.frame_index, self.style.grayscale, ) .log_err(); } else if let Some(replacement) = &mut layout_state.replacement { replacement.paint(window, cx); } }, ) } } impl Styled for Img { fn style(&mut self) -> &mut StyleRefinement { &mut self.interactivity.base_style } } impl InteractiveElement for Img { fn interactivity(&mut self) -> &mut Interactivity { &mut self.interactivity } } impl IntoElement for Img { type Element = Self; fn into_element(self) -> Self::Element { self } } impl FocusableElement for Img {} impl StatefulInteractiveElement for Img {} impl ImageSource { pub(crate) fn use_data( &self, window: &mut Window, cx: &mut App, ) -> Option<Result<Arc<RenderImage>, ImageCacheError>> { match self { ImageSource::Resource(resource) => window.use_asset::<ImgResourceLoader>(&resource, cx), ImageSource::Custom(loading_fn) => loading_fn(window, cx), ImageSource::Render(data) => Some(Ok(data.to_owned())), ImageSource::Image(data) => window.use_asset::<AssetLogger<ImageDecoder>>(data, cx), } } } #[derive(Clone)] enum ImageDecoder {} impl Asset for ImageDecoder { type Source = Arc<Image>; type Output = Result<Arc<RenderImage>, ImageCacheError>; fn load( source: Self::Source, cx: &mut App, ) -> impl Future<Output = Self::Output> + Send + 'static { let renderer = cx.svg_renderer(); async move { source.to_image_data(renderer).map_err(Into::into) } } }