// TRACES: FR-CAT-5 | NFR-A11Y-3 // Colour labels: the mark that shows one, and the row that chooses one. // // **A label is named, not only tinted.** NFR-A11Y-3 names colour labels as // the first thing that must not be told apart by hue alone, and a colour // label is the one status here whose *name* is a colour — so the temptation // is exactly a swatch. Every mark therefore carries the label's initial // (R, Y, G, B, P) in dark ink on the fill, and every place a label can be // chosen or filtered on says its name in words. Red and green are the pair // the commonest colour-vision deficiency merges, and they are "R" and "G" // here before they are anything else. // // The integers are `versions.label`'s own codes (`dr_catalog::rating:: // label_code`): 0 for none, 1 red, 2 yellow, 3 green, 4 blue, 5 purple. One // numbering from the column to the pixel, so nothing translates between them. import { Theme } from "theme.slint"; /// TRACES: FR-CAT-5 | NFR-A11Y-3 /// What each label code is called, and the letter its mark carries. export global Labels { /// Indexed by code. "None" at 0 is what the picker's clearing choice says. out property <[string]> names: ["None", "Red", "Yellow", "Green", "Blue", "Purple"]; /// The glyph on the mark, indexed by code. Distinct for every label: this /// is what carries the label for an eye that does not separate the fills. out property <[string]> letters: ["", "R", "Y", "G", "B", "P"]; /// The fill for a code. Data rather than chrome, the same exception the /// histogram's traces take (see `style.yaml`): the label *is* a colour. public pure function fill(code: int) -> color { if (code == 1) { return Theme.label-red; } if (code == 2) { return Theme.label-yellow; } if (code == 3) { return Theme.label-green; } if (code == 4) { return Theme.label-blue; } if (code == 5) { return Theme.label-purple; } return transparent; } } /// TRACES: FR-CAT-5 | NFR-A11Y-3 /// One label, drawn: a rounded tag in the label's colour with its initial /// on it. Invisible for code 0, so a caller can place it unconditionally. export component LabelMark inherits Rectangle { in property code: 0; in property size: 16px; accessible-role: text; accessible-label: Labels.names[root.code] + " label"; visible: root.code > 0 && root.code <= 5; width: root.size; height: root.size; border-radius: root.size / 4; background: Labels.fill(root.code); // A rim in the ground colour, so a yellow tag on a pale sky still has an // edge. border-width: 1px; border-color: Theme.ground; Text { // The letter is the label. Dark ink on every fill: the fills are held // to mid-tones in `style.yaml` so one ink reads on all five. text: Labels.letters[root.code]; color: Theme.ground; font-size: root.size * 0.62; font-weight: 800; width: 100%; height: 100%; horizontal-alignment: center; vertical-alignment: center; } } /// TRACES: FR-CAT-5 | FR-UI-4 | NFR-A11Y-3 /// The six choices — none, then the five labels — each a mark and a word. /// /// Inline rather than a popup: it opens in the bar that asked for it, where /// a finger already is, and there is no second surface to dismiss. export component LabelPicker inherits HorizontalLayout { /// The code currently carried, drawn as pressed. -1 where the photographs /// being labelled disagree, so nothing is shown as chosen. in property current: -1; callback chosen(int); spacing: 4px; for code in [0, 1, 2, 3, 4, 5]: Rectangle { accessible-role: button; accessible-label: code == 0 ? "No label" : Labels.names[code] + " label"; accessible-checkable: true; accessible-checked: root.current == code; accessible-action-default => { root.chosen(code); } height: Theme.control-height - 4px; width: row.preferred-width + 2 * Theme.gap-sm; y: (parent.height - self.height) / 2; border-radius: Theme.radius; border-width: 1px; border-color: root.current == code ? Theme.active : Theme.rule; background: touch.pressed ? Theme.pressed : (touch.has-hover ? Theme.hover : Theme.surface-raised); touch := TouchArea { width: 100%; height: max(parent.height, Theme.touch-target); y: (parent.height - self.height) / 2; mouse-cursor: pointer; clicked => { root.chosen(code); } } row := HorizontalLayout { padding-left: Theme.gap-sm; padding-right: Theme.gap-sm; spacing: 4px; if code > 0: LabelMark { code: code; size: 14px; y: (parent.height - self.height) / 2; } Text { text: Labels.names[code]; color: Theme.ink; font-size: Theme.text-sm; vertical-alignment: center; } } } }