// TRACES: FR-UI-2 | FR-UI-4 // // The gesture reference, as a sheet. // // # Why the application carries one at all // // FR-UI-4: a gesture with no visible counterpart is a feature only its author // knows about. Most of the grid's vocabulary now has one — Select, "Select to…" // and Select all are buttons that say what they do — but that a *hold* does the // same thing faster, or that two fingers resize the thumbnails, could still only // be discovered by accident. The buttons make the gestures usable; this makes // them knowable. // // # Why the rows come from Rust and not from this file // // Every line here is generated from the comment beside the code that implements // the gesture (`gesture_book.rs`, and `tools/traceability` that writes it). A // sheet with the text typed into it would be a second description of one // behaviour, and the second description is always the one that goes stale: the // code is exercised whenever somebody uses the application, and the help screen // is exercised never. This file draws whatever it is handed and knows nothing // about what a gesture is. import { Theme } from "theme.slint"; import { Button, Caption } from "widgets.slint"; // One line of the sheet: a section heading, or a gesture and its routes. // // Flat, and not a list of lists, because a sheet whose sections scrolled // independently is not one list — it is several, in a box. `heading` non-empty // is what marks the two apart; Slint has no sum type to say it better. export struct GestureRow { heading: string, title: string, // Empty where the gesture has no counterpart in that modality. Drawn as // nothing at all rather than as an empty label, so a touch-only gesture // does not read as one whose pointer half is broken. touch: string, pointer: string, keys: string, } // One route: how a modality performs the gesture. component Route inherits HorizontalLayout { in property modality; in property how; spacing: Theme.gap-sm; Caption { text: root.modality; // A fixed column so the three routes line up down the sheet. Without // it "Touch", "Pointer" and "Keyboard" each set their own left edge for // the text beside them, and a list of forty reads as ragged prose. width: 64px; horizontal-alignment: right; } Text { text: root.how; color: Theme.ink; font-size: Theme.text-sm; wrap: word-wrap; horizontal-stretch: 1; } } export component GestureSheet inherits Rectangle { in property <[GestureRow]> rows; callback close(); background: #000000CC; // Swallows the taps that miss the card, and closes. First, so the card's // own controls sit above it — the same scrim, card and dismissal the // library's other sheets use, because a user who has filed a selection // already knows how this works. TouchArea { clicked => { root.close(); } } Rectangle { width: min(520px, parent.width - 2 * Theme.gap-lg); height: min(560px, parent.height - 2 * Theme.gap-lg); x: (parent.width - self.width) / 2; // Centred, unlike the naming sheet: nothing here takes the keyboard, so // there is no keyboard to sit above. y: (parent.height - self.height) / 2; background: Theme.surface; border-radius: Theme.radius; border-width: 1px; border-color: Theme.rule; // Stops a press on the card reaching the scrim behind it. TouchArea { } VerticalLayout { padding: Theme.gap-lg; spacing: Theme.gap; Text { text: "How to drive the grid"; color: Theme.ink; font-size: Theme.text-lg; font-weight: 600; } // The list scrolls; the title and the button do not, so a long // vocabulary never pushes the way out off the bottom of the card. Flickable { vertical-stretch: 1; viewport-width: self.width; viewport-height: list.preferred-height; list := VerticalLayout { width: parent.viewport-width; spacing: Theme.gap-sm; alignment: start; for r[i] in root.rows: VerticalLayout { spacing: 2px; // A heading and a gesture are the same row type, so // each half is drawn under its own condition rather // than by two repeaters over one filtered list — which // would be two passes that could disagree about order. // Space above a heading, and none above the first: // the gap is what separates a section from the one // before it, and there is nothing before the first. // // An empty Rectangle rather than a `y` offset on the // heading — a child of a layout may not set its own // `y`, because the layout is already setting it. if r.heading != "" && i > 0: Rectangle { height: Theme.gap; } if r.heading != "": Text { text: r.heading; color: Theme.ink-dim; font-size: Theme.text-sm; font-weight: 700; } if r.heading == "": Text { text: r.title; color: Theme.ink; font-size: Theme.text; font-weight: 600; wrap: word-wrap; } if r.touch != "": Route { modality: "Touch"; how: r.touch; } if r.pointer != "": Route { modality: "Pointer"; how: r.pointer; } if r.keys != "": Route { modality: "Keyboard"; how: r.keys; } } } } HorizontalLayout { alignment: end; Button { text: "Done"; primary: true; clicked => { root.close(); } } } } } }