From 50b50cb72655d090ae201188fbfc6add42f43d40 Mon Sep 17 00:00:00 2001 From: rickpeters Date: Sun, 27 Sep 2026 17:11:46 +0200 Subject: [PATCH] feat(ui): Review & apply greyed out while nothing would change; the Done screen shows why a run stopped --- ui/qml/AppState.qml | 12 ++++++++++-- ui/qml/Btn.qml | 6 ++++-- ui/qml/DoneScreen.qml | 14 ++++++++++++-- ui/qml/MenuScreen.qml | 3 ++- 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/ui/qml/AppState.qml b/ui/qml/AppState.qml index 8e1cb3b..ac783a3 100644 --- a/ui/qml/AppState.qml +++ b/ui/qml/AppState.qml @@ -18,6 +18,10 @@ Item { property var steps: ({}) // id -> "wait"|"run"|"ok"|"fail" property var failed: [] property bool restartNeeded: false + // Review & apply only when something would change; "Re-apply what's on" + // is for when nothing did. + readonly property bool canApply: computePlan().length > 0 + onCanApplyChanged: if (!canApply && sel === rows.length) sel = Math.max(0, rows.length - 1) property string runError: "" property bool wrongPassword: false property bool typing: false // a text field has the keys @@ -93,7 +97,10 @@ Item { if (want.gaming && boot !== bootNow) p.push({ id: "boot", action: boot === "desktop" ? "desktop" : "gaming" }); return p; } - function goReview(again) { reapply = again; plan = computePlan(); screen = "review"; } + function goReview(again) { + if (!again && !canApply) return; + reapply = again; plan = computePlan(); screen = "review"; + } function wantedIds() { var ids = []; for (var i = 0; i < items.length; i++) @@ -195,7 +202,8 @@ Item { if (screen === "menu") { if (biosChecking) return; if (a === "up") sel = Math.max(0, sel - 1); - else if (a === "down") sel = Math.min(rows.length, sel + 1); + // The greyed-out Apply button can't be selected. + else if (a === "down") sel = Math.min(canApply ? rows.length : rows.length - 1, sel + 1); else if (a === "accept") { if (sel === rows.length) { goReview(false); return; } var r = rows[sel]; diff --git a/ui/qml/Btn.qml b/ui/qml/Btn.qml index 8a8533c..7c0d781 100644 --- a/ui/qml/Btn.qml +++ b/ui/qml/Btn.qml @@ -10,11 +10,13 @@ Rectangle { signal clicked height: 48; radius: 12; width: row.implicitWidth + 36 color: primary ? Theme.accent : Theme.button - border.width: focusRing ? 2 : 0; border.color: Theme.textHi + border.width: focusRing && enabled ? 2 : 0; border.color: Theme.textHi + // Greyed out while there's nothing to do. + opacity: enabled ? 1 : 0.4 Row { id: row; anchors.centerIn: parent; spacing: 10 Glyph { k: btn.k; visible: btn.k !== ""; dark: btn.primary; anchors.verticalCenter: parent.verticalCenter } Text { text: btn.text; color: btn.primary ? Theme.ink : Theme.text; font.family: Theme.body; font.pixelSize: 15; font.weight: Font.DemiBold anchors.verticalCenter: parent.verticalCenter } } - MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: btn.clicked() } + MouseArea { anchors.fill: parent; enabled: btn.enabled; cursorShape: Qt.PointingHandCursor; onClicked: btn.clicked() } } diff --git a/ui/qml/DoneScreen.qml b/ui/qml/DoneScreen.qml index 448369d..6f3aeac 100644 --- a/ui/qml/DoneScreen.qml +++ b/ui/qml/DoneScreen.qml @@ -1,5 +1,8 @@ -// The result of a run: all done, a problem, or nothing changed. +// The result of a run: all done, a problem, or nothing changed. With a +// problem, the run's output is shown below it (a BIOS check has no progress +// screen, so this is the only place to read why it stopped). import QtQuick +import QtQuick.Controls.Basic Item { required property AppState app @@ -20,7 +23,14 @@ Item { Text { width: parent.width; horizontalAlignment: Text.AlignHCenter; wrapMode: Text.WordWrap; color: Theme.soft; font.family: Theme.body; font.pixelSize: 17; text: message } Rectangle { visible: app.failed.length > 0; anchors.horizontalCenter: parent.horizontalCenter; width: ft.implicitWidth + 32; height: 44; radius: 12; color: Theme.warnBg Text { id: ft; anchors.centerIn: parent; color: "#f2c27a"; font.family: Theme.body; font.pixelSize: 14 - text: "Had a problem: " + app.failed.map(function (id) { return Texts.label(id); }).join(", ") + ". See the details." } } + text: "Had a problem: " + app.failed.map(function (id) { return Texts.label(id); }).join(", ") + (app.log.count ? ". Details below." : ".") } } + Rectangle { visible: app.failed.length > 0 && app.log.count > 0; width: parent.width; height: 190; radius: 14; color: Theme.card + ListView { id: details; anchors.fill: parent; anchors.margins: 16; clip: true; model: app.log + ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded } + // The end is where it stopped. + onCountChanged: positionViewAtEnd() + onVisibleChanged: if (visible) positionViewAtEnd() + delegate: Text { required property string line; width: details.width - 12; text: line; color: "#aab5c4"; font.family: Theme.mono; font.pixelSize: 13; wrapMode: Text.WrapAnywhere } } } Row { anchors.horizontalCenter: parent.horizontalCenter; spacing: 14; topPadding: 8 Btn { k: Input.g.back; text: "Back to the menu"; height: 52; onClicked: app.act("back") } Btn { visible: app.restartNeeded; k: Input.g.ok; text: "Restart now"; primary: true; height: 52; onClicked: app.backend.restart() } } diff --git a/ui/qml/MenuScreen.qml b/ui/qml/MenuScreen.qml index 0b24f54..e0f397e 100644 --- a/ui/qml/MenuScreen.qml +++ b/ui/qml/MenuScreen.qml @@ -70,7 +70,8 @@ Item { Text { readonly property int n: app.computePlan().length text: n === 0 ? "Everything is the way you want it" : n + (n === 1 ? " change" : " changes") color: Theme.faint; font.family: Theme.body; font.pixelSize: 14; anchors.verticalCenter: parent.verticalCenter } - Btn { k: screen.g.apply; text: "Review & apply"; primary: true; focusRing: app.sel === app.rows.length; height: 44; onClicked: app.goReview(false) } + Btn { k: screen.g.apply; text: "Review & apply"; primary: true; enabled: app.canApply + focusRing: app.sel === app.rows.length; height: 44; onClicked: app.goReview(false) } } } }