683 lines · 22.7 KB · Rust
RawDownload
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.