Skip to content

Commit

Permalink
Add alt text to Image
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasmerlin committed Dec 29, 2024
1 parent a0d3075 commit 05052ab
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 12 deletions.
4 changes: 3 additions & 1 deletion crates/egui/src/widgets/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,8 @@ impl Widget for Button<'_> {
});

if ui.is_rect_visible(rect) {
let visuals = ui.style().interact(&response);
let style = ui.style().clone();
let visuals = style.interact(&response);

let (frame_expansion, frame_rounding, frame_fill, frame_stroke) = if selected {
let selection = ui.visuals().selection;
Expand Down Expand Up @@ -344,6 +345,7 @@ impl Widget for Button<'_> {
image_rect,
image.show_loading_spinner,
&image_options,
None,
);
response = widgets::image::texture_load_result_response(
&image.source(ui.ctx()),
Expand Down
58 changes: 49 additions & 9 deletions crates/egui/src/widgets/image.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use std::{borrow::Cow, sync::Arc, time::Duration};

use emath::{Float as _, Rot2};
use epaint::RectShape;
use epaint::{
text::{LayoutJob, TextFormat, TextWrapping},
RectShape,
};

use crate::{
load::{Bytes, SizeHint, SizedTexture, TextureLoadResult, TexturePoll},
Expand Down Expand Up @@ -51,6 +54,7 @@ pub struct Image<'a> {
sense: Sense,
size: ImageSize,
pub(crate) show_loading_spinner: Option<bool>,
alt_text: Option<String>,
}

impl<'a> Image<'a> {
Expand All @@ -76,6 +80,7 @@ impl<'a> Image<'a> {
sense: Sense::hover(),
size,
show_loading_spinner: None,
alt_text: None,
}
}

Expand Down Expand Up @@ -255,6 +260,13 @@ impl<'a> Image<'a> {
self.show_loading_spinner = Some(show);
self
}

/// Set alt text for the image. This will be shown when the image fails to load.
/// It will also be read to screen readers.
pub fn alt_text(mut self, label: impl Into<String>) -> Self {
self.alt_text = Some(label.into());
self
}
}

impl<'a, T: Into<ImageSource<'a>>> From<T> for Image<'a> {
Expand Down Expand Up @@ -345,13 +357,14 @@ impl<'a> Image<'a> {
/// # });
/// ```
#[inline]
pub fn paint_at(&self, ui: &Ui, rect: Rect) {
pub fn paint_at(&self, ui: &mut Ui, rect: Rect) {
paint_texture_load_result(
ui,
&self.load_for_size(ui.ctx(), rect.size()),
rect,
self.show_loading_spinner,
&self.image_options,
self.alt_text.as_deref(),
);
}
}
Expand All @@ -363,14 +376,19 @@ impl<'a> Widget for Image<'a> {
let ui_size = self.calc_size(ui.available_size(), original_image_size);

let (rect, response) = ui.allocate_exact_size(ui_size, self.sense);
response.widget_info(|| WidgetInfo::new(WidgetType::Image));
response.widget_info(|| {
let mut info = WidgetInfo::new(WidgetType::Image);
info.label = self.alt_text.clone();
info
});
if ui.is_rect_visible(rect) {
paint_texture_load_result(
ui,
&tlr,
rect,
self.show_loading_spinner,
&self.image_options,
self.alt_text.as_deref(),
);
}
texture_load_result_response(&self.source(ui.ctx()), &tlr, response)
Expand Down Expand Up @@ -596,11 +614,12 @@ impl<'a> ImageSource<'a> {
}

pub fn paint_texture_load_result(
ui: &Ui,
ui: &mut Ui,
tlr: &TextureLoadResult,
rect: Rect,
show_loading_spinner: Option<bool>,
options: &ImageOptions,
alt: Option<&str>,
) {
match tlr {
Ok(TexturePoll::Ready { texture }) => {
Expand All @@ -615,12 +634,33 @@ pub fn paint_texture_load_result(
}
Err(_) => {
let font_id = TextStyle::Body.resolve(ui.style());
ui.painter().text(
rect.center(),
Align2::CENTER_CENTER,
let mut job = LayoutJob::default();
job.wrap = TextWrapping::wrap_at_width(rect.width());
job.append(
"⚠",
font_id,
ui.visuals().error_fg_color,
0.0,
TextFormat {
color: ui.visuals().error_fg_color,
font_id: font_id.clone(),
..Default::default()
},
);
if let Some(alt) = alt {
job.append(
alt,
ui.spacing().item_spacing.x,
TextFormat {
color: ui.visuals().text_color(),
font_id,
..Default::default()
},
);
}
let galley = ui.painter().layout_job(job);
ui.painter().galley(
rect.center() - 0.5 * galley.size(),
galley,
ui.visuals().text_color(),
);
}
}
Expand Down
17 changes: 15 additions & 2 deletions crates/egui/src/widgets/image_button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub struct ImageButton<'a> {
sense: Sense,
frame: bool,
selected: bool,
alt_text: Option<String>,
}

impl<'a> ImageButton<'a> {
Expand All @@ -20,6 +21,7 @@ impl<'a> ImageButton<'a> {
sense: Sense::click(),
frame: true,
selected: false,
alt_text: None,
}
}

Expand Down Expand Up @@ -87,7 +89,11 @@ impl<'a> Widget for ImageButton<'a> {

let padded_size = image_size + 2.0 * padding;
let (rect, response) = ui.allocate_exact_size(padded_size, self.sense);
response.widget_info(|| WidgetInfo::new(WidgetType::ImageButton));
response.widget_info(|| {
let mut info = WidgetInfo::new(WidgetType::ImageButton);
info.label = self.alt_text.clone();
info
});

if ui.is_rect_visible(rect) {
let (expansion, rounding, fill, stroke) = if self.selected {
Expand Down Expand Up @@ -121,7 +127,14 @@ impl<'a> Widget for ImageButton<'a> {
// let image_rect = image_rect.expand2(expansion); // can make it blurry, so let's not
let image_options = self.image.image_options().clone();

widgets::image::paint_texture_load_result(ui, &tlr, image_rect, None, &image_options);
widgets::image::paint_texture_load_result(
ui,
&tlr,
image_rect,
None,
&image_options,
self.alt_text.as_deref(),
);

// Draw frame outline:
ui.painter()
Expand Down
10 changes: 10 additions & 0 deletions crates/egui_demo_app/src/apps/image_viewer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub struct ImageViewer {
fit: ImageFit,
maintain_aspect_ratio: bool,
max_size: Vec2,
alt_text: String,
}

#[derive(Clone, Copy, PartialEq, Eq)]
Expand Down Expand Up @@ -44,6 +45,7 @@ impl Default for ImageViewer {
fit: ImageFit::Fraction(Vec2::splat(1.0)),
maintain_aspect_ratio: true,
max_size: Vec2::splat(2048.0),
alt_text: "My Image".to_owned(),
}
}
}
Expand Down Expand Up @@ -184,6 +186,11 @@ impl eframe::App for ImageViewer {
ui.add_space(5.0);
ui.label("Aspect ratio is maintained by scaling both sides as necessary");
ui.checkbox(&mut self.maintain_aspect_ratio, "Maintain aspect ratio");

// alt text
ui.add_space(5.0);
ui.label("Alt text");
ui.text_edit_singleline(&mut self.alt_text);

// forget all images
if ui.button("Forget all images").clicked() {
Expand Down Expand Up @@ -211,6 +218,9 @@ impl eframe::App for ImageViewer {
}
image = image.maintain_aspect_ratio(self.maintain_aspect_ratio);
image = image.max_size(self.max_size);
if !self.alt_text.is_empty() {
image = image.alt_text(&self.alt_text);
}

ui.add_sized(ui.available_size(), image);
});
Expand Down

0 comments on commit 05052ab

Please sign in to comment.