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 <noreply@anthropic.com>
This commit is contained in:
David Heinemeier Hansson
2026-07-21 15:26:10 -07:00
co-authored by Claude Fable 5
parent bee9ab476c
commit 61b7cd1c12
3 changed files with 64 additions and 11 deletions
+16 -10
View File
@@ -154,8 +154,12 @@ BarWidget {
onIndicatorEntriesChanged: syncActiveIndicatorOrder() onIndicatorEntriesChanged: syncActiveIndicatorOrder()
implicitWidth: root.vertical ? verticalIndicators.implicitWidth : horizontalIndicators.implicitWidth implicitWidth: root.vertical
implicitHeight: root.vertical ? verticalIndicators.implicitHeight : horizontalIndicators.implicitHeight ? Math.max(activeVerticalBlock.implicitWidth, inactiveVerticalArea.implicitWidth)
: activeHorizontalBlock.implicitWidth + inactiveHorizontalArea.implicitWidth
implicitHeight: root.vertical
? activeVerticalBlock.implicitHeight + inactiveVerticalArea.implicitHeight
: Math.max(activeHorizontalBlock.implicitHeight, inactiveHorizontalArea.implicitHeight)
IpcHandler { IpcHandler {
target: "omarchy.indicators" target: "omarchy.indicators"
@@ -187,6 +191,7 @@ BarWidget {
} }
ActiveIndicatorBlock { ActiveIndicatorBlock {
id: activeHorizontalBlock
indicatorsModule: root indicatorsModule: root
indicatorModel: activeIndicatorModel indicatorModel: activeIndicatorModel
horizontal: true horizontal: true
@@ -204,7 +209,7 @@ BarWidget {
IndicatorBlock { IndicatorBlock {
id: inactiveHorizontalBlock id: inactiveHorizontalBlock
anchors.fill: parent anchors.verticalCenter: parent.verticalCenter
indicatorsModule: root indicatorsModule: root
indicatorEntries: root.indicatorEntries indicatorEntries: root.indicatorEntries
indicatorBlock: "inactive" indicatorBlock: "inactive"
@@ -229,6 +234,7 @@ BarWidget {
} }
ActiveIndicatorBlock { ActiveIndicatorBlock {
id: activeVerticalBlock
indicatorsModule: root indicatorsModule: root
indicatorModel: activeIndicatorModel indicatorModel: activeIndicatorModel
horizontal: false horizontal: false
@@ -246,7 +252,7 @@ BarWidget {
IndicatorBlock { IndicatorBlock {
id: inactiveVerticalBlock id: inactiveVerticalBlock
anchors.fill: parent anchors.horizontalCenter: parent.horizontalCenter
indicatorsModule: root indicatorsModule: root
indicatorEntries: root.indicatorEntries indicatorEntries: root.indicatorEntries
indicatorBlock: "inactive" indicatorBlock: "inactive"
@@ -272,15 +278,15 @@ BarWidget {
property bool horizontal: true property bool horizontal: true
property bool reportActiveState: false property bool reportActiveState: false
implicitWidth: blockLoader.item ? blockLoader.item.childrenRect.width : 0 implicitWidth: blockLoader.item ? blockLoader.item.implicitWidth : 0
implicitHeight: blockLoader.item ? blockLoader.item.childrenRect.height : 0 implicitHeight: blockLoader.item ? blockLoader.item.implicitHeight : 0
width: implicitWidth width: implicitWidth
height: implicitHeight height: implicitHeight
Loader { Loader {
id: blockLoader id: blockLoader
anchors.fill: parent anchors.centerIn: parent
sourceComponent: activeIndicatorBlockRoot.horizontal ? horizontalActiveIndicatorBlock : verticalActiveIndicatorBlock sourceComponent: activeIndicatorBlockRoot.horizontal ? horizontalActiveIndicatorBlock : verticalActiveIndicatorBlock
} }
@@ -334,15 +340,15 @@ BarWidget {
property bool horizontal: true property bool horizontal: true
property bool reportActiveState: false property bool reportActiveState: false
implicitWidth: blockLoader.item ? blockLoader.item.childrenRect.width : 0 implicitWidth: blockLoader.item ? blockLoader.item.implicitWidth : 0
implicitHeight: blockLoader.item ? blockLoader.item.childrenRect.height : 0 implicitHeight: blockLoader.item ? blockLoader.item.implicitHeight : 0
width: implicitWidth width: implicitWidth
height: implicitHeight height: implicitHeight
Loader { Loader {
id: blockLoader id: blockLoader
anchors.fill: parent anchors.centerIn: parent
sourceComponent: indicatorBlockRoot.horizontal ? horizontalIndicatorBlock : verticalIndicatorBlock sourceComponent: indicatorBlockRoot.horizontal ? horizontalIndicatorBlock : verticalIndicatorBlock
} }
@@ -47,6 +47,11 @@ ShellRoot {
property bool vertical: false property bool vertical: false
property int barSize: 26 property int barSize: 26
property string fontFamily: "monospace" 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 property var shell: mockShell
function run(command) { function run(command) {
root.commands.push(String(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) { function shellQuote(value) {
return "'" + String(value).replace(/'/g, "'\\''") + "'" return "'" + String(value).replace(/'/g, "'\\''") + "'"
} }
@@ -165,7 +207,7 @@ ShellRoot {
root.assertTrue(idleService.stayAwake === true, "Stay Awake left click toggles the idle service") root.assertTrue(idleService.stayAwake === true, "Stay Awake left click toggles the idle service")
} }
root.writeResult() root.checkIndicatorTray()
} }
} }
} }
+5
View File
@@ -63,4 +63,9 @@ if ! jq -e '.ok == true' "$result" >/dev/null; then
fail "QML indicator contract checks pass" fail "QML indicator contract checks pass"
fi 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" pass "QML indicator contract checks pass"