Add Loading and Fallback States to Image Elements (via StyledImage) (#20371)

@iamnbutler edit:

This pull request enhances the image element by introducing the ability
to display loading and fallback states.

Changes:

- Implemented the loading and fallback states for image elements using
`.with_loading` and `.with_fallback` respectively.
- Introduced the `StyledImage` trait and `ImageStyle` to enable a fluent
API for changing image styles across image types (`Img`,
`Stateful<Img>`, etc).

Example Usage:

```rust
fn loading_element() -> impl IntoElement {
    div().size_full().flex_none().p_0p5().rounded_sm().child(
        div().size_full().with_animation(
            "loading-bg",
            Animation::new(Duration::from_secs(3))
                .repeat()
                .with_easing(pulsating_between(0.04, 0.24)),
            move |this, delta| this.bg(black().opacity(delta)),
        ),
    )
}

fn fallback_element() -> impl IntoElement {
    let fallback_color: Hsla = black().opacity(0.5);

    div().size_full().flex_none().p_0p5().child(
        div()
            .size_full()
            .flex()
            .items_center()
            .justify_center()
            .rounded_sm()
            .text_sm()
            .text_color(fallback_color)
            .border_1()
            .border_color(fallback_color)
            .child("?"),
    )
}

impl Render for ImageLoadingExample {
    fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
        img("some/image/path")
            .id("image-1")
            .with_fallback(|| Self::fallback_element().into_any_element())
            .with_loading(|| Self::loading_element().into_any_element())
    }
}
```

Note:

An `Img` must have an `id` to be able to add a loading state.

Release Notes:

- N/A

---------

Co-authored-by: nate <nate@zed.dev>
Co-authored-by: michael <michael@zed.dev>
Co-authored-by: Nate Butler <iamnbutler@gmail.com>
Co-authored-by: Antonio Scandurra <me@as-cii.com>
This commit is contained in:
Mikayla Maki 2024-11-15 19:12:01 -08:00 committed by GitHub
parent f34877334e
commit 516f7b3642
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 1700 additions and 1041 deletions

View file

@ -27,11 +27,11 @@ mod test;
mod windows;
use crate::{
point, Action, AnyWindowHandle, AppContext, AsyncWindowContext, BackgroundExecutor, Bounds,
DevicePixels, DispatchEventResult, Font, FontId, FontMetrics, FontRun, ForegroundExecutor,
GPUSpecs, GlyphId, ImageSource, Keymap, LineLayout, Pixels, PlatformInput, Point,
RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, ScaledPixels, Scene,
SharedString, Size, SvgSize, Task, TaskLabel, WindowContext, DEFAULT_WINDOW_SIZE,
point, Action, AnyWindowHandle, AsyncWindowContext, BackgroundExecutor, Bounds, DevicePixels,
DispatchEventResult, Font, FontId, FontMetrics, FontRun, ForegroundExecutor, GPUSpecs, GlyphId,
ImageSource, Keymap, LineLayout, Pixels, PlatformInput, Point, RenderGlyphParams, RenderImage,
RenderImageParams, RenderSvgParams, ScaledPixels, Scene, SharedString, Size, SvgRenderer,
SvgSize, Task, TaskLabel, WindowContext, DEFAULT_WINDOW_SIZE,
};
use anyhow::{anyhow, Result};
use async_task::Runnable;
@ -1290,11 +1290,13 @@ impl Image {
/// Use the GPUI `use_asset` API to make this image renderable
pub fn use_render_image(self: Arc<Self>, cx: &mut WindowContext) -> Option<Arc<RenderImage>> {
ImageSource::Image(self).use_data(cx)
ImageSource::Image(self)
.use_data(cx)
.and_then(|result| result.ok())
}
/// Convert the clipboard image to an `ImageData` object.
pub fn to_image_data(&self, cx: &AppContext) -> Result<Arc<RenderImage>> {
pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result<Arc<RenderImage>> {
fn frames_for_image(
bytes: &[u8],
format: image::ImageFormat,
@ -1331,10 +1333,7 @@ impl Image {
ImageFormat::Bmp => frames_for_image(&self.bytes, image::ImageFormat::Bmp)?,
ImageFormat::Tiff => frames_for_image(&self.bytes, image::ImageFormat::Tiff)?,
ImageFormat::Svg => {
// TODO: Fix this
let pixmap = cx
.svg_renderer()
.render_pixmap(&self.bytes, SvgSize::ScaleFactor(1.0))?;
let pixmap = svg_renderer.render_pixmap(&self.bytes, SvgSize::ScaleFactor(1.0))?;
let buffer =
image::ImageBuffer::from_raw(pixmap.width(), pixmap.height(), pixmap.take())