Say what the application is doing, in one bar and one list
Every background job reported into a window property of its own — library-thumbs-done, library-pin-total, library-syncing — which only the grid ever read. A pin download that outlived the view it was started from drew nothing at all once the user opened an image, and there was no answer anywhere to "what is this busy with", because the answer was spread across eight properties nothing collected. They report to one register now (ui/dr-ui/src/activity.rs). It publishes an aggregate, which draws a three-pixel bar across the top of the shell in every view, and a row per job, which the settings page lists: scans, thumbnail batches, pin and open downloads, sidecar uploads, the sync and the trash. Failures stay on the list until they are cleared; routine successes do not, or a scroll would bury them. The handle removes a still-running job when it drops, so a worker that dies mid-transfer takes its row with it rather than leaving the bar sweeping for the rest of the session. Also carries in-flight work from a parallel session — the drawn icon set and the dr-pipeline ops split. dr-pipeline's build script does not compile at this commit; ui/dr-ui does, with clippy clean and its tests passing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+122
-9
@@ -1,5 +1,5 @@
|
||||
import { Theme } from "theme.slint";
|
||||
import { Button, PanelHeading, Label, Value, Caption, Panel, Field } from "widgets.slint";
|
||||
import { Button, PanelHeading, Label, Value, Caption, Panel, Field, ProgressBar, ActivityRow, Icon } from "widgets.slint";
|
||||
|
||||
// Settings: how much disk the app may spend, and what an export defaults to.
|
||||
//
|
||||
@@ -146,15 +146,13 @@ component Switch inherits Rectangle {
|
||||
border-color: root.checked ? Theme.active : Theme.rule;
|
||||
background: root.checked ? Theme.active : transparent;
|
||||
|
||||
Text {
|
||||
text: "✓";
|
||||
color: Theme.ground;
|
||||
font-size: 12px;
|
||||
Icon {
|
||||
name: "check";
|
||||
ink: Theme.ground;
|
||||
size: 11px;
|
||||
visible: root.checked;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
x: (parent.width - self.width) / 2;
|
||||
y: (parent.height - self.height) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,7 +236,60 @@ component EntryRow inherits VerticalLayout {
|
||||
}
|
||||
}
|
||||
|
||||
// One background job: what it is, how far along, and what it last said.
|
||||
//
|
||||
// A row rather than a `Value`/`Caption` pair written out at the call site,
|
||||
// because the list is built by a `for` and every row has to answer the same
|
||||
// three questions in the same order — otherwise a failure reads as a different
|
||||
// kind of thing from the job it happened to.
|
||||
//
|
||||
// The bar is drawn only while the job runs. A stopped job's fraction is either
|
||||
// 1 (it finished, and a full bar is noise) or frozen part-way (it failed, and
|
||||
// a bar sitting at 60% invites the reading "still going").
|
||||
component ActivityItem inherits VerticalLayout {
|
||||
in property <ActivityRow> row;
|
||||
|
||||
spacing: 4px;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: Theme.gap;
|
||||
|
||||
Label {
|
||||
text: root.row.title;
|
||||
body: true;
|
||||
// Faded once it is over: the list is ordered running-first, and
|
||||
// this is what makes that ordering visible at a glance rather than
|
||||
// something the reader has to work out from the text.
|
||||
opacity: root.row.running ? 1.0 : 0.6;
|
||||
}
|
||||
|
||||
Caption {
|
||||
text: root.row.detail;
|
||||
// A failure is the one thing here worth a colour (NFR-A11Y-3 is
|
||||
// satisfied by the words, which say what failed; the hue only
|
||||
// finds them faster).
|
||||
warn: root.row.failed;
|
||||
horizontal-alignment: right;
|
||||
horizontal-stretch: 1;
|
||||
overflow: elide;
|
||||
}
|
||||
}
|
||||
|
||||
if root.row.running: ProgressBar {
|
||||
indeterminate: !root.row.determinate;
|
||||
fraction: root.row.fraction;
|
||||
}
|
||||
}
|
||||
|
||||
export component SettingsPage inherits Rectangle {
|
||||
// --- background activity --------------------------------------------
|
||||
in property <[ActivityRow]> activity-rows;
|
||||
in property <int> activity-running: 0;
|
||||
/// Stopped jobs still listed: recent successes, and failures held until
|
||||
/// they are read.
|
||||
in property <int> activity-kept: 0;
|
||||
callback clear-finished();
|
||||
|
||||
// --- cache ---------------------------------------------------------
|
||||
in-out property <string> original-budget;
|
||||
in property <bool> original-unlimited: false;
|
||||
@@ -388,6 +439,68 @@ export component SettingsPage inherits Rectangle {
|
||||
// `min` so a narrow window still uses what it has (FR-UI-1).
|
||||
property <length> column: min(root.width - 2 * Theme.gap-lg, 680px);
|
||||
|
||||
// --- background activity ---------------------------------
|
||||
//
|
||||
// First on the page, and the only panel here that is not a
|
||||
// preference. It earns the position by being the one thing
|
||||
// that changes while the page is open: a transfer the user
|
||||
// came here to check on is answered before they have scrolled.
|
||||
Rectangle {
|
||||
width: content.column;
|
||||
height: activity.preferred-height;
|
||||
|
||||
activity := Panel {
|
||||
width: 100%;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: Theme.gap;
|
||||
|
||||
PanelHeading { text: "BACKGROUND ACTIVITY"; }
|
||||
|
||||
// The count beside the heading, because the list
|
||||
// below carries stopped jobs too and "how many are
|
||||
// running" should not have to be counted by eye.
|
||||
Caption {
|
||||
text: root.activity-running > 0
|
||||
? root.activity-running + " running" : "";
|
||||
horizontal-alignment: right;
|
||||
horizontal-stretch: 1;
|
||||
}
|
||||
}
|
||||
|
||||
Caption {
|
||||
text: "Scans, transfers and syncs running behind the "
|
||||
+ "interface. Everything here survives leaving "
|
||||
+ "this page; nothing here needs it open.";
|
||||
wrap: word-wrap;
|
||||
}
|
||||
|
||||
Rectangle { height: Theme.gap-sm; }
|
||||
|
||||
// Says nothing is running, rather than showing an empty
|
||||
// box: a list that is empty because the work finished
|
||||
// and one that is empty because the register is broken
|
||||
// look identical otherwise.
|
||||
if root.activity-rows.length == 0: Caption {
|
||||
text: "Nothing running.";
|
||||
}
|
||||
|
||||
for row in root.activity-rows: ActivityItem {
|
||||
row: row;
|
||||
}
|
||||
|
||||
if root.activity-kept > 0: Rectangle {
|
||||
height: Theme.control-height;
|
||||
|
||||
Button {
|
||||
x: 0;
|
||||
text: "Clear finished";
|
||||
clicked => { root.clear-finished(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- storage ---------------------------------------------
|
||||
Rectangle {
|
||||
width: content.column;
|
||||
|
||||
Reference in New Issue
Block a user