diff --git a/Cargo.lock b/Cargo.lock index bbd3115..ec86380 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,110 @@ dependencies = [ "uuid", ] +[[package]] +name = "accesskit_atspi_common" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e8c61bee90b42a772d39d06a740207dc71a4e780004ace1db8d99fb1baaa954" +dependencies = [ + "accesskit", + "accesskit_consumer 0.36.0", + "atspi-common", + "phf", + "serde", + "zvariant 5.15.0", +] + +[[package]] +name = "accesskit_consumer" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53cf47daed85312e763fbf85ceca136e0d7abc68e0a7e12abe11f48172bc3b10" +dependencies = [ + "accesskit", + "hashbrown 0.16.1", +] + +[[package]] +name = "accesskit_consumer" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e0d7e25d06f4dc21d1774d67146e9e80d6789216cbd4d1e88185b0095dba60" +dependencies = [ + "accesskit", + "hashbrown 0.16.1", +] + +[[package]] +name = "accesskit_consumer" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d10a236f96f87d70732e44520046785431ef01d5bcd6b041317bfadd2f88245" +dependencies = [ + "accesskit", + "hashbrown 0.16.1", +] + +[[package]] +name = "accesskit_macos" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce02dc63b43f0c9296af9ac946312a2dc8814427d7a64d2d600971dac55b6076" +dependencies = [ + "accesskit", + "accesskit_consumer 0.38.0", + "hashbrown 0.16.1", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "accesskit_unix" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b016ca8db0ea0ea2ceff29a9d6240391492d960716aa471967c00e8cc8cb197c" +dependencies = [ + "accesskit", + "accesskit_atspi_common", + "async-channel", + "async-executor", + "async-task", + "atspi", + "futures-lite", + "futures-util", + "serde", + "zbus 5.19.0", +] + +[[package]] +name = "accesskit_windows" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff7009f1a532e917d66970a1e80c965140c6cfbbabbdde3d64e5431e6c78e21" +dependencies = [ + "accesskit", + "accesskit_consumer 0.35.0", + "hashbrown 0.16.1", + "static_assertions", + "windows 0.62.2", + "windows-core", +] + +[[package]] +name = "accesskit_winit" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fe9a94394896352cc4660ca2288bd4ef883d83238853c038b44070c8f134313" +dependencies = [ + "accesskit", + "accesskit_macos", + "accesskit_unix", + "accesskit_windows", + "raw-window-handle", + "winit", +] + [[package]] name = "adler2" version = "2.0.1" @@ -360,6 +464,20 @@ dependencies = [ "tokio", ] +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + [[package]] name = "async-io" version = "2.6.0" @@ -482,6 +600,43 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "atspi" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77886257be21c9cd89a4ae7e64860c6f0eefca799bb79127913052bd0eefb3d" +dependencies = [ + "atspi-common", + "atspi-proxies", +] + +[[package]] +name = "atspi-common" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c5617155740c98003016429ad13fe43ce7a77b007479350a9f8bf95a29f63d" +dependencies = [ + "enumflags2", + "serde", + "static_assertions", + "zbus 5.19.0", + "zbus-lockstep", + "zbus-lockstep-macros", + "zbus_names 4.3.4", + "zvariant 5.15.0", +] + +[[package]] +name = "atspi-proxies" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2230e48787ed3eb4088996eab66a32ca20c0b67bbd4fd6cdfe79f04f1f04c9fc" +dependencies = [ + "atspi-common", + "serde", + "zbus 5.19.0", +] + [[package]] name = "autocfg" version = "1.5.1" @@ -2017,6 +2172,7 @@ version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea6bf3608db949588b95b8b341ee358d0c3f95cf4dc3f53d8d76717edee87db" dependencies = [ + "accesskit_winit", "arboard", "bytemuck", "egui", @@ -5870,7 +6026,7 @@ dependencies = [ "rand 0.8.7", "serde", "sha2 0.10.9", - "zbus", + "zbus 4.4.0", ] [[package]] @@ -7229,6 +7385,11 @@ name = "uuid" version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +dependencies = [ + "js-sys", + "serde_core", + "wasm-bindgen", +] [[package]] name = "valuable" @@ -8544,9 +8705,68 @@ dependencies = [ "uds_windows", "windows-sys 0.52.0", "xdg-home", - "zbus_macros", - "zbus_names", - "zvariant", + "zbus_macros 4.4.0", + "zbus_names 3.0.0", + "zvariant 4.2.0", +] + +[[package]] +name = "zbus" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5db4be7c075cb421e4b7ee645541604239bd243ba7c357511f4ff3a74b555907" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "libc", + "ordered-stream", + "rustix 1.1.4", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow 1.0.4", + "zbus_macros 5.19.0", + "zbus_names 4.3.4", + "zvariant 5.15.0", +] + +[[package]] +name = "zbus-lockstep" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6998de05217a084b7578728a9443d04ea4cd80f2a0839b8d78770b76ccd45863" +dependencies = [ + "zbus_xml", + "zvariant 5.15.0", +] + +[[package]] +name = "zbus-lockstep-macros" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10da05367f3a7b7553c8cdf8fa91aee6b64afebe32b51c95177957efc47ca3a0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "zbus-lockstep", + "zbus_xml", + "zvariant 5.15.0", ] [[package]] @@ -8559,7 +8779,22 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.119", - "zvariant_utils", + "zvariant_utils 2.1.0", +] + +[[package]] +name = "zbus_macros" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 3.0.3", + "zbus_names 4.3.4", + "zvariant 5.15.0", + "zvariant_utils 4.2.0", ] [[package]] @@ -8570,7 +8805,39 @@ checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" dependencies = [ "serde", "static_assertions", - "zvariant", + "zvariant 4.2.0", +] + +[[package]] +name = "zbus_names" +version = "4.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" +dependencies = [ + "serde", + "winnow 1.0.4", + "zvariant 5.15.0", +] + +[[package]] +name = "zbus_xml" +version = "5.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1586c021a01ca0a9216dcd874e546382e156a5cbab5fab6cb5f10087e22682a" +dependencies = [ + "serde", + "winnow 1.0.4", + "zbus_names 4.3.4", + "zvariant 5.15.0", +] + +[[package]] +name = "zcheapstr" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1afec51604565183aeb5c54c20aeab286120d4e4460f7f76e3e8bb8c0d99473" +dependencies = [ + "serde", ] [[package]] @@ -8704,7 +8971,22 @@ dependencies = [ "enumflags2", "serde", "static_assertions", - "zvariant_derive", + "zvariant_derive 4.2.0", +] + +[[package]] +name = "zvariant" +version = "5.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1d34c27cc6cdd1f458427519dd6b8612f7b7e3f7b9a0b2355d041dda9869147" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow 1.0.4", + "zcheapstr", + "zvariant_derive 5.15.0", + "zvariant_utils 4.2.0", ] [[package]] @@ -8717,7 +8999,20 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.119", - "zvariant_utils", + "zvariant_utils 2.1.0", +] + +[[package]] +name = "zvariant_derive" +version = "5.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864155e69b4352db0c7f374917bf45d1e0c8d17659c8b3dbf9795f3673f8c497" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 3.0.3", + "zvariant_utils 4.2.0", ] [[package]] @@ -8730,3 +9025,16 @@ dependencies = [ "quote", "syn 2.0.119", ] + +[[package]] +name = "zvariant_utils" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad0294361a320b694a328460dc73add56c306150f5cb6bfafc44446120008a3" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 3.0.3", + "winnow 1.0.4", +] diff --git a/Cargo.toml b/Cargo.toml index 8c85630..805142f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,8 +73,10 @@ keyring = { version = "3" } clap = { version = "4", features = ["derive"] } # The UI. `glow` rather than `wgpu` because step two of the player hands mpv an # `MPV_RENDER_API_TYPE_OPENGL` context, and that has to be the same context -# eframe is drawing into. +# eframe is drawing into. `accesskit` hands the widget tree to the platform's +# screen reader (AT-SPI, UI Automation, NSAccessibility). eframe = { version = "0.35", default-features = false, features = [ + "accesskit", "default_fonts", "glow", "wayland", diff --git a/crates/pstr-app/src/ui/account.rs b/crates/pstr-app/src/ui/account.rs index 7e4d81e..0fe5d11 100644 --- a/crates/pstr-app/src/ui/account.rs +++ b/crates/pstr-app/src/ui/account.rs @@ -14,6 +14,7 @@ use crate::app::Action; use crate::engine::AccountStatus; use crate::theme; use crate::ui; +use crate::ui::widgets::Named; /// What the account section holds between frames. #[derive(Default)] @@ -379,7 +380,7 @@ pub fn browser( if !browser.trail.is_empty() && ui .button(egui_phosphor::regular::ARROW_LEFT) - .on_hover_text("Up a folder") + .hint("Up a folder") .clicked() { browser.up(actions); diff --git a/crates/pstr-app/src/ui/history.rs b/crates/pstr-app/src/ui/history.rs index e25579a..362f79a 100644 --- a/crates/pstr-app/src/ui/history.rs +++ b/crates/pstr-app/src/ui/history.rs @@ -10,6 +10,7 @@ use pstr_core::library::{Episode, Library, Title, TitleKind}; use crate::app::{Action, Page}; use crate::theme; +use crate::ui::widgets::Named; use crate::ui::{self, Art}; const STILL: egui::Vec2 = egui::vec2(128.0, 72.0); @@ -161,7 +162,7 @@ fn row( ) .frame(false), ) - .on_hover_text(if episode.is_watched() { + .hint(if episode.is_watched() { "Remove from history — marks it unwatched" } else { "Remove from history — forgets where you stopped" diff --git a/crates/pstr-app/src/ui/library.rs b/crates/pstr-app/src/ui/library.rs index 55f43a0..c110e71 100644 --- a/crates/pstr-app/src/ui/library.rs +++ b/crates/pstr-app/src/ui/library.rs @@ -315,8 +315,16 @@ fn shelf_controls(ui: &mut egui::Ui, view: &LibraryView, actions: &mut Vec egui::Respo ); let (rect, response) = ui.allocate_exact_size(size, Sense::click()); + let response = response.named(text); if !ui.is_rect_visible(rect) { return response; } @@ -626,6 +628,11 @@ pub fn card(ui: &mut egui::Ui, card: Card<'_>) -> egui::Response { let (rect, response) = ui.allocate_exact_size(Vec2::new(width, image_height + text_height), Sense::click()); + let response = if card.subtitle.is_empty() { + response.named(card.name) + } else { + response.named(format!("{}, {}", card.name, card.subtitle)) + }; // Reached with the arrow keys or Tab: bring it into view, before the // early return below would skip a tile that is just off screen. if response.gained_focus() { diff --git a/crates/pstr-app/src/ui/player.rs b/crates/pstr-app/src/ui/player.rs index 1734a9a..93f48b4 100644 --- a/crates/pstr-app/src/ui/player.rs +++ b/crates/pstr-app/src/ui/player.rs @@ -20,6 +20,7 @@ use egui::{Align2, Color32, CornerRadius, Rect, Sense, Vec2}; use crate::app::{Action, Adjacent}; use crate::playback::{Command, Playback}; use crate::theme; +use crate::ui::widgets::Named; use crate::ui::{self, transport::Neighbours}; /// How long the controls stay up after the pointer stops moving. @@ -586,7 +587,7 @@ fn up_next_card( theme::Role::Body.font(), Color32::WHITE, ); - if response.on_hover_text("Play now").clicked() { + if response.hint("Play now").clicked() { actions.push(Action::PlayAdjacent(Adjacent::Next)); } @@ -746,7 +747,7 @@ fn chrome( .corner_radius(CornerRadius::same(theme::radius::MD)) .min_size(Vec2::new(38.0, 32.0)), ) - .on_hover_text(if fullscreen { + .hint(if fullscreen { "Leave fullscreen (F)" } else { "Fullscreen (F)" @@ -778,7 +779,7 @@ fn chrome( .corner_radius(CornerRadius::same(theme::radius::MD)) .min_size(Vec2::new(38.0, 32.0)), ) - .on_hover_text("Episodes (E)") + .hint("Episodes (E)") .clicked() { actions.push(Action::ToggleEpisodes); @@ -798,7 +799,7 @@ fn chrome( .corner_radius(CornerRadius::same(theme::radius::MD)) .min_size(Vec2::new(38.0, 32.0)), ) - .on_hover_text("Stop") + .hint("Stop") .clicked() { actions.push(Action::Player(Command::Stop)); @@ -904,7 +905,7 @@ fn episode_drawer( ) .frame(false), ) - .on_hover_text("Close (E)") + .hint("Close (E)") .clicked() { actions.push(Action::ToggleEpisodes); diff --git a/crates/pstr-app/src/ui/settings.rs b/crates/pstr-app/src/ui/settings.rs index f17b534..efd9464 100644 --- a/crates/pstr-app/src/ui/settings.rs +++ b/crates/pstr-app/src/ui/settings.rs @@ -11,9 +11,10 @@ use pstr_core::prefs::PlaybackPrefs; use pstr_player::LANGUAGES; use crate::app::Action; -use crate::desktop_prefs::{CACHE_CHOICES, DesktopPrefs}; +use crate::desktop_prefs::{CACHE_CHOICES, DesktopPrefs, TEXT_SIZE_CHOICES}; use crate::theme; use crate::ui; +use crate::ui::widgets::Named; /// The settings that are a single value rather than a form. /// @@ -79,6 +80,7 @@ fn appearance_form(ui: &mut egui::Ui, appearance: Appearance, actions: &mut Vec< ui.horizontal_wrapped(|ui| { for flavor in Flavor::ALL { if palette_card(ui, appearance, flavor) + .named(flavor.label()) .on_hover_text(flavor.description()) .clicked() && appearance.flavor != flavor @@ -269,7 +271,7 @@ fn swatch(ui: &mut egui::Ui, appearance: Appearance, accent: Accent) -> egui::Re } } response - .on_hover_text(accent.label()) + .hint(accent.label()) .on_hover_cursor(egui::CursorIcon::PointingHand) } diff --git a/crates/pstr-app/src/ui/title.rs b/crates/pstr-app/src/ui/title.rs index 4c30864..da932ef 100644 --- a/crates/pstr-app/src/ui/title.rs +++ b/crates/pstr-app/src/ui/title.rs @@ -8,6 +8,7 @@ use crate::app::{Action, Page}; use crate::engine::{DownloadItem, DownloadKey, DownloadState}; use crate::playback::PlaybackTarget; use crate::theme; +use crate::ui::widgets::Named; use crate::ui::{self, Art, Card}; pub struct OfflineView<'a> { @@ -878,6 +879,7 @@ pub(crate) fn still( actions: &mut Vec, ) { let (rect, response) = ui.allocate_exact_size(size, egui::Sense::click()); + let response = response.named(format!("Play {}", episode.label())); if !ui.is_rect_visible(rect) { return; } diff --git a/crates/pstr-app/src/ui/toast.rs b/crates/pstr-app/src/ui/toast.rs index 19e4496..7ca46d1 100644 --- a/crates/pstr-app/src/ui/toast.rs +++ b/crates/pstr-app/src/ui/toast.rs @@ -9,6 +9,7 @@ use egui::{Align2, Color32, CornerRadius, Sense, Stroke, Vec2}; use crate::app::Action; use crate::theme; +use crate::ui::widgets::Named; /// How long a message stays up. const SECONDS: f64 = 5.0; @@ -214,7 +215,7 @@ fn card(ui: &mut egui::Ui, toast: &Toast) -> Pressed { [c + Vec2::new(-d, d), c + Vec2::new(d, -d)], Stroke::new(1.5, ink), ); - if response.on_hover_text("Dismiss").clicked() { + if response.hint("Dismiss").clicked() { pressed = Pressed::Close; } if let Some((label, _)) = &toast.action { diff --git a/crates/pstr-app/src/ui/transport.rs b/crates/pstr-app/src/ui/transport.rs index 9a34482..4de41c2 100644 --- a/crates/pstr-app/src/ui/transport.rs +++ b/crates/pstr-app/src/ui/transport.rs @@ -17,6 +17,7 @@ use crate::app::{Action, Adjacent, Page}; use crate::playback::{Command, Playback}; use crate::theme; use crate::ui; +use crate::ui::widgets::Named; /// Height of the clickable seek bar. Comfortably larger than the few pixels it /// draws, because a seek bar you have to aim at is a seek bar nobody uses. @@ -163,7 +164,7 @@ pub fn mini( egui::StrokeKind::Inside, ); } - if response.on_hover_text("Show the picture again").clicked() { + if response.hint("Show the picture again").clicked() { actions.push(Action::Goto(Page::Player)); } } @@ -313,7 +314,7 @@ fn play_pause(ui: &mut egui::Ui, playback: &Playback, actions: &mut Vec, } if response - .on_hover_text(if playback.paused { + .hint(if playback.paused { "Play (Space)" } else { "Pause (Space)" @@ -354,7 +355,7 @@ fn step_button( enabled, egui::Button::new(theme::Role::Subhead.rich(label)).min_size(Vec2::new(38.0, 30.0)), ); - if response.on_hover_text(hover).clicked() { + if response.hint(hover).clicked() { push(actions); } } @@ -436,7 +437,7 @@ fn volume(ui: &mut egui::Ui, playback: &Playback, actions: &mut Vec) { }; if ui .button(theme::Role::Subhead.rich(icon)) - .on_hover_text(format!("Mute (M) — {:.0}%", playback.volume)) + .hint(format!("Mute (M) — {:.0}%", playback.volume)) .clicked() { actions.push(Action::ToggleMute); @@ -567,6 +568,13 @@ fn seek_bar(ui: &mut egui::Ui, playback: &Playback, actions: &mut Vec, t let width = ui.available_width(); let (rect, response) = ui.allocate_exact_size(Vec2::new(width, SEEK_HIT_HEIGHT), Sense::click_and_drag()); + response.widget_info(|| { + egui::WidgetInfo::slider( + playback.duration.is_some(), + playback.position, + format!("Position, {}", ui::format_time(playback.position)), + ) + }); let active = response.hovered() || response.dragged(); // Grows rather than jumps when the pointer arrives. diff --git a/crates/pstr-app/src/ui/widgets.rs b/crates/pstr-app/src/ui/widgets.rs index eb0de65..590509b 100644 --- a/crates/pstr-app/src/ui/widgets.rs +++ b/crates/pstr-app/src/ui/widgets.rs @@ -8,6 +8,31 @@ use egui::{Color32, CornerRadius, Sense, Stroke, Vec2}; use crate::theme; +/// A name for a screen reader, on a control whose face does not say it. +/// +/// A button drawn as an icon glyph is announced as the icon font's private +/// code point, and one painted by hand as nothing at all. +pub trait Named { + /// The control's name to a screen reader. + fn named(self, name: impl Into) -> Self; + /// Hover text that is also the control's name. + fn hint(self, text: impl Into) -> Self; +} + +impl Named for egui::Response { + fn named(self, name: impl Into) -> Self { + let name = name.into(); + let enabled = self.enabled(); + self.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, enabled, &name)); + self + } + + fn hint(self, text: impl Into) -> Self { + let text = text.into(); + self.named(text.clone()).on_hover_text(text) + } +} + /// An on/off switch with its label beside it, for settings. /// /// Replaces egui's checkbox, whose unchecked box is drawn in the card colour — @@ -98,6 +123,9 @@ pub fn toggle(ui: &mut egui::Ui, on: &mut bool, text: &str) -> egui::Response { pub fn watched_mark(ui: &mut egui::Ui, watched: bool) -> egui::Response { const SIZE: f32 = 20.0; let (rect, response) = ui.allocate_exact_size(Vec2::splat(SIZE), Sense::click()); + response.widget_info(|| { + egui::WidgetInfo::selected(egui::WidgetType::Checkbox, true, watched, "Watched") + }); if ui.is_rect_visible(rect) { let ctx = ui.ctx(); let t = ctx.animate_bool_with_time(response.id, watched, theme::motion::STATE); @@ -278,6 +306,12 @@ pub enum DownloadGlyph { pub fn download_button(ui: &mut egui::Ui, glyph: DownloadGlyph) -> egui::Response { const SIZE: f32 = 30.0; let (rect, response) = ui.allocate_exact_size(Vec2::splat(SIZE), Sense::click()); + let response = response.named(match glyph { + DownloadGlyph::Download => "Download".to_owned(), + DownloadGlyph::Progress(done) => format!("Downloading, {:.0}%", done * 100.0), + DownloadGlyph::Paused(done) => format!("Download paused, {:.0}%", done * 100.0), + DownloadGlyph::Done => "Downloaded".to_owned(), + }); if !ui.is_rect_visible(rect) { return response; }