Fail CI when a key is bound but not in the gesture book, or listed but not bound
The gesture book is generated from GESTURE tags, so it could not describe a gesture nobody tagged, but nothing made anyone tag one. The arrow keys, Enter, P, X, U, Delete, F1 and F2 all worked in the grid with no line in the help sheet, and a tag could name a key whose handler had gone. Key handlers now compare one canonical string, Keys.chord(event) == "Ctrl+Z", instead of reading event.text and the modifiers themselves. keys.slint folds the key and its modifiers into that spelling, so the literal in the handler is the whole binding and the checker reads exactly what the handler dispatches on. Each handler carries a KEYMAP comment naming the gesture-book section its keys belong to, and a tag's keys field names its keys between backticks. gestures-check now fails when a handler binds a key no tag in that section names, when a tag names a key no handler there binds, when any .slint file other than keys.slint reads event.text, when a compared literal is not canonical, and when keys.slint's named keys drift from the Rust list. Spellings are normalised in one place, chord.rs: Ctrl+z, Control+Z and LeftArrow all mean what the handler's "Ctrl+Z" and "Left" mean. Shift and Alt count only for letters and named keys, because on the French layout every digit needs shift and a 6 has to be a 6 however it was typed. A Rust keymap that both dispatched and was read by the generator was the alternative. It would have moved the handlers' decisions away from the Slint state they depend on, and a window that forgot to install it would have had no working keys at all. The keys that were already bound and undocumented are now tagged.
This commit is contained in:
@@ -72,6 +72,14 @@ pub const GESTURES: &[Gesture] = &[
|
||||
keys: "Ctrl+Shift+Z",
|
||||
manual: "history-snapshots-presets",
|
||||
},
|
||||
Gesture {
|
||||
title: "Remove a repair",
|
||||
section: "Develop",
|
||||
touch: "Tap it, then Delete Repair",
|
||||
pointer: "Click it, then Delete Repair",
|
||||
keys: "Delete or Backspace, while repairing",
|
||||
manual: "",
|
||||
},
|
||||
Gesture {
|
||||
title: "Copy the settings from this photograph",
|
||||
section: "Develop",
|
||||
@@ -141,7 +149,7 @@ pub const GESTURES: &[Gesture] = &[
|
||||
section: "Develop",
|
||||
touch: "Tap a frame in the roll along the foot of the canvas",
|
||||
pointer: "Click a frame in the roll",
|
||||
keys: "Right arrow, D or space for the next; left arrow or A for the one before",
|
||||
keys: "→, D or Space for the next; ← or A for the one before",
|
||||
manual: "moving-between-photographs",
|
||||
},
|
||||
Gesture {
|
||||
@@ -181,7 +189,7 @@ pub const GESTURES: &[Gesture] = &[
|
||||
section: "Develop",
|
||||
touch: "Tap the ring at the head of its row",
|
||||
pointer: "Click the ring at the head of its row",
|
||||
keys: "H, for the selected layer history step, unlike holding \"Before\" — the layer really is off until it is switched back on.",
|
||||
keys: "H, for the selected layer",
|
||||
manual: "local-adjustments",
|
||||
},
|
||||
Gesture {
|
||||
@@ -208,6 +216,14 @@ pub const GESTURES: &[Gesture] = &[
|
||||
keys: "",
|
||||
manual: "local-adjustments",
|
||||
},
|
||||
Gesture {
|
||||
title: "Close what is open, or go back a step",
|
||||
section: "Everywhere",
|
||||
touch: "The system Back gesture, or the Back button",
|
||||
pointer: "The Close or Back button on whatever is open",
|
||||
keys: "Escape, or Back where the device has one",
|
||||
manual: "",
|
||||
},
|
||||
Gesture {
|
||||
title: "Pick a collection up to rearrange the tree",
|
||||
section: "Collections sidebar",
|
||||
@@ -237,7 +253,7 @@ pub const GESTURES: &[Gesture] = &[
|
||||
section: "Collections sidebar",
|
||||
touch: "Hold the collection, then \"Rename\"",
|
||||
pointer: "Double-click its name, or right-click it and choose \"Rename\"",
|
||||
keys: "",
|
||||
keys: "Escape while typing abandons the new name",
|
||||
manual: "collections",
|
||||
},
|
||||
Gesture {
|
||||
@@ -293,7 +309,7 @@ pub const GESTURES: &[Gesture] = &[
|
||||
section: "Library grid",
|
||||
touch: "Press Done in the header",
|
||||
pointer: "Press Done in the header",
|
||||
keys: "Escape",
|
||||
keys: "Escape, or Back; an open sheet closes first",
|
||||
manual: "selecting-several",
|
||||
},
|
||||
Gesture {
|
||||
@@ -309,7 +325,7 @@ pub const GESTURES: &[Gesture] = &[
|
||||
section: "Library grid",
|
||||
touch: "While selecting, press \"Select to…\", then tap the last photograph of the run",
|
||||
pointer: "Shift-click the last photograph of the run",
|
||||
keys: "",
|
||||
keys: "Shift with any key that walks the grid — Shift+←, Shift+→, Shift+↑, Shift+↓, Shift+Page Up, Shift+Page Down, Shift+Home, Shift+End",
|
||||
manual: "selecting-several",
|
||||
},
|
||||
Gesture {
|
||||
@@ -357,7 +373,7 @@ pub const GESTURES: &[Gesture] = &[
|
||||
section: "Library grid",
|
||||
touch: "Tap a star chip in the filter bar",
|
||||
pointer: "Click a star chip in the filter bar",
|
||||
keys: "Hold F and tap a digit for exactly that many stars, or two digits for everything between them; tap F alone to show every rating again",
|
||||
keys: "Hold F and tap a digit, 0–5, for exactly that many stars, or two digits for everything between them; tap F alone to show every rating again",
|
||||
manual: "rating-and-flagging",
|
||||
},
|
||||
Gesture {
|
||||
@@ -368,6 +384,46 @@ pub const GESTURES: &[Gesture] = &[
|
||||
keys: "6 red, 7 yellow, 8 green, 9 blue, with the pointer over it or on the selection; the same key again takes the label off",
|
||||
manual: "rating-and-flagging",
|
||||
},
|
||||
Gesture {
|
||||
title: "Pick or reject a photograph",
|
||||
section: "Library grid",
|
||||
touch: "",
|
||||
pointer: "",
|
||||
keys: "P picks, X rejects and U takes the flag off, with the pointer over it or on the selection",
|
||||
manual: "",
|
||||
},
|
||||
Gesture {
|
||||
title: "Move photographs to the trash",
|
||||
section: "Library grid",
|
||||
touch: "Tap the bin at the start of a cell's stars",
|
||||
pointer: "Hover the cell and click the bin before its stars",
|
||||
keys: "Delete or Backspace, on the selection",
|
||||
manual: "",
|
||||
},
|
||||
Gesture {
|
||||
title: "Open this list",
|
||||
section: "Library grid",
|
||||
touch: "Press Help in the header",
|
||||
pointer: "Press Help in the header",
|
||||
keys: "F1",
|
||||
manual: "",
|
||||
},
|
||||
Gesture {
|
||||
title: "Rename the collection the grid is showing",
|
||||
section: "Library grid",
|
||||
touch: "Hold it in the sidebar, then \"Rename\"",
|
||||
pointer: "Double-click it in the sidebar",
|
||||
keys: "F2",
|
||||
manual: "",
|
||||
},
|
||||
Gesture {
|
||||
title: "Move through the grid",
|
||||
section: "Library grid",
|
||||
touch: "Scroll, and tap a photograph",
|
||||
pointer: "Scroll, and click a photograph",
|
||||
keys: "←, →, ↑ and ↓ move one photograph; Page Up and Page Down a screenful; Home and End to the first and the last",
|
||||
manual: "",
|
||||
},
|
||||
Gesture {
|
||||
title: "Resize the thumbnails",
|
||||
section: "Library grid",
|
||||
@@ -389,7 +445,7 @@ pub const GESTURES: &[Gesture] = &[
|
||||
section: "Library grid",
|
||||
touch: "Tap it — a single tap, any length",
|
||||
pointer: "Click it",
|
||||
keys: "",
|
||||
keys: "Enter, on the photograph the arrow keys have walked to",
|
||||
manual: "developing-a-photograph",
|
||||
},
|
||||
Gesture {
|
||||
@@ -397,7 +453,7 @@ pub const GESTURES: &[Gesture] = &[
|
||||
section: "Library grid",
|
||||
touch: "Tap a star on the cell",
|
||||
pointer: "Hover the cell, then click a star",
|
||||
keys: "0 to 5 with the pointer over it, or on the selection",
|
||||
keys: "0–5 with the pointer over it, or on the selection",
|
||||
manual: "rating-and-flagging",
|
||||
},
|
||||
Gesture {
|
||||
|
||||
+77
-76
@@ -22,6 +22,7 @@ import { ImportPage } from "import.slint";
|
||||
import { MergePage, MergeFrameRow } from "merge.slint";
|
||||
import { Capture, StatusBar, InfoPanel } from "develop.slint";
|
||||
import { ToolRail } from "toolrail.slint";
|
||||
import { Keys } from "keys.slint";
|
||||
|
||||
export { LibraryCell, TimelineBar, CollectionRow, ActivityRow, HistogramView, PersonChip, MergeFrameRow }
|
||||
export { GestureRow }
|
||||
@@ -912,11 +913,22 @@ in property <bool> panel-visible: true;
|
||||
// `init` runs before or after its children's, so on the wrong ordering
|
||||
// this would take focus back off the grid and silently break culling.
|
||||
|
||||
// GESTURE: Close what is open, or go back a step
|
||||
// where: Everywhere
|
||||
// touch: The system Back gesture, or the Back button
|
||||
// pointer: The Close or Back button on whatever is open
|
||||
// keys: `Escape`, or `Back` where the device has one
|
||||
// why: One key for "up one", innermost first: a question
|
||||
// before the sheet under it, a sheet before the view, a
|
||||
// view before the library. Nothing is left behind a
|
||||
// dialogue that the key walked straight past.
|
||||
//
|
||||
// KEYMAP: Everywhere
|
||||
key-pressed(event) => {
|
||||
// Escape alongside Back: the same "go up one" on a keyboard, and
|
||||
// the inner scopes that use it for something more specific — a
|
||||
// rename in the sidebar — see it first and accept it there.
|
||||
if (event.text == Key.Back || event.text == Key.Escape) {
|
||||
if (Keys.chord(event) == "Back" || Keys.chord(event) == "Escape") {
|
||||
// A dialogue takes "go up one" first, and it is the *only*
|
||||
// thing that goes up. Without this, Android's Back button
|
||||
// would leave the library from behind an open question — the
|
||||
@@ -2157,6 +2169,7 @@ in property <bool> panel-visible: true;
|
||||
// which reports no modifiers of its own. Tracked on
|
||||
// both edges so releasing it while the pointer is over
|
||||
// the canvas is noticed.
|
||||
// KEYMAP: Develop
|
||||
key-released(event) => {
|
||||
root.shift-held = event.modifiers.shift;
|
||||
// TRACES: FR-DEV-7
|
||||
@@ -2165,13 +2178,14 @@ in property <bool> panel-visible: true;
|
||||
// from the next press, or a photographer who holds
|
||||
// it and then reaches for a slider would be
|
||||
// adjusting an image they cannot see.
|
||||
if (event.text == "\\") {
|
||||
if (Keys.chord(event) == "\\") {
|
||||
root.compare-original(false);
|
||||
return accept;
|
||||
}
|
||||
return reject;
|
||||
}
|
||||
|
||||
// KEYMAP: Develop
|
||||
key-pressed(event) => {
|
||||
root.shift-held = event.modifiers.shift;
|
||||
// A sheet is up over the photograph, and these
|
||||
@@ -2187,7 +2201,7 @@ in property <bool> panel-visible: true;
|
||||
// Rust ignores a repeat that says what it already
|
||||
// knows, so the key repeating under a long look
|
||||
// costs nothing.
|
||||
if (event.text == "\\") {
|
||||
if (Keys.chord(event) == "\\") {
|
||||
root.compare-original(true);
|
||||
return accept;
|
||||
}
|
||||
@@ -2197,7 +2211,7 @@ in property <bool> panel-visible: true;
|
||||
// the History list
|
||||
// pointer: Click it, or press Undo in the
|
||||
// History header
|
||||
// keys: Ctrl+Z
|
||||
// keys: `Ctrl+Z`
|
||||
// why: A whole drag is one step, so undo
|
||||
// takes back a decision rather than a
|
||||
// frame of a gesture. The list is there
|
||||
@@ -2211,22 +2225,25 @@ in property <bool> panel-visible: true;
|
||||
// the History list
|
||||
// pointer: Click it, or press Redo in the
|
||||
// History header
|
||||
// keys: Ctrl+Shift+Z
|
||||
// keys: `Ctrl+Shift+Z`
|
||||
// manual: history-snapshots-presets
|
||||
//
|
||||
// Ctrl+Z and Ctrl+Shift+Z (FR-DEV-5). Both cases
|
||||
// of the letter, because the logical key that
|
||||
// reaches us carries the shift: holding it for
|
||||
// redo turns "z" into "Z".
|
||||
if (event.modifiers.control
|
||||
&& (event.text == "z" || event.text == "Z")) {
|
||||
if (event.modifiers.shift) {
|
||||
Steps.redo();
|
||||
} else {
|
||||
Steps.undo();
|
||||
}
|
||||
// Ctrl+Z and Ctrl+Shift+Z (FR-DEV-5).
|
||||
if (Keys.chord(event) == "Ctrl+Z") {
|
||||
Steps.undo();
|
||||
return accept;
|
||||
}
|
||||
if (Keys.chord(event) == "Ctrl+Shift+Z") {
|
||||
Steps.redo();
|
||||
return accept;
|
||||
}
|
||||
// GESTURE: Remove a repair
|
||||
// where: Develop
|
||||
// touch: Tap it, then Delete Repair
|
||||
// pointer: Click it, then Delete Repair
|
||||
// keys: `Delete` or `Backspace`, while
|
||||
// repairing
|
||||
//
|
||||
// TRACES: FR-DEV-8
|
||||
// Delete removes the selected repair, and only in
|
||||
// repair mode: the same key in the grid judges
|
||||
@@ -2234,8 +2251,8 @@ in property <bool> panel-visible: true;
|
||||
// depending on a mode the user has forgotten they
|
||||
// are in is how work disappears.
|
||||
if (Develop.repairing && Repair.selected != ""
|
||||
&& (event.text == Key.Delete
|
||||
|| event.text == Key.Backspace)) {
|
||||
&& (Keys.chord(event) == "Delete"
|
||||
|| Keys.chord(event) == "Backspace")) {
|
||||
Repair.removed(Repair.selected);
|
||||
return accept;
|
||||
}
|
||||
@@ -2243,7 +2260,7 @@ in property <bool> panel-visible: true;
|
||||
// where: Develop
|
||||
// touch: Press Copy in the top bar
|
||||
// pointer: Press Copy in the top bar
|
||||
// keys: Ctrl+C
|
||||
// keys: `Ctrl+C`
|
||||
// why: The button is the copy that has to
|
||||
// work: a tablet has no modifier key
|
||||
// to hold and no menu bar to hang the
|
||||
@@ -2256,7 +2273,7 @@ in property <bool> panel-visible: true;
|
||||
// where: Develop
|
||||
// touch: Press Paste in the top bar
|
||||
// pointer: Press Paste in the top bar
|
||||
// keys: Ctrl+V
|
||||
// keys: `Ctrl+V`
|
||||
// why: The button names what would be
|
||||
// pasted — "3 adjustments", and
|
||||
// whether the crop is coming with it —
|
||||
@@ -2274,7 +2291,7 @@ in property <bool> panel-visible: true;
|
||||
// where: Develop
|
||||
// touch: Open Presets and toggle the kinds
|
||||
// pointer: Open Presets and toggle the kinds
|
||||
// keys: Ctrl+Shift+C, which offers Copy
|
||||
// keys: `Ctrl+Shift+C`, which offers Copy
|
||||
// beside them
|
||||
// why: Lightroom's Copy Settings. Pasting a
|
||||
// look across a shoot usually means
|
||||
@@ -2283,20 +2300,15 @@ in property <bool> panel-visible: true;
|
||||
// to make at the moment of copying.
|
||||
// manual: copying-settings
|
||||
//
|
||||
// Shift first: the logical key is "C" under shift,
|
||||
// which the plain copy below would otherwise take.
|
||||
if (event.modifiers.control && event.modifiers.shift
|
||||
&& (event.text == "c" || event.text == "C")) {
|
||||
if (Keys.chord(event) == "Ctrl+Shift+C") {
|
||||
root.copy-sheet-open = true;
|
||||
return accept;
|
||||
}
|
||||
if (event.modifiers.control
|
||||
&& (event.text == "c" || event.text == "C")) {
|
||||
if (Keys.chord(event) == "Ctrl+C") {
|
||||
Transfer.copy();
|
||||
return accept;
|
||||
}
|
||||
if (event.modifiers.control && Transfer.armed
|
||||
&& (event.text == "v" || event.text == "V")) {
|
||||
if (Transfer.armed && Keys.chord(event) == "Ctrl+V") {
|
||||
Transfer.paste();
|
||||
return accept;
|
||||
}
|
||||
@@ -2304,7 +2316,7 @@ in property <bool> panel-visible: true;
|
||||
// where: Develop
|
||||
// touch: Press Export in the top bar
|
||||
// pointer: Press Export in the top bar
|
||||
// keys: Ctrl+Shift+E
|
||||
// keys: `Ctrl+Shift+E`
|
||||
// why: Every export runs on the defaults in
|
||||
// Settings, so "as the last one was" is
|
||||
// what the button already does. The
|
||||
@@ -2317,7 +2329,7 @@ in property <bool> panel-visible: true;
|
||||
// where: Develop
|
||||
// touch: Open Settings, then Export defaults
|
||||
// pointer: Open Settings, then Export defaults
|
||||
// keys: Ctrl+E
|
||||
// keys: `Ctrl+E`
|
||||
// why: The export sheet is the export
|
||||
// defaults alone with an Export button.
|
||||
// What is chosen there is kept, so it
|
||||
@@ -2326,17 +2338,15 @@ in property <bool> panel-visible: true;
|
||||
// manual: export
|
||||
//
|
||||
// TRACES: FR-UI-5 | FR-EXP-7
|
||||
// Both cases of the letter, as for Z: holding
|
||||
// shift turns "e" into "E". Rust turns away a
|
||||
// second export while one is running, so the
|
||||
// chord needs no busy guard of its own.
|
||||
if (event.modifiers.control
|
||||
&& (event.text == "e" || event.text == "E")) {
|
||||
if (event.modifiers.shift) {
|
||||
root.export-image();
|
||||
} else {
|
||||
root.export-options-open();
|
||||
}
|
||||
// Rust turns away a second export while one is
|
||||
// running, so the chord needs no busy guard of its
|
||||
// own.
|
||||
if (Keys.chord(event) == "Ctrl+Shift+E") {
|
||||
root.export-image();
|
||||
return accept;
|
||||
}
|
||||
if (Keys.chord(event) == "Ctrl+E") {
|
||||
root.export-options-open();
|
||||
return accept;
|
||||
}
|
||||
// GESTURE: Change which group of adjustments is on screen
|
||||
@@ -2344,7 +2354,7 @@ in property <bool> panel-visible: true;
|
||||
// touch: Tap a group in the rail down the left
|
||||
// pointer: Click a group in the strip above the
|
||||
// develop column
|
||||
// keys: [ and ] step through them, wrapping
|
||||
// keys: `[` and `]` step through them, wrapping
|
||||
// round through "everything"
|
||||
// why: The groups are whatever the operation
|
||||
// set declares itself to be about, so
|
||||
@@ -2360,7 +2370,7 @@ in property <bool> panel-visible: true;
|
||||
// panel starts, so a photographer stepping round
|
||||
// the groups arrives back at the whole set instead
|
||||
// of being trapped in the last one.
|
||||
if (event.text == "]") {
|
||||
if (Keys.chord(event) == "]") {
|
||||
Adjustments.tab-picked(
|
||||
(Adjustments.active-tab + 1
|
||||
>= Adjustments.tabs.length)
|
||||
@@ -2368,7 +2378,7 @@ in property <bool> panel-visible: true;
|
||||
: (Adjustments.active-tab + 1));
|
||||
return accept;
|
||||
}
|
||||
if (event.text == "[") {
|
||||
if (Keys.chord(event) == "[") {
|
||||
Adjustments.tab-picked(
|
||||
(Adjustments.active-tab - 1 < -1)
|
||||
? (Adjustments.tabs.length - 1)
|
||||
@@ -2380,7 +2390,7 @@ in property <bool> panel-visible: true;
|
||||
// touch: Double-tap the photograph
|
||||
// pointer: Double-click it, or press the zoom
|
||||
// readout floating over the canvas
|
||||
// keys: Z
|
||||
// keys: `Z`
|
||||
// why: Noise reduction and capture
|
||||
// sharpening are judgements about
|
||||
// single pixels, and a fitted view
|
||||
@@ -2401,21 +2411,17 @@ in property <bool> panel-visible: true;
|
||||
// TRACES: FR-UI-4 | FR-UI-5
|
||||
// Fit and 1:1 from the keyboard, so inspecting
|
||||
// forty portraits at the same eye is forty
|
||||
// keystrokes rather than forty pans. Bare, with
|
||||
// no modifier: the control-Z case above has
|
||||
// already returned, so there is nothing left for
|
||||
// this to shadow.
|
||||
if (event.text == "z" || event.text == "Z") {
|
||||
// keystrokes rather than forty pans.
|
||||
if (Keys.chord(event) == "Z") {
|
||||
root.inspect-toggled(-1, -1);
|
||||
return accept;
|
||||
}
|
||||
// TRACES: FR-DEV-16
|
||||
// R puts back the control last moved. Bare, like
|
||||
// Z: the modifier chords above have all returned.
|
||||
// Nothing has been moved on this photograph until
|
||||
// something has, so the key is silent until then
|
||||
// rather than resetting a control nobody named.
|
||||
if ((event.text == "r" || event.text == "R")
|
||||
// R puts back the control last moved. Nothing has
|
||||
// been moved on this photograph until something
|
||||
// has, so the key is silent until then rather than
|
||||
// resetting a control nobody named.
|
||||
if (Keys.chord(event) == "R"
|
||||
&& Adjustments.touched-op >= 0) {
|
||||
Adjustments.param-reset(
|
||||
Adjustments.touched-op,
|
||||
@@ -2427,7 +2433,7 @@ in property <bool> panel-visible: true;
|
||||
// ring at the head of its row, from the keyboard.
|
||||
// With nothing selected there is nothing to hide,
|
||||
// and Rust says so by doing nothing.
|
||||
if (event.text == "h" || event.text == "H") {
|
||||
if (Keys.chord(event) == "H") {
|
||||
Masking.selected-toggled();
|
||||
return accept;
|
||||
}
|
||||
@@ -2437,7 +2443,7 @@ in property <bool> panel-visible: true;
|
||||
// colour
|
||||
// pointer: Click Label in the top bar, then a
|
||||
// colour
|
||||
// keys: 6 red, 7 yellow, 8 green, 9 blue; the
|
||||
// keys: `6` red, `7` yellow, `8` green, `9` blue; the
|
||||
// same key again takes it off
|
||||
// why: The grid's keys, on the photograph
|
||||
// that is open, so labelling while
|
||||
@@ -2450,20 +2456,19 @@ in property <bool> panel-visible: true;
|
||||
// Only with a library: a file named on the command
|
||||
// line has no catalog to hold a label.
|
||||
if (Library.library-total > 0
|
||||
&& Library.library-roll-current >= 0
|
||||
&& !event.modifiers.control) {
|
||||
if (event.text == "6") { Library.library-cell-labelled(Library.library-roll-current, 1, true); return accept; }
|
||||
if (event.text == "7") { Library.library-cell-labelled(Library.library-roll-current, 2, true); return accept; }
|
||||
if (event.text == "8") { Library.library-cell-labelled(Library.library-roll-current, 3, true); return accept; }
|
||||
if (event.text == "9") { Library.library-cell-labelled(Library.library-roll-current, 4, true); return accept; }
|
||||
&& Library.library-roll-current >= 0) {
|
||||
if (Keys.chord(event) == "6") { Library.library-cell-labelled(Library.library-roll-current, 1, true); return accept; }
|
||||
if (Keys.chord(event) == "7") { Library.library-cell-labelled(Library.library-roll-current, 2, true); return accept; }
|
||||
if (Keys.chord(event) == "8") { Library.library-cell-labelled(Library.library-roll-current, 3, true); return accept; }
|
||||
if (Keys.chord(event) == "9") { Library.library-cell-labelled(Library.library-roll-current, 4, true); return accept; }
|
||||
}
|
||||
// GESTURE: Move to the next or previous photograph
|
||||
// where: Develop
|
||||
// touch: Tap a frame in the roll along the
|
||||
// foot of the canvas
|
||||
// pointer: Click a frame in the roll
|
||||
// keys: Right arrow, D or space for the
|
||||
// next; left arrow or A for the one
|
||||
// keys: `Right`, `D` or `Space` for the
|
||||
// next; `Left` or `A` for the one
|
||||
// before
|
||||
// why: The edit on screen is saved on the
|
||||
// way out, so stepping through a folder
|
||||
@@ -2471,18 +2476,14 @@ in property <bool> panel-visible: true;
|
||||
// to the grid and loses nothing.
|
||||
// A and D as well as the arrows, so the left hand
|
||||
// steps along the roll while the right stays on
|
||||
// the mouse. Unmodified only: Ctrl+D and Ctrl+A
|
||||
// are not this.
|
||||
// the mouse.
|
||||
// manual: moving-between-photographs
|
||||
if (event.text == Key.RightArrow || event.text == " "
|
||||
|| (!event.modifiers.control
|
||||
&& (event.text == "d" || event.text == "D"))) {
|
||||
if (Keys.chord(event) == "Right" || Keys.chord(event) == "Space"
|
||||
|| Keys.chord(event) == "D") {
|
||||
root.step-photo(1);
|
||||
return accept;
|
||||
}
|
||||
if (event.text == Key.LeftArrow
|
||||
|| (!event.modifiers.control
|
||||
&& (event.text == "a" || event.text == "A"))) {
|
||||
if (Keys.chord(event) == "Left" || Keys.chord(event) == "A") {
|
||||
root.step-photo(-1);
|
||||
return accept;
|
||||
}
|
||||
@@ -2595,7 +2596,7 @@ in property <bool> panel-visible: true;
|
||||
// where: Develop
|
||||
// touch: Press and hold "Before"
|
||||
// pointer: Press and hold "Before"
|
||||
// keys: Hold \
|
||||
// keys: Hold `\`
|
||||
// why: Held rather than toggled, and no split
|
||||
// screen: a split halves the working image
|
||||
// on the tablet the column was sized for,
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
// release rather than the drop being silently discarded after it.
|
||||
|
||||
import { Theme } from "theme.slint";
|
||||
import { Keys } from "keys.slint";
|
||||
import { Button, Icon } from "widgets.slint";
|
||||
|
||||
// One row of the collection tree.
|
||||
@@ -247,11 +248,12 @@ component TreeRow inherits Rectangle {
|
||||
|
||||
accepted => { root.rename-committed(self.text); }
|
||||
|
||||
// KEYMAP: Collections sidebar
|
||||
key-pressed(event) => {
|
||||
// Escape abandons. Handled here rather than on a
|
||||
// surrounding FocusScope, which would never see the key —
|
||||
// the input has focus and consumes it.
|
||||
if (event.text == Key.Escape) {
|
||||
if (Keys.chord(event) == "Escape") {
|
||||
root.rename-cancelled();
|
||||
return accept;
|
||||
}
|
||||
@@ -373,6 +375,7 @@ component TreeRow inherits Rectangle {
|
||||
// where: Collections sidebar
|
||||
// touch: Hold the collection, then "Rename"
|
||||
// pointer: Double-click its name, or right-click it and choose "Rename"
|
||||
// keys: `Escape` while typing abandons the new name
|
||||
// why: Double-click is what a file manager and a Lightroom panel use
|
||||
// for the same thing, so it needs no discovering — but nothing
|
||||
// on screen says so, which is what the menu item is for.
|
||||
|
||||
@@ -295,7 +295,7 @@ export component SliderTrack inherits Rectangle {
|
||||
// where: Develop
|
||||
// touch: Double-tap its track
|
||||
// pointer: Double-click its track, or right-click it
|
||||
// keys: R, for the control last moved
|
||||
// keys: `R`, for the control last moved
|
||||
// why: The column is 280px wide and the colour mixer alone
|
||||
// puts thirty-six of these in it, so a reset button per
|
||||
// row would be most of the width. Two ways in with a
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
// TRACES: FR-UI-5 | FR-DEV-16
|
||||
//
|
||||
// One name for every key press, and the only code that reads the raw key.
|
||||
//
|
||||
// A handler compares `Keys.chord(event)` against a canonical spelling —
|
||||
// "Ctrl+Shift+Z", "Shift+Left", "6", "Escape" — rather than reading
|
||||
// `event.text` and `event.modifiers` itself. Two reasons, one for each reader:
|
||||
//
|
||||
// **For the handler**, the literal is the whole binding. Undo used to be
|
||||
// `control && (text == "z" || text == "Z")` with a nested `if` on shift to
|
||||
// split it from redo; now it is "Ctrl+Z", and redo is "Ctrl+Shift+Z", and
|
||||
// neither can accidentally answer the other.
|
||||
//
|
||||
// **For the checker**, the literal is the whole binding too. `traces
|
||||
// gestures-check` reads every `Keys.chord(event) == "…"` in a key handler and
|
||||
// fails CI if the key is not named by a GESTURE tag for that place, or if a
|
||||
// tag names a key no handler binds (tools/traceability/src/keymap.rs). It can
|
||||
// only do that because nothing else reads `event.text` — which it also checks.
|
||||
//
|
||||
// The canonical form is defined in tools/traceability/src/chord.rs, and this
|
||||
// file must agree with it; the checker compares the names `named` can return
|
||||
// against the list there. The rules, briefly:
|
||||
//
|
||||
// - Modifiers in the order Ctrl, Alt, Shift, Meta, each only when held.
|
||||
// - A letter is upper case whatever its case arrived in — caps lock must not
|
||||
// stop `P` picking — and shift is read from the modifier.
|
||||
// - **Shift and Alt count only for letters and named keys.** For any other
|
||||
// character the layout has already spent them: on the French layout every
|
||||
// digit needs shift, and a 6 is a 6 however it was made.
|
||||
// - `+` is "Plus", because it is the separator.
|
||||
|
||||
export global Keys {
|
||||
// The canonical name of a key press. What every key handler compares.
|
||||
public pure function chord(event: KeyEvent) -> string {
|
||||
return self.name(event.text, event.modifiers.control, event.modifiers.alt,
|
||||
event.modifiers.shift, event.modifiers.meta);
|
||||
}
|
||||
|
||||
// The same, from its parts.
|
||||
public pure function name(text: string, control: bool, alt: bool, shift: bool,
|
||||
meta: bool) -> string {
|
||||
return (control ? "Ctrl+" : "")
|
||||
+ (alt && self.modifiable(text) ? "Alt+" : "")
|
||||
+ (shift && self.modifiable(text) ? "Shift+" : "")
|
||||
+ (meta ? "Meta+" : "")
|
||||
+ self.base(text);
|
||||
}
|
||||
|
||||
// Whether shift and alt are part of the chord rather than of the character.
|
||||
pure function modifiable(text: string) -> bool {
|
||||
return self.named(text) != "" || text.to-uppercase() != text.to-lowercase();
|
||||
}
|
||||
|
||||
pure function base(text: string) -> string {
|
||||
if (self.named(text) != "") { return self.named(text); }
|
||||
if (text == "+") { return "Plus"; }
|
||||
return text.to-uppercase();
|
||||
}
|
||||
|
||||
// Keys with a name rather than a character. Mirrors `NAMED` in chord.rs.
|
||||
pure function named(text: string) -> string {
|
||||
if (text == Key.LeftArrow) { return "Left"; }
|
||||
if (text == Key.RightArrow) { return "Right"; }
|
||||
if (text == Key.UpArrow) { return "Up"; }
|
||||
if (text == Key.DownArrow) { return "Down"; }
|
||||
if (text == Key.Home) { return "Home"; }
|
||||
if (text == Key.End) { return "End"; }
|
||||
if (text == Key.PageUp) { return "PageUp"; }
|
||||
if (text == Key.PageDown) { return "PageDown"; }
|
||||
if (text == Key.Return) { return "Enter"; }
|
||||
if (text == Key.Escape) { return "Escape"; }
|
||||
if (text == Key.Back) { return "Back"; }
|
||||
if (text == Key.Tab || text == Key.Backtab) { return "Tab"; }
|
||||
if (text == Key.Backspace) { return "Backspace"; }
|
||||
if (text == Key.Delete) { return "Delete"; }
|
||||
if (text == Key.Insert) { return "Insert"; }
|
||||
if (text == Key.Space) { return "Space"; }
|
||||
if (text == Key.F1) { return "F1"; }
|
||||
if (text == Key.F2) { return "F2"; }
|
||||
if (text == Key.F3) { return "F3"; }
|
||||
if (text == Key.F4) { return "F4"; }
|
||||
if (text == Key.F5) { return "F5"; }
|
||||
if (text == Key.F6) { return "F6"; }
|
||||
if (text == Key.F7) { return "F7"; }
|
||||
if (text == Key.F8) { return "F8"; }
|
||||
if (text == Key.F9) { return "F9"; }
|
||||
if (text == Key.F10) { return "F10"; }
|
||||
if (text == Key.F11) { return "F11"; }
|
||||
if (text == Key.F12) { return "F12"; }
|
||||
return "";
|
||||
}
|
||||
}
|
||||
+105
-56
@@ -9,6 +9,7 @@
|
||||
// must not look identical (FR-NC-6c).
|
||||
|
||||
import { Theme } from "theme.slint";
|
||||
import { Keys } from "keys.slint";
|
||||
import { Button, IconButton, Label, Value, Caption, EmptyState, FilterChip, ProgressBar, Icon, Field } from "widgets.slint";
|
||||
// The filing sheet lists the same rows the sidebar draws, from the same model:
|
||||
// two lists of collections that could disagree about what exists is one list
|
||||
@@ -1649,7 +1650,7 @@ export component LibraryGrid inherits Rectangle {
|
||||
// where: Library grid
|
||||
// touch: Press Done in the header
|
||||
// pointer: Press Done in the header
|
||||
// keys: Escape
|
||||
// keys: `Escape`, or `Back`; an open sheet closes first
|
||||
// manual: selecting-several
|
||||
in property <bool> select-mode: false;
|
||||
callback toggle-select-mode();
|
||||
@@ -1747,6 +1748,9 @@ export component LibraryGrid inherits Rectangle {
|
||||
// touch: While selecting, press "Select to…", then tap the last
|
||||
// photograph of the run
|
||||
// pointer: Shift-click the last photograph of the run
|
||||
// keys: Shift with any key that walks the grid — `Shift+Left`,
|
||||
// `Shift+Right`, `Shift+Up`, `Shift+Down`, `Shift+PageUp`,
|
||||
// `Shift+PageDown`, `Shift+Home`, `Shift+End`
|
||||
// why: This replaced a double tap, which had no visible state and
|
||||
// could take forty photographs by accident. The run is resolved
|
||||
// by the catalog rather than by what is on screen, so the grid
|
||||
@@ -3024,10 +3028,11 @@ export component LibraryGrid inherits Rectangle {
|
||||
property <int> f-first: -1;
|
||||
property <bool> f-used: false;
|
||||
|
||||
// KEYMAP: Library grid
|
||||
key-released(event) => {
|
||||
// Tapping F on its own clears the star filter; having
|
||||
// tapped digits under it, letting go only ends the chord.
|
||||
if (self.f-held && (event.text == "f" || event.text == "F")) {
|
||||
if (self.f-held && Keys.chord(event) == "F") {
|
||||
if (!self.f-used) {
|
||||
root.filter-rating-range(0, 5);
|
||||
}
|
||||
@@ -3043,6 +3048,7 @@ export component LibraryGrid inherits Rectangle {
|
||||
// the first keystroke of a culling session is swallowed.
|
||||
init => { self.focus(); }
|
||||
|
||||
// KEYMAP: Library grid
|
||||
key-pressed(event) => {
|
||||
// Declined, not swallowed, so Escape reaches the shell
|
||||
// and closes the sheet rather than judging under it.
|
||||
@@ -3055,7 +3061,7 @@ export component LibraryGrid inherits Rectangle {
|
||||
// "leave the library". On Android that is the system Back
|
||||
// button, and a sheet it walked straight past would leave
|
||||
// the user out of the grid with their selection gone.
|
||||
if (event.text == Key.Back || event.text == Key.Escape) {
|
||||
if (Keys.chord(event) == "Back" || Keys.chord(event) == "Escape") {
|
||||
if (root.keywording) {
|
||||
root.keywording = false;
|
||||
return accept;
|
||||
@@ -3078,7 +3084,7 @@ export component LibraryGrid inherits Rectangle {
|
||||
// where: Library grid
|
||||
// touch: Select them, then Export in the selection bar
|
||||
// pointer: Select them, then Export in the selection bar
|
||||
// keys: Ctrl+Shift+E, or Ctrl+E to see the export
|
||||
// keys: `Ctrl+Shift+E`, or `Ctrl+E` to see the export
|
||||
// settings first
|
||||
// why: Lightroom's and darktable's chords. Every
|
||||
// export runs on the saved defaults, so the
|
||||
@@ -3090,13 +3096,12 @@ export component LibraryGrid inherits Rectangle {
|
||||
// TRACES: FR-EXP-7 | FR-UI-5
|
||||
// Before the bare keys below: a chord must not also be read
|
||||
// as the letter it is spelled with.
|
||||
if (event.modifiers.control
|
||||
&& (event.text == "e" || event.text == "E")) {
|
||||
if (event.modifiers.shift) {
|
||||
root.export-selection();
|
||||
} else {
|
||||
root.open-export-options();
|
||||
}
|
||||
if (Keys.chord(event) == "Ctrl+Shift+E") {
|
||||
root.export-selection();
|
||||
return accept;
|
||||
}
|
||||
if (Keys.chord(event) == "Ctrl+E") {
|
||||
root.open-export-options();
|
||||
return accept;
|
||||
}
|
||||
// GESTURE: Paste copied settings onto the selection
|
||||
@@ -3104,21 +3109,19 @@ export component LibraryGrid inherits Rectangle {
|
||||
// touch: Select them, then "Paste to N" in the
|
||||
// selection bar
|
||||
// pointer: Select them, then "Paste to N"
|
||||
// keys: Ctrl+V
|
||||
// keys: `Ctrl+V`
|
||||
// manual: copying-settings
|
||||
//
|
||||
// TRACES: FR-DEV-6 | FR-UI-5
|
||||
// Silent with nothing copied, as the button is disabled.
|
||||
if (event.modifiers.control && root.settings-armed
|
||||
&& (event.text == "v" || event.text == "V")) {
|
||||
if (root.settings-armed && Keys.chord(event) == "Ctrl+V") {
|
||||
root.paste-settings-to-selection();
|
||||
return accept;
|
||||
}
|
||||
// TRACES: FR-CAT-5 | FR-UI-5
|
||||
// Ctrl+A takes everything the grid shows, as "Select all"
|
||||
// in the selection strip does.
|
||||
if (event.modifiers.control
|
||||
&& (event.text == "a" || event.text == "A")) {
|
||||
if (Keys.chord(event) == "Ctrl+A") {
|
||||
root.select-all();
|
||||
return accept;
|
||||
}
|
||||
@@ -3126,9 +3129,10 @@ export component LibraryGrid inherits Rectangle {
|
||||
// where: Library grid
|
||||
// touch: Tap a star chip in the filter bar
|
||||
// pointer: Click a star chip in the filter bar
|
||||
// keys: Hold F and tap a digit for exactly that many
|
||||
// stars, or two digits for everything between
|
||||
// them; tap F alone to show every rating again
|
||||
// keys: Hold `F` and tap a digit, `0`–`5`, for exactly
|
||||
// that many stars, or two digits for everything
|
||||
// between them; tap `F` alone to show every
|
||||
// rating again
|
||||
// why: The chips say "this many or more". A range
|
||||
// with a ceiling — the twos and threes still
|
||||
// to be decided — is the keyboard's alone, and
|
||||
@@ -3140,8 +3144,7 @@ export component LibraryGrid inherits Rectangle {
|
||||
// digit filters as it lands. Any other key ends the chord,
|
||||
// in case its release went to a window that had focus by
|
||||
// then and the grid never heard it.
|
||||
if (!event.modifiers.control
|
||||
&& (event.text == "f" || event.text == "F")) {
|
||||
if (Keys.chord(event) == "F") {
|
||||
// Key repeat while held says nothing new.
|
||||
if (!self.f-held) {
|
||||
self.f-held = true;
|
||||
@@ -3151,34 +3154,34 @@ export component LibraryGrid inherits Rectangle {
|
||||
return accept;
|
||||
}
|
||||
if (self.f-held) {
|
||||
if (event.text == "0" || event.text == "1"
|
||||
|| event.text == "2" || event.text == "3"
|
||||
|| event.text == "4" || event.text == "5") {
|
||||
if (Keys.chord(event) == "0" || Keys.chord(event) == "1"
|
||||
|| Keys.chord(event) == "2" || Keys.chord(event) == "3"
|
||||
|| Keys.chord(event) == "4" || Keys.chord(event) == "5") {
|
||||
if (self.f-first < 0) {
|
||||
self.f-first = event.text.to-float();
|
||||
self.f-first = Keys.chord(event).to-float();
|
||||
root.filter-rating-range(self.f-first, self.f-first);
|
||||
} else {
|
||||
root.filter-rating-range(self.f-first,
|
||||
event.text.to-float());
|
||||
Keys.chord(event).to-float());
|
||||
}
|
||||
self.f-used = true;
|
||||
return accept;
|
||||
}
|
||||
self.f-held = false;
|
||||
}
|
||||
if (event.text == "0") { root.judged(0, -1, root.hover-ordinal); return accept; }
|
||||
if (event.text == "1") { root.judged(1, -1, root.hover-ordinal); return accept; }
|
||||
if (event.text == "2") { root.judged(2, -1, root.hover-ordinal); return accept; }
|
||||
if (event.text == "3") { root.judged(3, -1, root.hover-ordinal); return accept; }
|
||||
if (event.text == "4") { root.judged(4, -1, root.hover-ordinal); return accept; }
|
||||
if (event.text == "5") { root.judged(5, -1, root.hover-ordinal); return accept; }
|
||||
if (Keys.chord(event) == "0") { root.judged(0, -1, root.hover-ordinal); return accept; }
|
||||
if (Keys.chord(event) == "1") { root.judged(1, -1, root.hover-ordinal); return accept; }
|
||||
if (Keys.chord(event) == "2") { root.judged(2, -1, root.hover-ordinal); return accept; }
|
||||
if (Keys.chord(event) == "3") { root.judged(3, -1, root.hover-ordinal); return accept; }
|
||||
if (Keys.chord(event) == "4") { root.judged(4, -1, root.hover-ordinal); return accept; }
|
||||
if (Keys.chord(event) == "5") { root.judged(5, -1, root.hover-ordinal); return accept; }
|
||||
// GESTURE: Give photographs a colour label
|
||||
// where: Library grid
|
||||
// touch: Select them, then Label in the selection bar
|
||||
// and tap a colour
|
||||
// pointer: Select them, then Label in the selection bar
|
||||
// and click a colour
|
||||
// keys: 6 red, 7 yellow, 8 green, 9 blue, with the
|
||||
// keys: `6` red, `7` yellow, `8` green, `9` blue, with the
|
||||
// pointer over it or on the selection; the
|
||||
// same key again takes the label off
|
||||
// why: Lightroom's keys, so hands that learned them
|
||||
@@ -3192,30 +3195,51 @@ export component LibraryGrid inherits Rectangle {
|
||||
// TRACES: FR-CAT-5 | FR-UI-5 | NFR-A11Y-3
|
||||
// After the F chord above, which has returned for its own
|
||||
// digits (0-5 only), so these never filter.
|
||||
if (event.text == "6") { root.labelled(1, root.hover-ordinal); return accept; }
|
||||
if (event.text == "7") { root.labelled(2, root.hover-ordinal); return accept; }
|
||||
if (event.text == "8") { root.labelled(3, root.hover-ordinal); return accept; }
|
||||
if (event.text == "9") { root.labelled(4, root.hover-ordinal); return accept; }
|
||||
// Case-insensitive: caps lock during a long cull must not
|
||||
// silently stop the keys working.
|
||||
if (event.text == "p" || event.text == "P") {
|
||||
if (Keys.chord(event) == "6") { root.labelled(1, root.hover-ordinal); return accept; }
|
||||
if (Keys.chord(event) == "7") { root.labelled(2, root.hover-ordinal); return accept; }
|
||||
if (Keys.chord(event) == "8") { root.labelled(3, root.hover-ordinal); return accept; }
|
||||
if (Keys.chord(event) == "9") { root.labelled(4, root.hover-ordinal); return accept; }
|
||||
// GESTURE: Pick or reject a photograph
|
||||
// where: Library grid
|
||||
// keys: `P` picks, `X` rejects and `U` takes the flag
|
||||
// off, with the pointer over it or on the
|
||||
// selection
|
||||
// why: The keys every culling tool uses, so muscle
|
||||
// memory built elsewhere works here.
|
||||
//
|
||||
// Case-insensitive, because `Keys.chord` is: caps lock
|
||||
// during a long cull must not silently stop the keys
|
||||
// working.
|
||||
if (Keys.chord(event) == "P") {
|
||||
root.judged(-1, 1, root.hover-ordinal);
|
||||
return accept;
|
||||
}
|
||||
if (event.text == "x" || event.text == "X") {
|
||||
if (Keys.chord(event) == "X") {
|
||||
root.judged(-1, 2, root.hover-ordinal);
|
||||
return accept;
|
||||
}
|
||||
if (event.text == "u" || event.text == "U") {
|
||||
if (Keys.chord(event) == "U") {
|
||||
root.judged(-1, 0, root.hover-ordinal);
|
||||
return accept;
|
||||
}
|
||||
// GESTURE: Move photographs to the trash
|
||||
// where: Library grid
|
||||
// touch: Tap the bin at the start of a cell's stars
|
||||
// pointer: Hover the cell and click the bin before its
|
||||
// stars
|
||||
// keys: `Delete` or `Backspace`, on the selection
|
||||
// why: The bin acts on one photograph, so a stray
|
||||
// click cannot trash a selection; the key acts
|
||||
// on the selection because that is what every
|
||||
// file manager's Delete does. Both are undone
|
||||
// from the trash view.
|
||||
//
|
||||
// Delete moves the selection to the trash folder on the
|
||||
// server. Unlike every other key here it is not metadata —
|
||||
// it relocates files — but it is also the key every file
|
||||
// manager binds to exactly this, and the operation is
|
||||
// reversible from the trash view.
|
||||
if (event.text == Key.Delete || event.text == Key.Backspace) {
|
||||
if (Keys.chord(event) == "Delete" || Keys.chord(event) == "Backspace") {
|
||||
root.trash-selection();
|
||||
return accept;
|
||||
}
|
||||
@@ -3223,20 +3247,43 @@ export component LibraryGrid inherits Rectangle {
|
||||
// rename key everywhere else, and the reason it is bound
|
||||
// here is that this scope is what holds focus in library
|
||||
// mode. Rust ignores it when nothing is scoped.
|
||||
// GESTURE: Open this list
|
||||
// where: Library grid
|
||||
// touch: Press Help in the header
|
||||
// pointer: Press Help in the header
|
||||
// keys: `F1`
|
||||
//
|
||||
// GESTURE: Rename the collection the grid is showing
|
||||
// where: Library grid
|
||||
// touch: Hold it in the sidebar, then "Rename"
|
||||
// pointer: Double-click it in the sidebar
|
||||
// keys: `F2`
|
||||
//
|
||||
// TRACES: FR-UI-4 | FR-UI-5
|
||||
// F1 opens the reference the Help button does — the help
|
||||
// key everywhere else.
|
||||
if (event.text == Key.F1) {
|
||||
if (Keys.chord(event) == "F1") {
|
||||
root.helping = true;
|
||||
return accept;
|
||||
}
|
||||
if (event.text == Key.F2) {
|
||||
if (Keys.chord(event) == "F2") {
|
||||
root.rename-scope();
|
||||
return accept;
|
||||
}
|
||||
|
||||
// --- walking the grid ---------------------------------
|
||||
//
|
||||
// GESTURE: Move through the grid
|
||||
// where: Library grid
|
||||
// touch: Scroll, and tap a photograph
|
||||
// pointer: Scroll, and click a photograph
|
||||
// keys: `Left`, `Right`, `Up` and `Down` move one
|
||||
// photograph; `PageUp` and `PageDown` a
|
||||
// screenful; `Home` and `End` to the first and
|
||||
// the last
|
||||
// why: The cursor selects what it lands on, so
|
||||
// walking and judging are one hand's work.
|
||||
//
|
||||
// The keys that make a cull possible without the mouse:
|
||||
// arrows move the cursor, shift extends the selection from
|
||||
// the anchor, `Return` opens what the cursor is on. The
|
||||
@@ -3252,28 +3299,28 @@ export component LibraryGrid inherits Rectangle {
|
||||
// The vertical steps are expressed in columns and rows,
|
||||
// which only the grid knows: how far "down" is depends on
|
||||
// how wide the window happens to be.
|
||||
if (event.text == Key.LeftArrow) {
|
||||
if (Keys.chord(event) == "Left" || Keys.chord(event) == "Shift+Left") {
|
||||
root.move-cursor(-1, event.modifiers.shift);
|
||||
return accept;
|
||||
}
|
||||
if (event.text == Key.RightArrow) {
|
||||
if (Keys.chord(event) == "Right" || Keys.chord(event) == "Shift+Right") {
|
||||
root.move-cursor(1, event.modifiers.shift);
|
||||
return accept;
|
||||
}
|
||||
if (event.text == Key.UpArrow) {
|
||||
if (Keys.chord(event) == "Up" || Keys.chord(event) == "Shift+Up") {
|
||||
root.move-cursor(-root.columns, event.modifiers.shift);
|
||||
return accept;
|
||||
}
|
||||
if (event.text == Key.DownArrow) {
|
||||
if (Keys.chord(event) == "Down" || Keys.chord(event) == "Shift+Down") {
|
||||
root.move-cursor(root.columns, event.modifiers.shift);
|
||||
return accept;
|
||||
}
|
||||
if (event.text == Key.PageUp) {
|
||||
if (Keys.chord(event) == "PageUp" || Keys.chord(event) == "Shift+PageUp") {
|
||||
root.move-cursor(-root.columns * root.visible-rows,
|
||||
event.modifiers.shift);
|
||||
return accept;
|
||||
}
|
||||
if (event.text == Key.PageDown) {
|
||||
if (Keys.chord(event) == "PageDown" || Keys.chord(event) == "Shift+PageDown") {
|
||||
root.move-cursor(root.columns * root.visible-rows,
|
||||
event.modifiers.shift);
|
||||
return accept;
|
||||
@@ -3281,15 +3328,15 @@ export component LibraryGrid inherits Rectangle {
|
||||
// A step longer than the library, clamped at the far end.
|
||||
// `total` is what the grid was told the library holds, so
|
||||
// this stays honest as it grows.
|
||||
if (event.text == Key.Home) {
|
||||
if (Keys.chord(event) == "Home" || Keys.chord(event) == "Shift+Home") {
|
||||
root.move-cursor(-root.total, event.modifiers.shift);
|
||||
return accept;
|
||||
}
|
||||
if (event.text == Key.End) {
|
||||
if (Keys.chord(event) == "End" || Keys.chord(event) == "Shift+End") {
|
||||
root.move-cursor(root.total, event.modifiers.shift);
|
||||
return accept;
|
||||
}
|
||||
if (event.text == Key.Return) {
|
||||
if (Keys.chord(event) == "Enter") {
|
||||
root.open-cursor();
|
||||
return accept;
|
||||
}
|
||||
@@ -3852,6 +3899,8 @@ export component LibraryGrid inherits Rectangle {
|
||||
// where: Library grid
|
||||
// touch: Tap it — a single tap, any length
|
||||
// pointer: Click it
|
||||
// keys: `Enter`, on the photograph the arrow
|
||||
// keys have walked to
|
||||
// why: A tap opens; a tap that *moved* does not.
|
||||
// Travel is what separates a deliberate tap
|
||||
// from a hand brushing past, and it is the
|
||||
@@ -3973,7 +4022,7 @@ export component LibraryGrid inherits Rectangle {
|
||||
// where: Library grid
|
||||
// touch: Tap a star on the cell
|
||||
// pointer: Hover the cell, then click a star
|
||||
// keys: 0 to 5 with the pointer over it, or
|
||||
// keys: `0`–`5` with the pointer over it, or
|
||||
// on the selection
|
||||
// why: A star has to take the press without it
|
||||
// also reaching the cell, or every rating
|
||||
@@ -4801,7 +4850,7 @@ export component LibraryGrid inherits Rectangle {
|
||||
// where: Library grid
|
||||
// touch: While selecting, press "Select all"
|
||||
// pointer: While selecting, press "Select all"
|
||||
// keys: Ctrl+A
|
||||
// keys: `Ctrl+A`
|
||||
// why: A scoped grid of two hundred frames is two hundred
|
||||
// taps otherwise, and "all of them, except those
|
||||
// three" is a far more common shape than the taps it
|
||||
|
||||
@@ -214,11 +214,10 @@ component MaskEntry inherits Rectangle {
|
||||
// where: Develop
|
||||
// touch: Tap the ring at the head of its row
|
||||
// pointer: Click the ring at the head of its row
|
||||
// keys: H, for the selected layer
|
||||
// keys: `H`, for the selected layer
|
||||
// why: Disabling a layer is the before-and-after a local
|
||||
// edit constantly wants, so it is one press away rather
|
||||
// than inside the row. It is an edit and does take a
|
||||
// keys: H, for the selected layer
|
||||
// history step, unlike holding "Before" — the layer
|
||||
// really is off until it is switched back on.
|
||||
// manual: local-adjustments
|
||||
|
||||
Reference in New Issue
Block a user