diff --git a/shell/Commons/Style.qml b/shell/Commons/Style.qml index 88aa6a90..8854e052 100644 --- a/shell/Commons/Style.qml +++ b/shell/Commons/Style.qml @@ -341,6 +341,8 @@ QtObject { readonly property QtObject bar: QtObject { readonly property int sizeHorizontal: root.barToken("size-horizontal", 26) readonly property int sizeVertical: root.barToken("size-vertical", 28) + readonly property int iconSlot: root.barToken("icon-slot", 27) + readonly property int iconCanvas: root.barToken("icon-canvas", 16) } function refresh() { diff --git a/shell/Ui/BarIconButton.qml b/shell/Ui/BarIconButton.qml new file mode 100644 index 00000000..21c4e94f --- /dev/null +++ b/shell/Ui/BarIconButton.qml @@ -0,0 +1,60 @@ +import QtQuick +import Quickshell +import qs.Commons + +WidgetButton { + id: root + + property Component iconComponent: null + property real opticalSize: Style.bar.iconCanvas + property bool debugOpticalBounds: Quickshell.env("OMARCHY_DEBUG_BAR_ICONS") === "1" + readonly property real opticalCenterErrorX: glyph.visible ? glyph.paintedCenterX - opticalCanvas.width / 2 : 0 + readonly property real opticalCenterErrorY: glyph.visible ? glyph.paintedCenterY - opticalCanvas.height / 2 : 0 + + labelVisible: false + hasVisualContent: text !== "" || iconComponent !== null + fontSize: Style.font.body + fixedWidth: vertical ? -1 : Style.bar.iconSlot + fixedHeight: vertical ? Style.bar.iconSlot : -1 + + Item { + id: opticalCanvas + anchors.centerIn: parent + width: root.opticalSize + height: root.opticalSize + + OpticalGlyph { + id: glyph + anchors.fill: parent + visible: root.iconComponent === null + text: root.text + fontFamily: root.fontFamily + fontSize: root.fontSize + color: root.active && root.useActiveColor ? root.activeColor : root.foreground + rotation: root.textRotation + debugBounds: root.debugOpticalBounds + } + + Loader { + anchors.fill: parent + visible: root.iconComponent !== null + sourceComponent: root.iconComponent + } + + Rectangle { + visible: root.debugOpticalBounds && root.iconComponent !== null + anchors.fill: parent + color: "transparent" + border.width: 1 + border.color: "#4488ff" + } + } + + Rectangle { + visible: root.debugOpticalBounds + anchors.fill: parent + color: "transparent" + border.width: 1 + border.color: "#ff4455" + } +} diff --git a/shell/Ui/OpticalGlyph.qml b/shell/Ui/OpticalGlyph.qml new file mode 100644 index 00000000..0900d78d --- /dev/null +++ b/shell/Ui/OpticalGlyph.qml @@ -0,0 +1,58 @@ +import QtQuick +import qs.Commons + +Item { + id: root + + property string text: "" + property string fontFamily: Style.font.family + property real fontSize: Style.font.body + property color color: Color.foreground + property real targetExtent: Math.min(width, height) * 0.82 + property bool debugBounds: false + + readonly property real baseTightWidth: Math.max(1, baseMetrics.tightBoundingRect.width) + readonly property real baseTightHeight: Math.max(1, baseMetrics.tightBoundingRect.height) + readonly property real normalizedScale: Math.min(1.5, targetExtent / Math.max(baseTightWidth, baseTightHeight)) + readonly property int normalizedFontSize: Math.max(1, Math.round(fontSize * normalizedScale)) + readonly property real tightWidth: Math.max(1, glyphMetrics.tightBoundingRect.width) + readonly property real tightHeight: Math.max(1, glyphMetrics.tightBoundingRect.height) + readonly property real horizontalCorrection: glyph.implicitWidth / 2 - (glyphMetrics.tightBoundingRect.x + tightWidth / 2) + readonly property real verticalCorrection: glyph.implicitHeight / 2 - (glyph.baselineOffset + glyphMetrics.tightBoundingRect.y + tightHeight / 2) + readonly property real paintedCenterX: glyph.x + glyphMetrics.tightBoundingRect.x + tightWidth / 2 + readonly property real paintedCenterY: glyph.y + glyph.baselineOffset + glyphMetrics.tightBoundingRect.y + tightHeight / 2 + + TextMetrics { + id: baseMetrics + font.family: root.fontFamily + font.pixelSize: root.fontSize + text: root.text + } + + TextMetrics { + id: glyphMetrics + font.family: root.fontFamily + font.pixelSize: root.normalizedFontSize + text: root.text + } + + Text { + id: glyph + anchors.centerIn: parent + anchors.horizontalCenterOffset: root.horizontalCorrection + anchors.verticalCenterOffset: root.verticalCorrection + text: root.text + color: root.color + font.family: root.fontFamily + font.pixelSize: root.normalizedFontSize + renderType: Text.NativeRendering + } + + Rectangle { + visible: root.debugBounds + anchors.fill: parent + color: "transparent" + border.width: 1 + border.color: "#4488ff" + } +} diff --git a/shell/Ui/WidgetButton.qml b/shell/Ui/WidgetButton.qml index 951cb4af..1641fcfd 100644 --- a/shell/Ui/WidgetButton.qml +++ b/shell/Ui/WidgetButton.qml @@ -12,7 +12,6 @@ Item { property color activeColor: bar ? bar.urgent : Color.urgent property bool active: false property real horizontalMargin: 8.5 - property real rightExtraMargin: 0 property real verticalPadding: 6 property real fixedWidth: -1 property real fixedHeight: -1 @@ -24,6 +23,8 @@ Item { property bool pressable: true property bool useActiveColor: true property bool maintainIndicatorReveal: false + property bool labelVisible: true + property bool hasVisualContent: text !== "" property var revealHost: bar property string tooltipText: "" property var registeredBar: null @@ -56,13 +57,12 @@ Item { readonly property bool vertical: bar ? bar.vertical : false readonly property int barSize: bar ? bar.barSize : Style.bar.sizeHorizontal readonly property real scaledHorizontalMargin: Style.spaceReal(horizontalMargin) - readonly property real scaledRightExtraMargin: Style.spaceReal(rightExtraMargin) readonly property real scaledVerticalPadding: Style.spaceReal(verticalPadding) readonly property bool tooltipHovered: visible && interactive && !concealed && mouseArea.containsMouse - visible: text !== "" || keepSpace - opacity: text === "" || concealed ? 0 : (dimmed ? 0.45 : 1) - implicitWidth: fixedWidth > 0 ? fixedWidth : (vertical ? barSize : Math.max(12, label.implicitWidth + scaledHorizontalMargin * 2 + scaledRightExtraMargin)) + visible: hasVisualContent || keepSpace + opacity: !hasVisualContent || concealed ? 0 : (dimmed ? 0.45 : 1) + implicitWidth: fixedWidth > 0 ? fixedWidth : (vertical ? barSize : Math.max(12, label.implicitWidth + scaledHorizontalMargin * 2)) implicitHeight: fixedHeight > 0 ? fixedHeight : (vertical ? Math.max(12, label.implicitHeight + scaledVerticalPadding * 2) : barSize) Behavior on opacity { @@ -71,8 +71,8 @@ Item { Text { id: label + visible: root.labelVisible anchors.centerIn: parent - anchors.horizontalCenterOffset: root.vertical ? 0 : -root.scaledRightExtraMargin / 2 text: root.text color: root.active && root.useActiveColor ? root.activeColor : root.foreground font.family: root.fontFamily diff --git a/shell/Ui/qmldir b/shell/Ui/qmldir index 5e436654..5953f312 100644 --- a/shell/Ui/qmldir +++ b/shell/Ui/qmldir @@ -1,6 +1,7 @@ module qs.Ui BarIndicator 1.0 BarIndicator.qml +BarIconButton 1.0 BarIconButton.qml BarWidget 1.0 BarWidget.qml BorderOverlay 1.0 BorderOverlay.qml BorderSurface 1.0 BorderSurface.qml @@ -12,6 +13,7 @@ Dropdown 1.0 Dropdown.qml KeyboardPanel 1.0 KeyboardPanel.qml MultiSelect 1.0 MultiSelect.qml NumberField 1.0 NumberField.qml +OpticalGlyph 1.0 OpticalGlyph.qml Panel 1.0 Panel.qml PanelActionButton 1.0 PanelActionButton.qml PanelController 1.0 PanelController.qml diff --git a/shell/plugins/bar/Bar.qml b/shell/plugins/bar/Bar.qml index 939019d8..9a0b320c 100644 --- a/shell/plugins/bar/Bar.qml +++ b/shell/plugins/bar/Bar.qml @@ -1297,13 +1297,6 @@ Item { readonly property bool hovered: moduleHover.hovered readonly property bool dragSource: root.barDragSource === slot readonly property bool panelOpen: root.activePopout === slot.activeItem - readonly property real openIndicatorInlineOffset: { - var item = slot.activeItem - if (!item || !("openIndicatorInlineOffset" in item)) return 0 - var offset = Number(item.openIndicatorInlineOffset) - return isFinite(offset) ? offset : 0 - } - implicitWidth: activeItem && activeItem.visible ? (root.vertical ? root.barSize : activeItem.implicitWidth) : 0 implicitHeight: activeItem && activeItem.visible ? activeItem.implicitHeight : 0 width: implicitWidth @@ -1377,9 +1370,9 @@ Item { height: root.vertical ? Math.max(Style.space(10), Math.round(parent.height * 0.55)) : Style.space(2) x: root.vertical ? (root.position === "left" ? parent.width - width - inset : inset) - : (slot.openIndicatorInlineOffset === 0 ? Math.round((parent.width - width) / 2) : (parent.width - width) / 2 + slot.openIndicatorInlineOffset) + : Math.round((parent.width - width) / 2) y: root.vertical - ? (slot.openIndicatorInlineOffset === 0 ? Math.round((parent.height - height) / 2) : (parent.height - height) / 2 + slot.openIndicatorInlineOffset) + ? Math.round((parent.height - height) / 2) : (root.position === "top" ? parent.height - height - inset : inset) z: 50 diff --git a/shell/plugins/bar/widgets/Microphone.qml b/shell/plugins/bar/widgets/Microphone.qml index 200eae97..4f1bf211 100644 --- a/shell/plugins/bar/widgets/Microphone.qml +++ b/shell/plugins/bar/widgets/Microphone.qml @@ -34,7 +34,7 @@ BarWidget { PwObjectTracker { objects: root.source ? [root.source] : [] } - WidgetButton { + BarIconButton { id: button anchors.fill: parent bar: root.bar diff --git a/shell/plugins/bar/widgets/SystemUpdate.qml b/shell/plugins/bar/widgets/SystemUpdate.qml index e91a8f6b..b6563741 100644 --- a/shell/plugins/bar/widgets/SystemUpdate.qml +++ b/shell/plugins/bar/widgets/SystemUpdate.qml @@ -52,12 +52,11 @@ BarWidget { onTriggered: root.refresh() } - WidgetButton { + BarIconButton { id: button anchors.fill: parent bar: root.bar text: "\uf021" - fontSize: Style.font.caption tooltipText: "" onPressed: root.runUpdate() } diff --git a/shell/plugins/bar/widgets/Tray.qml b/shell/plugins/bar/widgets/Tray.qml index be55ceb8..06f2734a 100644 --- a/shell/plugins/bar/widgets/Tray.qml +++ b/shell/plugins/bar/widgets/Tray.qml @@ -24,9 +24,9 @@ BarWidget { readonly property var drawerItems: bucket("drawer") readonly property var allItems: bucket("all") readonly property int drawerCount: drawerItems.length - readonly property int trayItemExtent: Style.space(16) - readonly property int trayItemGap: Style.space(9) - readonly property int trayJoinGap: Style.space(4) + readonly property int trayItemExtent: Style.bar.iconSlot + readonly property int trayItemGap: 0 + readonly property int trayJoinGap: 0 readonly property int drawerExtent: drawerCount > 0 ? drawerCount * trayItemExtent + (drawerCount - 1) * trayItemGap : 0 // Match Waybar's group/tray-expander drawer transition-duration. readonly property int animationDuration: 600 @@ -182,15 +182,13 @@ BarWidget { onHoveredChanged: root.expanded = hovered } - WidgetButton { + BarIconButton { id: expandIcon bar: root.bar width: implicitWidth height: implicitHeight x: root.drawerExtent - root.revealExtent text: "\uf053" - horizontalMargin: 9 - verticalPadding: 6 onPressed: function(button) { if (button === Qt.RightButton) root.managePopupOpen = !root.managePopupOpen } @@ -266,7 +264,7 @@ BarWidget { onHoveredChanged: root.expanded = hovered } - WidgetButton { + BarIconButton { id: expandIcon bar: root.bar width: implicitWidth @@ -274,8 +272,6 @@ BarWidget { y: root.drawerExtent - root.revealExtent text: "\uf053" textRotation: 90 - horizontalMargin: 9 - verticalPadding: 6 onPressed: function(button) { if (button === Qt.RightButton) root.managePopupOpen = !root.managePopupOpen } diff --git a/shell/plugins/panels/audio/Panel.qml b/shell/plugins/panels/audio/Panel.qml index 35abd1dc..bdce12b3 100644 --- a/shell/plugins/panels/audio/Panel.qml +++ b/shell/plugins/panels/audio/Panel.qml @@ -522,14 +522,11 @@ Panel { onTriggered: root.refreshDisplayAudioModels() } - WidgetButton { + BarIconButton { id: button anchors.fill: parent bar: root.bar text: root.outputIcon() - fontSize: Style.font.body - fixedWidth: root.bar && root.bar.vertical ? -1 : Style.space(27) - fixedHeight: root.bar && root.bar.vertical ? Style.space(26) : -1 onPressed: function(b) { if (b === Qt.RightButton) root.toggleOutputMute() else root.toggle() diff --git a/shell/plugins/panels/bluetooth/Panel.qml b/shell/plugins/panels/bluetooth/Panel.qml index 2c23e4c8..5b43d5cf 100644 --- a/shell/plugins/panels/bluetooth/Panel.qml +++ b/shell/plugins/panels/bluetooth/Panel.qml @@ -469,13 +469,11 @@ Panel { }) } - WidgetButton { + BarIconButton { id: button anchors.fill: parent bar: root.bar text: root.icon - fixedWidth: root.bar && root.bar.vertical ? -1 : Style.space(27) - fixedHeight: root.bar && root.bar.vertical ? Style.space(26) : -1 onPressed: function(b) { if (b === Qt.RightButton) root.toggleBluetooth() else if (b === Qt.MiddleButton) root.bar.run("omarchy-launch-bluetooth") diff --git a/shell/plugins/panels/dropbox/Panel.qml b/shell/plugins/panels/dropbox/Panel.qml index 92203a56..031853fe 100644 --- a/shell/plugins/panels/dropbox/Panel.qml +++ b/shell/plugins/panels/dropbox/Panel.qml @@ -139,48 +139,25 @@ Panel { function status(): string { return dropbox.statusText } } - Item { + BarIconButton { id: button anchors.fill: parent - implicitWidth: root.bar && root.bar.vertical ? root.bar.barSize : Style.space(27) - implicitHeight: root.bar && root.bar.vertical ? Style.space(26) : (root.bar ? root.bar.barSize : Style.space(26)) - - property var registeredBar: null - - function triggerPress(buttonCode) { + bar: root.bar + iconComponent: Component { + Item { + DropboxIcon { + anchors.centerIn: parent + iconSize: Style.space(12) + color: root.barIconColor + opacity: dropbox.authenticated ? 1.0 : 0.6 + } + } + } + onPressed: function(buttonCode) { if (buttonCode === Qt.RightButton) dropbox.refresh() else if (buttonCode === Qt.MiddleButton) dropbox.login() else root.toggle() } - - function syncClickRegistration() { - if (registeredBar && registeredBar.unregisterClickTarget) registeredBar.unregisterClickTarget(button) - registeredBar = root.bar - if (registeredBar && registeredBar.registerClickTarget) registeredBar.registerClickTarget(button) - } - - Component.onCompleted: syncClickRegistration() - Component.onDestruction: if (registeredBar && registeredBar.unregisterClickTarget) registeredBar.unregisterClickTarget(button) - - Connections { - target: root - function onBarChanged() { button.syncClickRegistration() } - } - - DropboxIcon { - anchors.centerIn: parent - iconSize: Style.space(12) - color: root.barIconColor - opacity: dropbox.authenticated ? 1.0 : 0.6 - } - - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: function(mouse) { button.triggerPress(mouse.button) } - } } KeyboardPanel { diff --git a/shell/plugins/panels/monitor/Panel.qml b/shell/plugins/panels/monitor/Panel.qml index 75d19fc0..1ca893c7 100644 --- a/shell/plugins/panels/monitor/Panel.qml +++ b/shell/plugins/panels/monitor/Panel.qml @@ -337,15 +337,11 @@ Panel { onRunningChanged: if (!running) root.refresh() } - WidgetButton { + BarIconButton { id: button anchors.fill: parent bar: root.bar text: root.displays.length > 1 ? "󰍺" : "󰍹" - fontSize: Style.font.subtitle - fixedWidth: root.bar && root.bar.vertical ? -1 : Style.space(27) - fixedHeight: root.bar && root.bar.vertical ? Style.space(26) : -1 - rightExtraMargin: 4 onPressed: function(b) { root.toggle() } onWheelMoved: function(delta) { if (root.brightnessAvailable) root.setBrightness(root.brightnessPercent + (delta > 0 ? 5 : -5)) diff --git a/shell/plugins/panels/network/Panel.qml b/shell/plugins/panels/network/Panel.qml index e4b3ea9b..2d3c9bae 100644 --- a/shell/plugins/panels/network/Panel.qml +++ b/shell/plugins/panels/network/Panel.qml @@ -707,14 +707,11 @@ Panel { } } - WidgetButton { + BarIconButton { id: button anchors.fill: parent bar: root.bar text: root.icon - fixedWidth: root.bar && root.bar.vertical ? -1 : Style.space(27) - fixedHeight: root.bar && root.bar.vertical ? Style.space(26) : -1 - rightExtraMargin: 5.5 onPressed: function(b) { if (root.opened) root.close() diff --git a/shell/plugins/panels/power/Panel.qml b/shell/plugins/panels/power/Panel.qml index 1f0f3b79..e5fb1f49 100644 --- a/shell/plugins/panels/power/Panel.qml +++ b/shell/plugins/panels/power/Panel.qml @@ -249,13 +249,11 @@ Panel { } } - WidgetButton { + BarIconButton { id: button anchors.fill: parent bar: root.bar text: root.batteryIcon() - fixedWidth: root.bar && root.bar.vertical ? -1 : Style.space(27) - fixedHeight: root.bar && root.bar.vertical ? Style.space(26) : -1 tooltipText: "" onPressed: function(b) { if (root.batteryPresent) root.toggle() } } diff --git a/shell/plugins/panels/tailscale/Panel.qml b/shell/plugins/panels/tailscale/Panel.qml index a6e084f7..e0de662e 100644 --- a/shell/plugins/panels/tailscale/Panel.qml +++ b/shell/plugins/panels/tailscale/Panel.qml @@ -12,7 +12,6 @@ Panel { ipcTarget: "omarchy.tailscale" manageIpc: false - readonly property real openIndicatorInlineOffset: bar && bar.vertical ? 0 : Style.spaceReal(1.5) property string focusSection: "header" property int headerIndex: 0 property int accountIndex: 0 @@ -360,53 +359,27 @@ Panel { function status(): string { return tailscale.statusText } } - Item { + BarIconButton { id: button anchors.fill: parent - implicitWidth: root.bar && root.bar.vertical ? root.bar.barSize : Style.space(27) - implicitHeight: root.bar && root.bar.vertical ? Style.space(26) : (root.bar ? root.bar.barSize : Style.space(26)) - - property var registeredBar: null - - function triggerPress(buttonCode) { + bar: root.bar + iconComponent: Component { + Item { + TailscaleIcon { + anchors.centerIn: parent + iconSize: Style.space(11) + color: root.barIconColor + badgeColor: root.urgent + crossed: !tailscale.running && !tailscale.needsLogin + warning: tailscale.needsLogin + } + } + } + onPressed: function(buttonCode) { if (buttonCode === Qt.RightButton) tailscale.toggleTailscale() else if (buttonCode === Qt.MiddleButton) tailscale.refresh() else root.toggle() } - - function syncClickRegistration() { - if (registeredBar && registeredBar.unregisterClickTarget) registeredBar.unregisterClickTarget(button) - registeredBar = root.bar - if (registeredBar && registeredBar.registerClickTarget) registeredBar.registerClickTarget(button) - } - - Component.onCompleted: syncClickRegistration() - Component.onDestruction: if (registeredBar && registeredBar.unregisterClickTarget) registeredBar.unregisterClickTarget(button) - - Connections { - target: root - function onBarChanged() { button.syncClickRegistration() } - } - - TailscaleIcon { - anchors.centerIn: parent - anchors.horizontalCenterOffset: root.openIndicatorInlineOffset - anchors.verticalCenterOffset: -Style.space(1) - iconSize: Style.space(12) * 0.85 - color: root.barIconColor - badgeColor: root.urgent - crossed: !tailscale.running && !tailscale.needsLogin - warning: tailscale.needsLogin - } - - MouseArea { - id: mouseArea - anchors.fill: parent - acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: function(mouse) { button.triggerPress(mouse.button) } - } } KeyboardPanel { diff --git a/test/shell.d/bar-icon-geometry-test.sh b/test/shell.d/bar-icon-geometry-test.sh new file mode 100644 index 00000000..6e6eb5cd --- /dev/null +++ b/test/shell.d/bar-icon-geometry-test.sh @@ -0,0 +1,102 @@ +#!/bin/bash + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" + +if ! command -v quickshell >/dev/null 2>&1; then + pass "quickshell not installed; skipping bar icon geometry test" + exit 0 +fi + +test_tmp=$(mktemp -d) +trap 'rm -rf "$test_tmp"' EXIT + +ln -s "$ROOT/shell/Ui" "$test_tmp/Ui" +ln -s "$ROOT/shell/Commons" "$test_tmp/Commons" + +cat >"$test_tmp/shell.qml" <<'QML' +import QtQuick +import Quickshell +import qs.Commons +import qs.Ui + +ShellRoot { + id: root + + function fail(message) { + console.log("RESULT fail " + message) + Qt.quit() + } + + function checkIcon(icon, name) { + if (icon.implicitWidth !== Style.bar.iconSlot) { + fail(name + " slot width is " + icon.implicitWidth) + return false + } + if (icon.opticalSize !== Style.bar.iconCanvas) { + fail(name + " optical canvas is " + icon.opticalSize) + return false + } + if (Math.abs(icon.opticalCenterErrorX) > 0.5 || Math.abs(icon.opticalCenterErrorY) > 0.5) { + fail(name + " painted bounds are over half a pixel off center by " + icon.opticalCenterErrorX + "," + icon.opticalCenterErrorY) + return false + } + return true + } + + Component.onCompleted: Qt.callLater(function() { + if (!checkIcon(bluetooth, "bluetooth")) return + if (!checkIcon(network, "network")) return + if (!checkIcon(audio, "audio")) return + if (!checkIcon(monitor, "monitor")) return + if (!checkIcon(power, "power")) return + if (vector.implicitWidth !== Style.bar.iconSlot || vector.opticalSize !== Style.bar.iconCanvas) { + fail("vector icon does not share glyph geometry") + return + } + console.log("RESULT pass") + Qt.quit() + }) + + QtObject { + id: testBar + property bool vertical: false + property int barSize: Style.bar.sizeHorizontal + property string fontFamily: Style.font.family + property color barForeground: "white" + property color urgent: "red" + property bool foregroundAnimationEnabled: false + function registerClickTarget(target) {} + function unregisterClickTarget(target) {} + function hideTooltip(target) {} + function showTooltip(target, text) {} + } + + BarIconButton { id: bluetooth; bar: testBar; text: "󰂯" } + BarIconButton { id: network; bar: testBar; text: "󰖩" } + BarIconButton { id: audio; bar: testBar; text: "󰖁" } + BarIconButton { id: monitor; bar: testBar; text: "󰍹" } + BarIconButton { id: power; bar: testBar; text: "󰁹" } + BarIconButton { + id: vector + bar: testBar + iconComponent: Component { Rectangle { width: 12; height: 12 } } + } +} +QML + +output=$(timeout 15 env \ + QML2_IMPORT_PATH="$ROOT/shell${QML2_IMPORT_PATH:+:$QML2_IMPORT_PATH}" \ + QML_IMPORT_PATH="$ROOT/shell${QML_IMPORT_PATH:+:$QML_IMPORT_PATH}" \ + quickshell -p "$test_tmp" --no-color 2>&1) || { + printf '%s\n' "$output" >&2 + fail "bar icon geometry fixture exits cleanly" +} + +if ! grep -q 'RESULT pass' <<<"$output"; then + printf '%s\n' "$output" >&2 + fail "bar icons share centered optical geometry" +fi + +pass "bar icons share centered optical geometry"