Let the manual's scripts find a control by its name

Every scene in tools/manual aimed at window pixels written in by hand, so
a panel that gained a row moved every slider under it and the recording
went on dragging where the slider used to be. The develop column has
already moved that way (Compose now sits above Adjust), and nothing said.

A build with the `automation` feature listens on the Unix socket named
by DR_AUTOMATION and answers where an element is: by its accessible
label, the name a screen reader reads, or by its markup id for the few
things that are not controls (the canvas, the crop rectangle). It uses
Slint's element queries, which need the compiler's debug tables, so the
feature also turns those on in build.rs. It only answers questions; the
input is still xdotool's real pointer. No default build has the feature,
and one that has it listens only when the variable is set.

drive.py gains click-on, drag-on, hold-on, wait-for, wait-gone, labels
and ids. The grid's cells are now named by their file, each rating star
by its value, the sidebar's + as "New collection", and the Adjust
heading's reset as "Reset all adjustments" - controls a screen reader
could not reach before either.
This commit is contained in:
2026-09-25 07:26:36 -04:00
parent b480de5bff
commit a6ea6ba83f
12 changed files with 569 additions and 94 deletions
+6
View File
@@ -1285,6 +1285,12 @@ export component AdjustPanel inherits Rectangle {
width: 44px;
height: 20px;
clicked => { Adjustments.reset-all(); }
// Named apart from the groups' "Reset" buttons below it: this
// one empties the whole panel, and a reader moving through
// the column has to be able to tell the two apart.
accessible-role: button;
accessible-label: "Reset all adjustments";
accessible-action-default => { Adjustments.reset-all(); }
Label {
text: "reset";
emphasised: reset.has-hover;
+4
View File
@@ -579,6 +579,10 @@ export component CollectionsPanel inherits Rectangle {
: (add-touch.has-hover ? Theme.hover : transparent);
border-radius: Theme.radius-sm;
accessible-role: button;
accessible-label: "New collection";
accessible-action-default => { root.new-collection(); }
Icon {
name: "plus";
ink: Theme.ink-dim;
+23
View File
@@ -1210,6 +1210,21 @@ export component StarStrip inherits Rectangle {
width: root.star;
height: root.star;
// Each star is a button that sets its rating, and says so. The
// strip is only in the tree while it is drawn — on the cell under
// the pointer, or on a rated one — which is also when it can be
// pressed.
accessible-role: button;
accessible-label: n == 1 ? "1 star" : n + " stars";
accessible-enabled: root.interactive;
accessible-checkable: true;
accessible-checked: root.rating >= n;
accessible-action-default => {
if (root.interactive) {
root.rate(root.rating == n ? 0 : n);
}
}
Icon {
// Solid versus outline: the shape says it, not the colour.
name: root.rating >= n ? "star" : "star-outline";
@@ -3730,6 +3745,14 @@ export component LibraryGrid inherits Rectangle {
// photographs file in one gesture.
// manual: collections
for cell[i] in root.cells: DragArea {
// A cell is its photograph, named by its file: the name
// printed under the thumbnail, and the one a screen
// reader or the manual's recording scripts ask for.
accessible-role: list-item;
accessible-label: cell.name;
accessible-item-selectable: true;
accessible-item-selected: cell.selected;
// Cells are positioned at their **absolute** place in the
// library, not their index in the loaded window: the window
// starts at `offset`, so a cell drawn at window-index 0 belongs