From 61b7cd1c12f9df9be3d278e21d894cd85ae05b49 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 21 Jul 2026 15:26:10 -0700 Subject: [PATCH] Size the indicator tray from block implicit sizes Deriving the tray's implicit size from childrenRect fed layout results back into the bindings that produced them, tripping implicitWidth binding loop warnings. Compute it from the active and inactive blocks' own implicit sizes instead, and center the loaded blocks rather than anchor-filling containers that are sized by their content. The contract test now instantiates the tray to check the collapse/expand cycle and fails on any implicitWidth binding loop in the log. Co-Authored-By: Claude Fable 5 --- shell/plugins/bar/widgets/Indicators.qml | 26 ++++++----- .../fixtures/indicator-contract/shell.qml | 44 ++++++++++++++++++- test/shell.d/indicator-contract-test.sh | 5 +++ 3 files changed, 64 insertions(+), 11 deletions(-) diff --git a/shell/plugins/bar/widgets/Indicators.qml b/shell/plugins/bar/widgets/Indicators.qml index bf6e42c2..5b3a1f3a 100644 --- a/shell/plugins/bar/widgets/Indicators.qml +++ b/shell/plugins/bar/widgets/Indicators.qml @@ -154,8 +154,12 @@ BarWidget { onIndicatorEntriesChanged: syncActiveIndicatorOrder() - implicitWidth: root.vertical ? verticalIndicators.implicitWidth : horizontalIndicators.implicitWidth - implicitHeight: root.vertical ? verticalIndicators.implicitHeight : horizontalIndicators.implicitHeight + implicitWidth: root.vertical + ? Math.max(activeVerticalBlock.implicitWidth, inactiveVerticalArea.implicitWidth) + : activeHorizontalBlock.implicitWidth + inactiveHorizontalArea.implicitWidth + implicitHeight: root.vertical + ? activeVerticalBlock.implicitHeight + inactiveVerticalArea.implicitHeight + : Math.max(activeHorizontalBlock.implicitHeight, inactiveHorizontalArea.implicitHeight) IpcHandler { target: "omarchy.indicators" @@ -187,6 +191,7 @@ BarWidget { } ActiveIndicatorBlock { + id: activeHorizontalBlock indicatorsModule: root indicatorModel: activeIndicatorModel horizontal: true @@ -204,7 +209,7 @@ BarWidget { IndicatorBlock { id: inactiveHorizontalBlock - anchors.fill: parent + anchors.verticalCenter: parent.verticalCenter indicatorsModule: root indicatorEntries: root.indicatorEntries indicatorBlock: "inactive" @@ -229,6 +234,7 @@ BarWidget { } ActiveIndicatorBlock { + id: activeVerticalBlock indicatorsModule: root indicatorModel: activeIndicatorModel horizontal: false @@ -246,7 +252,7 @@ BarWidget { IndicatorBlock { id: inactiveVerticalBlock - anchors.fill: parent + anchors.horizontalCenter: parent.horizontalCenter indicatorsModule: root indicatorEntries: root.indicatorEntries indicatorBlock: "inactive" @@ -272,15 +278,15 @@ BarWidget { property bool horizontal: true property bool reportActiveState: false - implicitWidth: blockLoader.item ? blockLoader.item.childrenRect.width : 0 - implicitHeight: blockLoader.item ? blockLoader.item.childrenRect.height : 0 + implicitWidth: blockLoader.item ? blockLoader.item.implicitWidth : 0 + implicitHeight: blockLoader.item ? blockLoader.item.implicitHeight : 0 width: implicitWidth height: implicitHeight Loader { id: blockLoader - anchors.fill: parent + anchors.centerIn: parent sourceComponent: activeIndicatorBlockRoot.horizontal ? horizontalActiveIndicatorBlock : verticalActiveIndicatorBlock } @@ -334,15 +340,15 @@ BarWidget { property bool horizontal: true property bool reportActiveState: false - implicitWidth: blockLoader.item ? blockLoader.item.childrenRect.width : 0 - implicitHeight: blockLoader.item ? blockLoader.item.childrenRect.height : 0 + implicitWidth: blockLoader.item ? blockLoader.item.implicitWidth : 0 + implicitHeight: blockLoader.item ? blockLoader.item.implicitHeight : 0 width: implicitWidth height: implicitHeight Loader { id: blockLoader - anchors.fill: parent + anchors.centerIn: parent sourceComponent: indicatorBlockRoot.horizontal ? horizontalIndicatorBlock : verticalIndicatorBlock } diff --git a/test/shell.d/fixtures/indicator-contract/shell.qml b/test/shell.d/fixtures/indicator-contract/shell.qml index ad02aa13..77899318 100644 --- a/test/shell.d/fixtures/indicator-contract/shell.qml +++ b/test/shell.d/fixtures/indicator-contract/shell.qml @@ -47,6 +47,11 @@ ShellRoot { property bool vertical: false property int barSize: 26 property string fontFamily: "monospace" + property color barForeground: "white" + property color urgent: "red" + property bool foregroundAnimationEnabled: false + property bool centerSectionRevealHeld: false + property bool centerHoverRevealSuppressed: false property var shell: mockShell function run(command) { root.commands.push(String(command)) @@ -108,6 +113,43 @@ ShellRoot { } } + function checkIndicatorTray() { + idleService.setIdleEnabled(true) + + var component = Qt.createComponent("file://" + rootPath + "/shell/plugins/bar/widgets/Indicators.qml") + if (component.status !== Component.Ready) { + fail("Indicators failed to load: " + component.errorString()) + writeResult() + return + } + + var tray = component.createObject(root, { + bar: mockBar, + settings: { items: ["StayAwake"] } + }) + if (!tray) { + fail("Indicators failed to instantiate: " + component.errorString()) + writeResult() + return + } + + Qt.callLater(function() { + root.assertTrue(tray.implicitWidth === 0, "inactive indicator tray starts collapsed") + mockBar.centerSectionRevealHeld = true + + Qt.callLater(function() { + root.assertTrue(tray.implicitWidth > 0, "inactive indicator tray expands on center hover") + mockBar.centerSectionRevealHeld = false + + Qt.callLater(function() { + root.assertTrue(tray.implicitWidth === 0, "inactive indicator tray collapses after hover") + tray.destroy() + root.writeResult() + }) + }) + }) + } + function shellQuote(value) { return "'" + String(value).replace(/'/g, "'\\''") + "'" } @@ -165,7 +207,7 @@ ShellRoot { root.assertTrue(idleService.stayAwake === true, "Stay Awake left click toggles the idle service") } - root.writeResult() + root.checkIndicatorTray() } } } diff --git a/test/shell.d/indicator-contract-test.sh b/test/shell.d/indicator-contract-test.sh index 3746c09c..674b24c4 100755 --- a/test/shell.d/indicator-contract-test.sh +++ b/test/shell.d/indicator-contract-test.sh @@ -63,4 +63,9 @@ if ! jq -e '.ok == true' "$result" >/dev/null; then fail "QML indicator contract checks pass" fi +if rg -q 'Indicators.qml.*Binding loop detected for property "implicitWidth"' "$log"; then + sed -n '1,200p' "$log" >&2 + fail "indicator tray avoids implicit-width binding loops" +fi + pass "QML indicator contract checks pass"