Keep consecutive spaces in recent file accessible names

Fixes #436

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
NetterSilver
2026-10-15 12:37:55 +02:00
co-authored by Claude Haiku 5.5
parent 8482c3e85b
commit 72a3c8522f
3 changed files with 71 additions and 2 deletions
+5
View File
@@ -813,6 +813,11 @@ pub fn button(resp: &Response, tip: &str) {
});
}
/// A button named by `label` as it is: no key is split off (a file name may hold two spaces).
pub fn named_button(resp: &Response, label: &str) {
node(resp, label, |n| n.set_role(Role::Button));
}
/// A link, named by its text.
pub fn link(resp: &Response, label: &str) {
node(resp, label, |n| n.set_role(Role::Link));
+2 -2
View File
@@ -738,7 +738,7 @@ fn grid(ui: &mut Ui, t: &Tokens, recents: &mut Recents, files: &[Shown], now: u6
for (i, file) in row.iter().enumerate() {
let rect = Rect::from_min_size(pos2(r.min.x + i as f32 * (card_w + GAP), r.min.y), vec2(card_w, card_h));
let resp = ui.interact(rect, card_id(&file.path), Sense::click());
a11y::button(&resp, &spoken(file, now));
a11y::named_button(&resp, &spoken(file, now));
let resp = resp.on_hover_text(&file.path);
let visible = ui.is_rect_visible(rect);
let picture = recents.picture(&file.path, visible, time);
@@ -878,7 +878,7 @@ fn list(ui: &mut Ui, t: &Tokens, recents: &mut Recents, files: &[Shown], now: u6
for file in files {
let (r, _) = ui.allocate_exact_size(vec2(width, ROW_H), Sense::hover());
let resp = ui.interact(r, card_id(&file.path), Sense::click());
a11y::button(&resp, &spoken(file, now));
a11y::named_button(&resp, &spoken(file, now));
let resp = resp.on_hover_text(&file.path);
if let Some(a) = interact(ui, &resp, recents, file) {
action = Some(a);
+64
View File
@@ -0,0 +1,64 @@
//! Recent file rows keep their full accessible name.
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use egui::{Context, Id, Pos2, RawInput, Rect, accesskit::Role, vec2};
use figcraft_engine::Session;
use figcraft_ui_egui::{FigApp, NoServices};
/// Render the home screen in `view` with `path` (and a single-space control) as recents and return
/// the row's (label, shortcut, focused name).
fn row(view: &str, path: &str, prefix: &str, exclude: &str) -> (String, Option<String>, String) {
let mut session = Session::empty();
session.prefs.recent_files = vec![path.into(), "Q3 report.figcraft".into()];
session.prefs.getting_started_dismissed = true;
session.prefs.home_views.insert("recents".into(), view.into());
let mut app = FigApp::new(session, Box::new(NoServices));
app.ui.home = true;
let ctx = Context::default();
ctx.enable_accesskit();
let mut found = None;
for frame in 0..4 {
if frame >= 2 {
ctx.memory_mut(|m| m.request_focus(Id::new(("home_recent_file", path))));
}
let mut out = ctx.run_ui(
RawInput {
screen_rect: Some(Rect::from_min_size(Pos2::ZERO, vec2(1440.0, 900.0))),
time: Some(frame as f64 / 60.0),
..Default::default()
},
|ui| app.ui(ui),
);
out.textures_delta.clear();
if frame == 3 {
let nodes = out.platform_output.accesskit_update.unwrap().nodes;
let mut matches = nodes.iter().map(|(_, n)| n).filter(|n| {
n.role() == Role::Button && n.label().is_some_and(|s| s.starts_with(prefix) && (exclude.is_empty() || !s.starts_with(exclude)))
});
let n = matches.next().expect("file row");
assert!(matches.next().is_none(), "{view}: more than one row matches {prefix:?}");
found = Some((
n.label().unwrap_or_default().to_string(),
n.keyboard_shortcut().map(str::to_string),
figcraft_ui_egui::a11y::focused_name(&ctx),
));
}
}
found.expect("rendered")
}
#[test]
fn recent_file_names_preserve_spaces() {
for view in ["grid", "list"] {
for (path, prefix, full) in [("Q3 report.figcraft", "Q3", "Q3 report"), ("Notes A.figcraft", "Notes", "Notes A")] {
// The single-space control "Q3 report" also starts with "Q3"; it is not the row under test.
let (label, shortcut, focused) = row(view, path, prefix, "Q3 report");
assert!(label.starts_with(full), "{view}: label {label:?}");
assert_eq!(shortcut, None, "{view}: {path}");
assert!(focused.starts_with(full), "{view}: focused {focused:?}");
}
let (label, shortcut, _) = row(view, "Q3 report.figcraft", "Q3 report", "");
assert!(label.starts_with("Q3 report"), "{view}: control {label:?}");
assert_eq!(shortcut, None);
}
}