diff --git a/crates/ui-egui/src/a11y.rs b/crates/ui-egui/src/a11y.rs index d0e34d6fe..d09cb5165 100644 --- a/crates/ui-egui/src/a11y.rs +++ b/crates/ui-egui/src/a11y.rs @@ -995,6 +995,18 @@ pub fn disabled(resp: &Response) { node(resp, "", |n| n.set_disabled()); } +/// `resp`'s node also says `text` (a menu row's state, e.g. "locked"), after any description +/// already set (joined with ", "). +pub fn describe(resp: &Response, text: &str) { + node(resp, "", |n| { + let d = match n.description() { + Some(old) if !old.is_empty() => format!("{old}, {text}"), + _ => text.to_string(), + }; + n.set_description(d); + }); +} + /// What a layer row says besides its name. pub struct RowInfo<'a> { /// The layer's type ("Frame", "Text"). diff --git a/crates/ui-egui/src/context_menu_tests.rs b/crates/ui-egui/src/context_menu_tests.rs index 6ff9ebed9..ab480e225 100644 --- a/crates/ui-egui/src/context_menu_tests.rs +++ b/crates/ui-egui/src/context_menu_tests.rs @@ -189,6 +189,28 @@ fn select_layer_marks_locked_layers_with_a_padlock_and_picks_them() { let _ = lower; } +/// The padlock is only paint: the locked row says "locked" to assistive technology too. +#[test] +fn select_layer_locked_row_is_described_as_locked() { + let (mut u, _, _) = overlap_ui(); + let at = u.app.to_screen(150.0, 150.0); + crate::canvas::open_context_menu(&mut u.app, at, false); + u.app.session.set_selection(vec![]); + crate::menus::open_select_layer(&u.ctx, true); + u.ctx.enable_accesskit(); + u.frame(vec![]); + let nodes: Vec = + u.frame_output(vec![]).platform_output.accesskit_update.expect("an AccessKit update").nodes.into_iter().map(|(_, n)| n).collect(); + let row = |name: &str| { + nodes + .iter() + .find(|n| n.role() == egui::accesskit::Role::MenuItem && n.label() == Some(name)) + .unwrap_or_else(|| panic!("no menu row {name:?}")) + }; + assert!(row("Upper").description().is_some_and(|d| d.contains("locked")), "the locked row says locked"); + assert!(!row("Lower").description().is_some_and(|d| d.contains("locked")), "the other row doesn't"); +} + /// Measured on a single locked layer only (Figma's web app, 2026-11-10): the least certain rule. #[test] fn select_layer_needs_more_than_one_layer_under_the_pointer() { diff --git a/crates/ui-egui/src/menus/tree.rs b/crates/ui-egui/src/menus/tree.rs index b48a8c9de..7443ffc57 100644 --- a/crates/ui-egui/src/menus/tree.rs +++ b/crates/ui-egui/src/menus/tree.rs @@ -408,6 +408,9 @@ pub(super) fn indented_item(ui: &mut Ui, label: &str, shortcut: &str, enabled: b if !enabled { a11y::disabled(&resp); } + if locked { + a11y::describe(&resp, i18n::tr("locked")); + } // The keyboard's item is highlighted like the hovered one. a11y::own_focus(&resp); let hov = plain_row_hover(ui, &resp) && enabled;