From 9a7b045df44c22a1d915f1083974946f90e2c8c6 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sat, 22 Aug 2026 10:46:35 +0200 Subject: [PATCH] Pin the group strip where it can be found MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It was inside AdjustPanel, which on a tablet put it below five other panels and off the bottom of the screen: present, working, and unreachable without scrolling past everything it exists to save you scrolling past. A control that answers "where is everything else" cannot itself be somewhere else. Now a GroupStrip above the scrolling column, so it never scrolls away. It still names no group — the strings arrive resolved from whatever the operations declared themselves to be about. --- ui/dr-ui/ui/adjust.slint | 126 +++++++++++++++++++++++---------------- ui/dr-ui/ui/app.slint | 14 +++-- 2 files changed, 85 insertions(+), 55 deletions(-) diff --git a/ui/dr-ui/ui/adjust.slint b/ui/dr-ui/ui/adjust.slint index b1cbeb5..8d81154 100644 --- a/ui/dr-ui/ui/adjust.slint +++ b/ui/dr-ui/ui/adjust.slint @@ -628,16 +628,84 @@ export component TransferPanel inherits VerticalLayout { } } -export component AdjustPanel inherits Rectangle { - in property <[ParamRow]> rows; - /// The groups worth offering, derived from what the operations say they - /// are about. **This file names none of them** — the strings arrive - /// already resolved and the panel only has to draw them, which is what - /// keeps a new operation a zero-edit change here (FR-DEV-3a). + +/// The group strip: which kind of adjustment the panel is showing. +/// +/// **Pinned above the scrolling column, not inside a panel.** It began inside +/// `AdjustPanel`, which put it below five other panels and off the bottom of a +/// tablet screen — present, working, and unreachable without scrolling past +/// everything it was meant to help you avoid scrolling past. A control that +/// answers "where is everything else" cannot itself be somewhere else. +/// +/// **This file names no group.** The strings arrive already resolved from +/// whatever the operations declared themselves to be about, so a new operation +/// joins a group without an edit here (FR-DEV-3a). +export component GroupStrip inherits Rectangle { in property <[string]> tabs; /// Index into `tabs`, or -1 for "everything". in property active-tab: -1; - callback tab-picked(int); + in property enabled: true; + + callback picked(int); + + background: Theme.surface; + height: root.enabled && root.tabs.length > 1 ? layout.preferred-height : 0px; + visible: root.enabled && root.tabs.length > 1; + + layout := HorizontalLayout { + padding-left: Theme.gap; + padding-right: Theme.gap; + spacing: Theme.gap-sm; + alignment: start; + + all := TouchArea { + width: 34px; + height: Theme.touch-target; + mouse-cursor: pointer; + clicked => { root.picked(-1); } + + Label { + text: "All"; + emphasised: root.active-tab == -1 || all.has-hover; + vertical-alignment: center; + } + + Rectangle { + y: parent.height - 2px; + height: 2px; + width: parent.width; + background: root.active-tab == -1 ? Theme.ink : transparent; + } + } + + for tab[i] in root.tabs: tab-area := TouchArea { + width: name.preferred-width + 10px; + height: Theme.touch-target; + mouse-cursor: pointer; + clicked => { root.picked(i); } + + name := Label { + text: tab; + emphasised: root.active-tab == i || tab-area.has-hover; + vertical-alignment: center; + horizontal-alignment: center; + } + + // Underlined rather than filled: the accent means *modified* + // everywhere else here, and spending it on "which group" would + // blunt the one signal the panel has. + Rectangle { + y: parent.height - 2px; + height: 2px; + width: parent.width; + background: root.active-tab == i ? Theme.ink : transparent; + } + } + } +} + +export component AdjustPanel inherits Rectangle { + in property <[ParamRow]> rows; in property enabled: true; /// The tone curve's sampled shape, evaluated in Rust by the same spline /// the shader runs so the drawn line cannot disagree with the applied one. @@ -685,50 +753,6 @@ export component AdjustPanel inherits Rectangle { if !root.enabled: Caption { text: "No image"; } - // The group strip. Only when there is more than one group to choose - // between — a single tab is a control with one option, which is - // decoration. - if root.enabled && root.tabs.length > 1: HorizontalLayout { - spacing: Theme.gap-sm; - alignment: start; - - all := TouchArea { - width: 34px; - height: Theme.touch-target; - mouse-cursor: pointer; - clicked => { root.tab-picked(-1); } - Label { - text: "All"; - emphasised: root.active-tab == -1 || all.has-hover; - vertical-alignment: center; - } - } - - for tab[i] in root.tabs: tab-area := TouchArea { - width: label.preferred-width + 8px; - height: Theme.touch-target; - mouse-cursor: pointer; - clicked => { root.tab-picked(i); } - - label := Label { - text: tab; - emphasised: root.active-tab == i || tab-area.has-hover; - vertical-alignment: center; - } - - // The selected group is underlined rather than filled: the - // accent means *modified* everywhere else in this interface, - // and spending it on "which tab" would blunt the one signal - // the panel has (ui-refinement.md, principle 2). - Rectangle { - y: parent.height - 2px; - height: 2px; - width: parent.width; - background: root.active-tab == i ? Theme.ink : transparent; - } - } - } - // No Flickable here any more. The histogram, the capture metadata and // the geometry controls sat *above* this one and could not be scrolled // away, so on a 280px column in portrait they ate the height the diff --git a/ui/dr-ui/ui/app.slint b/ui/dr-ui/ui/app.slint index 7cdbe0a..adea532 100644 --- a/ui/dr-ui/ui/app.slint +++ b/ui/dr-ui/ui/app.slint @@ -1,5 +1,5 @@ import { Theme } from "theme.slint"; -import { AdjustPanel, GeometryPanel, ParamRow, TransferPanel } from "adjust.slint"; +import { AdjustPanel, GeometryPanel, GroupStrip, ParamRow, TransferPanel } from "adjust.slint"; import { MaskPanel, MaskRow, SubjectRow } from "masks.slint"; import { LaunchScreen } from "launch.slint"; import { LibraryGrid, LibraryCell, TimelineBar, PhotoRoll } from "library.slint"; @@ -1837,6 +1837,15 @@ in property panel-visible: true; // scroller — see the note on `SliderTrack`'s `claimed`. // Without it every attempt to drag a slider would scroll // the column instead. + // Pinned: the strip is how the column is navigated, so it + // cannot be somewhere you have to navigate to. + GroupStrip { + enabled: root.adjust-enabled; + tabs: root.adjust-tabs; + active-tab: root.adjust-active-tab; + picked(i) => { root.adjust-tab-picked(i); } + } + Flickable { viewport-height: column.preferred-height; interactive: !adjust.slider-dragging; @@ -1963,9 +1972,6 @@ in property panel-visible: true; adjust := AdjustPanel { rows: root.adjust-rows; - tabs: root.adjust-tabs; - active-tab: root.adjust-active-tab; - tab-picked(i) => { root.adjust-tab-picked(i); } enabled: root.adjust-enabled; curve-samples: root.curve-samples; param-changed(op, param, value) => {