mirror of
https://github.com/storytold/lightcraft.git
synced 2026-11-01 14:07:34 -06:00
LR-BEHAV-ACCESS: an alert dialog carries what it says as its accessible description
This commit is contained in:
@@ -28,6 +28,10 @@ use crate::widgets::register;
|
||||
/// windows and their dim.
|
||||
const DIM_ID: &str = "library-problem-dim";
|
||||
const ERROR_WINDOW_ID: &str = "library-problem";
|
||||
/// What the error window says about the library's safety and the way on (drawn as it is, not translated).
|
||||
const EXPLANATION: &str = "Your photos and edits in it are untouched. You can try again (e.g. after closing another program that has it open \
|
||||
or reconnecting its drive), open a different library or create a new one, or continue without a library — \
|
||||
then nothing you do is saved.";
|
||||
const CHOOSER_WINDOW_ID: &str = "library-chooser";
|
||||
|
||||
/// The named `Foreground` layer used by the dim and the two blocking library windows.
|
||||
@@ -1016,6 +1020,12 @@ pub fn show(app: &mut LightcraftApp, ctx: &egui::Context, blocked: bool) {
|
||||
dim(ctx);
|
||||
let mut choice = None;
|
||||
let frame = egui::Frame::window(&ctx.global_style()).inner_margin(egui::Margin::symmetric(18, 14));
|
||||
// what the window says, in one piece, for a screen reader: the labels below are separate nodes
|
||||
let description = if problem.path.is_empty() {
|
||||
format!("{} {} {EXPLANATION}", crate::i18n::tr("LightCraft couldn't find where to keep your library."), problem.error)
|
||||
} else {
|
||||
format!("{} {}. {} {EXPLANATION}", crate::i18n::tr("LightCraft couldn't open the library at"), problem.path, problem.error)
|
||||
};
|
||||
let shown = egui::Window::new(crate::i18n::tr("Your library couldn't be opened"))
|
||||
.id(egui::Id::unique(ERROR_WINDOW_ID))
|
||||
.order(egui::Order::Foreground)
|
||||
@@ -1035,14 +1045,7 @@ pub fn show(app: &mut LightcraftApp, ctx: &egui::Context, blocked: bool) {
|
||||
ui.label(RichText::new(&problem.path).font(t.semibold(12.5)).color(t.text));
|
||||
}
|
||||
ui.add(egui::Label::new(RichText::new(&problem.error).color(t.caution)).wrap());
|
||||
ui.label(
|
||||
RichText::new(
|
||||
"Your photos and edits in it are untouched. You can try again (e.g. after closing another program that has it open \
|
||||
or reconnecting its drive), open a different library or create a new one, or continue without a library — \
|
||||
then nothing you do is saved.",
|
||||
)
|
||||
.color(t.text_dim),
|
||||
);
|
||||
ui.label(RichText::new(EXPLANATION).color(t.text_dim));
|
||||
ui.add_space(4.0);
|
||||
ui.horizontal(|ui| {
|
||||
let mut button = |ui: &mut egui::Ui, id: &str, label: &str, enabled: bool, c: Choice| {
|
||||
@@ -1060,6 +1063,7 @@ pub fn show(app: &mut LightcraftApp, ctx: &egui::Context, blocked: bool) {
|
||||
});
|
||||
if let Some(w) = &shown {
|
||||
crate::widgets::announce_dialog(ctx, &w.response, crate::widgets::Modal::Alert);
|
||||
crate::widgets::describe_dialog(ctx, &w.response, &description);
|
||||
// A press on the dim raises its area; keep this window (and its own buttons) above it.
|
||||
ctx.move_to_top(w.response.layer_id);
|
||||
}
|
||||
|
||||
@@ -513,6 +513,7 @@ fn window(ctx: &egui::Context, id: &str, title: &str, text: &str, buttons: &[(&s
|
||||
// A backdrop press can raise its area; keep the prompt buttons above it.
|
||||
if let Some(window) = shown {
|
||||
crate::widgets::announce_dialog(ctx, &window.response, crate::widgets::Modal::Alert);
|
||||
crate::widgets::describe_dialog(ctx, &window.response, text);
|
||||
ctx.move_to_top(window.response.layer_id);
|
||||
}
|
||||
input.button_ids = seen;
|
||||
|
||||
@@ -1186,6 +1186,13 @@ pub(crate) fn announce_dialog(ctx: &egui::Context, response: &Response, kind: Mo
|
||||
});
|
||||
}
|
||||
|
||||
/// Gives the dialog `response` drew the words it says as its accessible description, for an alert
|
||||
/// whose content (the task names, the error) is drawn as separate labels: a screen reader then
|
||||
/// has what the alert is about when it announces the alert, not only its title.
|
||||
pub(crate) fn describe_dialog(ctx: &egui::Context, response: &Response, text: &str) {
|
||||
ctx.accesskit_node_builder(response.id, |node| node.set_description(text));
|
||||
}
|
||||
|
||||
/// The same for one that is on or off (a toggle, a selected row or swatch).
|
||||
pub(crate) fn announce_selected(resp: &egui::Response, name: impl Into<String>, selected: bool) {
|
||||
let name = name.into();
|
||||
|
||||
@@ -85,3 +85,30 @@ fn a_library_that_could_not_be_opened_is_a_modal_alert_dialog() {
|
||||
assert_eq!((role, modal), (Role::AlertDialog, true), "{title:?} is a modal alert dialog");
|
||||
}
|
||||
}
|
||||
|
||||
/// The description of every alert dialog in the tree.
|
||||
fn alert_descriptions(h: &Harness<'_, LightcraftApp>) -> Vec<String> {
|
||||
h.root()
|
||||
.children_recursive()
|
||||
.map(|n| n.accesskit_node())
|
||||
.filter(|n| n.role() == Role::AlertDialog)
|
||||
.map(|n| n.data().description().unwrap_or_default().to_string())
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_alert_carries_what_it_says_as_its_description() {
|
||||
let mut h = harness();
|
||||
h.state_mut().quit_prompt = Some(lightcraft_ui_egui::QuitPrompt::Tasks("1 task is still running: Import. Quitting stops it.".into()));
|
||||
h.run_steps(6);
|
||||
let said = alert_descriptions(&h);
|
||||
assert!(said.iter().any(|d| d.contains("1 task is still running: Import")), "the quit prompt names the task: {said:?}");
|
||||
let mut h = harness();
|
||||
h.state_mut().library_problem = Some(lightcraft_ui_egui::panels::library_problem::LibraryProblem::new("/no/such/library", "No catalog here."));
|
||||
h.run_steps(6);
|
||||
let said = alert_descriptions(&h);
|
||||
assert!(
|
||||
said.iter().any(|d| d.contains("/no/such/library") && d.contains("No catalog here.") && d.contains("untouched")),
|
||||
"the library alert has its path, error and explanation: {said:?}"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user