Open Help from develop as well as from the grid

The "Controls and shortcuts" sheet was drawn by LibraryGrid, so only the
grid's Help button and its F1 could open it. Develop, where most of the
keys it lists are bound (Ctrl+E, Ctrl+Shift+C, A/D, Z, R, H, [ ]), had no
way to it: a photographer who wanted to look a shortcut up had to leave
the photograph they wanted it for.

The sheet now hangs off the shell beside the export and copy sheets, on a
`help-open` property both views set. The grid's Help button and F1 raise
it through a callback, and its keys stand down through the `sheet-open`
they already honour for the export sheet, so Escape falls through to the
shell, which closes it. Develop gains a Help button beside Settings in
its top bar, as in the library header, and F1 in its key scope; its keys
decline while the sheet is up, as they do for the other two sheets, so
nothing behind it is rated or stepped. The book already begins with the
Develop section, so from develop it opens where the reader wants it.

Tests hold the shape: the sheet is drawn by the shell and not the grid,
and develop's opening guard names all three sheets and its F1 opens this
one. The new GESTURE: block puts the develop route in the book.
This commit is contained in:
2026-09-25 23:24:52 -04:00
parent 19dd3257e3
commit d9f259656a
7 changed files with 224 additions and 127 deletions
+8
View File
@@ -56,6 +56,14 @@ pub const GESTURES: &[Gesture] = &[
keys: "",
manual: "local-adjustments",
},
Gesture {
title: "Open this list",
section: "Develop",
touch: "Press Help in the top bar, and Done to put it away",
pointer: "Press Help in the top bar, and Done to put it away",
keys: "F1; the key that closes any sheet puts it away",
manual: "",
},
Gesture {
title: "Take back the last change",
section: "Develop",
+49
View File
@@ -113,6 +113,55 @@ mod tests {
assert_eq!(rows.iter().filter(|r| !r.heading.is_empty()).count(), 1);
}
/// The develop view's key handler, from its `KEYMAP:` tag to the end of
/// the file — enough to hold its opening guard and its bindings.
fn develop_keys() -> &'static str {
let app = include_str!("../ui/app.slint");
let tag = "// KEYMAP: Develop\n key-pressed(event)";
let at = app
.find(tag)
.expect("app.slint no longer has develop's key-pressed handler");
&app[at..]
}
/// TRACES: FR-UI-4
/// The sheet is drawn by the shell, so both views can open it. Drawn
/// inside the grid, it was unreachable from develop — where most of the
/// keys it lists are bound.
#[test]
fn the_help_sheet_is_drawn_by_the_shell_and_not_by_the_grid() {
let app = include_str!("../ui/app.slint");
let library = include_str!("../ui/library.slint");
assert!(app.contains("if root.help-open: GestureSheet {"));
assert!(!library.contains("GestureSheet {"));
}
/// TRACES: FR-UI-4 | FR-UI-5
/// F1 opens it from develop, and develop's keys stand down while it is
/// up, as they do for the export and copy sheets — a key acting behind
/// the sheet would change a photograph the reader cannot see.
#[test]
fn develop_opens_help_on_f1_and_stands_down_behind_it() {
let keys = develop_keys();
let guard = keys
.find("return reject;")
.expect("develop's handler never declines");
let opening = &keys[..guard];
for sheet in ["export-sheet-open", "copy-sheet-open", "help-open"] {
assert!(
opening.contains(sheet),
"develop's keys do not stand down behind `{sheet}`"
);
}
let f1 = keys
.find("Keys.chord(event) == \"F1\"")
.expect("develop does not bind F1");
assert!(
keys[f1..].lines().nth(1).unwrap_or("").trim() == "root.help-open = true;",
"develop's F1 does not open the help sheet"
);
}
#[test]
fn nothing_in_makes_nothing_out() {
assert!(rows_from(&[]).is_empty());
+51 -7
View File
@@ -8,7 +8,7 @@ import { HistoryPanel, HistoryRow, Steps } from "history.slint";
import { LaunchScreen } from "launch.slint";
import { IdentityScreen, IdentityPerson, IdentityFace } from "identity.slint";
import { LibraryGrid, LibraryCell, TimelineBar, PhotoRoll, KeywordRow, PersonChip, Library } from "library.slint";
import { GestureRow } from "gestures.slint";
import { GestureRow, GestureSheet } from "gestures.slint";
import { Button, PanelHeading, Label, Value, Caption, Panel, EmptyState, ProgressBar, ActivityRow } from "widgets.slint";
import { CollectionsPanel, CollectionRow, Collections, OfflinePrompt, CollectionMenu,
MembershipSheet, MembershipRow } from "collections.slint";
@@ -654,6 +654,12 @@ export component AppWindow inherits Window {
/// The copy sheet: which kinds of edit a copy carries, and Copy. Ctrl+Shift+C.
in-out property <bool> copy-sheet-open: false;
/// TRACES: FR-UI-4 | FR-UI-5
/// The gesture reference, "Controls and shortcuts": Help, or F1. Over
/// the shell rather than inside the grid, because develop — where most
/// of the keys are — has to be able to open it too.
in-out property <bool> help-open: false;
/// TRACES: FR-EXP-7 | FR-UI-5
/// Whether the export sheet's button would do anything — the develop
/// view's photograph, or the grid's selection. One definition for the
@@ -1007,8 +1013,12 @@ in property <bool> panel-visible: true;
root.recovery-dismiss();
return accept;
}
// The two shortcut sheets, which are drawn over everything
// but the recovery question.
// The help sheet and the two shortcut sheets, which are drawn
// over everything but the recovery question.
if (root.help-open) {
root.help-open = false;
return accept;
}
if (root.export-sheet-open) {
root.export-sheet-open = false;
return accept;
@@ -1497,7 +1507,8 @@ in property <bool> panel-visible: true;
open-import() => { root.import-open(); }
open-settings() => { root.settings-open(); }
open-export-options() => { root.export-options-open(); }
sheet-open: root.export-sheet-open;
sheet-open: root.export-sheet-open || root.help-open;
open-help() => { root.help-open = true; }
open-people() => { root.identity-open(); }
cell-pressed(i, ctrl, shift) => {
@@ -1535,8 +1546,6 @@ in property <bool> panel-visible: true;
filter-people-all: Library.library-filter-people-all;
filter-eyes-open: Library.library-filter-eyes-open;
people: Library.library-people;
gestures: Library.library-gestures;
open-manual(anchor) => { Library.library-open-manual(anchor); }
filter-min-rating: Library.library-filter-min-rating;
filter-max-rating: Library.library-filter-max-rating;
filter-rating-range(low, high) => {
@@ -1632,6 +1641,7 @@ in property <bool> panel-visible: true;
back-to-library() => { root.back-to-library(); }
open-people() => { root.identity-open(); }
open-settings() => { root.settings-open(); }
open-help() => { root.help-open = true; }
toggle-panel() => { root.toggle-panel(); }
export-image() => { root.export-image(); }
undo() => { Steps.undo(); }
@@ -2291,9 +2301,31 @@ in property <bool> panel-visible: true;
// the next frame behind the export form. Declined
// rather than swallowed, so Escape still reaches
// the shell and closes the sheet.
if (root.export-sheet-open || root.copy-sheet-open) {
if (root.export-sheet-open || root.copy-sheet-open
|| root.help-open) {
return reject;
}
// GESTURE: Open this list
// where: Develop
// touch: Press Help in the top bar, and Done to
// put it away
// pointer: Press Help in the top bar, and Done to
// put it away
// keys: `F1`; the key that closes any sheet
// puts it away
// why: Most of the keys are develop's, and a
// reference that could only be opened
// from the grid had to be looked up
// before opening the photograph they
// were wanted for.
//
// TRACES: FR-UI-4 | FR-UI-5
// The same sheet the grid's Help opens, drawn by
// the shell; the book already starts on Develop.
if (Keys.chord(event) == "F1") {
root.help-open = true;
return accept;
}
// TRACES: FR-DEV-7
// Hold backslash to see the unedited original.
// Rust ignores a repeat that says what it already
@@ -3362,6 +3394,18 @@ in property <bool> panel-visible: true;
dismiss => { root.copy-sheet-open = false; }
}
// TRACES: FR-UI-2 | FR-UI-4
// The gesture reference. Above the other sheets, because it is the one
// a user opens *because* another one confused them; over the shell for
// their reason — the grid and develop both open it.
if root.help-open: GestureSheet {
width: 100%;
height: 100%;
rows: Library.library-gestures;
close => { root.help-open = false; }
open-manual(anchor) => { Library.library-open-manual(anchor); }
}
OfflinePrompt {
width: 100%;
height: 100%;
+14
View File
@@ -63,6 +63,9 @@ export component StatusBar inherits Rectangle {
callback back-to-library();
callback open-people();
callback open-settings();
/// TRACES: FR-UI-4
/// Open the gesture reference, which the window draws.
callback open-help();
callback toggle-panel();
callback export-image();
callback undo();
@@ -303,6 +306,17 @@ export component StatusBar inherits Rectangle {
clicked => { root.export-image(); }
}
// TRACES: FR-UI-4
// The pointer and touch way to the reference F1 opens. Beside
// Settings, as in the library header, because it is about the
// application rather than about the photograph — and in the same
// place on both screens, so it is found where it was last seen.
Button {
text: "Help";
y: (parent.height - self.height) / 2;
clicked => { root.open-help(); }
}
// Reachable from develop as well as from the grid: export defaults are
// most likely to be wanted with a finished photograph on screen, which
// is exactly where this bar is and the library header is not.
+8 -36
View File
@@ -15,7 +15,7 @@ import { Button, IconButton, Label, Value, Caption, EmptyState, FilterChip, Prog
// two lists of collections that could disagree about what exists is one list
// too many.
import { CollectionRow } from "collections.slint";
import { GestureSheet, GestureRow } from "gestures.slint";
import { GestureRow } from "gestures.slint";
import { Labels, LabelMark, LabelPicker } from "labels.slint";
// TRACES: FR-CAT-5
@@ -1661,14 +1661,10 @@ export component LibraryGrid inherits Rectangle {
callback open-settings();
callback open-people();
/// TRACES: FR-UI-4
/// The gesture reference's rows, from Rust — which reads them from the
/// generated table. See gestures.slint for why they cannot be written here.
in property <[GestureRow]> gestures;
/// Open the manual, at the section `anchor` names or at the top.
callback open-manual(string);
/// Whether the reference is up. Local, like `naming` and `filing`: nothing
/// in Rust needs to know a sheet is open.
property <bool> helping: false;
/// Open the gesture reference. The window draws it, as it draws the
/// export sheet, so develop can open the same one; while it is up
/// `sheet-open` is true and the keys here stand down.
callback open-help();
// --- selection and drag ---
//
@@ -2396,7 +2392,7 @@ export component LibraryGrid inherits Rectangle {
open-import => { root.open-import(); }
open-settings => { root.open-settings(); }
open-people => { root.open-people(); }
open-gestures => { root.helping = true; }
open-gestures => { root.open-help(); }
}
// Compact: one button in place of the row. Labelled rather
@@ -2452,7 +2448,7 @@ export component LibraryGrid inherits Rectangle {
open-import => { root.open-import(); }
open-settings => { root.open-settings(); }
open-people => { root.open-people(); }
open-gestures => { root.helping = true; }
open-gestures => { root.open-help(); }
}
}
}
@@ -3137,13 +3133,6 @@ export component LibraryGrid inherits Rectangle {
// button, and a sheet it walked straight past would leave
// the user out of the grid with their selection gone.
if (Keys.chord(event) == "Back" || Keys.chord(event) == "Escape") {
// The help sheet first: it is drawn over everything
// else here, and F1 opened it from the keyboard, so
// the keyboard has to be able to put it away.
if (root.helping) {
root.helping = false;
return accept;
}
if (root.keywording) {
root.keywording = false;
return accept;
@@ -3166,12 +3155,6 @@ export component LibraryGrid inherits Rectangle {
}
return reject;
}
// Nothing else acts behind the help sheet: a key that
// judged or keyworded the grid under it would change
// photographs the reader cannot see.
if (root.helping) {
return reject;
}
// GESTURE: Export the selection as the last export was
// where: Library grid
// touch: Select them, then Export in the selection bar
@@ -3403,7 +3386,7 @@ export component LibraryGrid inherits Rectangle {
// F1 opens the reference the Help button does — the help
// key everywhere else.
if (Keys.chord(event) == "F1") {
root.helping = true;
root.open-help();
return accept;
}
if (Keys.chord(event) == "F2") {
@@ -5232,17 +5215,6 @@ export component LibraryGrid inherits Rectangle {
}
}
// TRACES: FR-UI-2 | FR-UI-4
// The gesture reference. Last of the sheets, and above them all, because it
// is the one a user opens *because* another one confused them.
if root.helping: GestureSheet {
width: 100%;
height: 100%;
rows: root.gestures;
close => { root.helping = false; }
open-manual(anchor) => { root.open-manual(anchor); }
}
// --- the naming sheet (FR-CAT-5, FR-CAT-7) ------------------------------
//
// Why a sheet at all, rather than the sidebar's rename field: see `naming`