Standardize bar icon geometry

This commit is contained in:
David Heinemeier Hansson
2026-07-17 15:20:55 -07:00
parent fe48e83222
commit 6e3b69b8da
17 changed files with 272 additions and 124 deletions
+2
View File
@@ -341,6 +341,8 @@ QtObject {
readonly property QtObject bar: QtObject { readonly property QtObject bar: QtObject {
readonly property int sizeHorizontal: root.barToken("size-horizontal", 26) readonly property int sizeHorizontal: root.barToken("size-horizontal", 26)
readonly property int sizeVertical: root.barToken("size-vertical", 28) 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() { function refresh() {
+60
View File
@@ -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"
}
}
+58
View File
@@ -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"
}
}
+6 -6
View File
@@ -12,7 +12,6 @@ Item {
property color activeColor: bar ? bar.urgent : Color.urgent property color activeColor: bar ? bar.urgent : Color.urgent
property bool active: false property bool active: false
property real horizontalMargin: 8.5 property real horizontalMargin: 8.5
property real rightExtraMargin: 0
property real verticalPadding: 6 property real verticalPadding: 6
property real fixedWidth: -1 property real fixedWidth: -1
property real fixedHeight: -1 property real fixedHeight: -1
@@ -24,6 +23,8 @@ Item {
property bool pressable: true property bool pressable: true
property bool useActiveColor: true property bool useActiveColor: true
property bool maintainIndicatorReveal: false property bool maintainIndicatorReveal: false
property bool labelVisible: true
property bool hasVisualContent: text !== ""
property var revealHost: bar property var revealHost: bar
property string tooltipText: "" property string tooltipText: ""
property var registeredBar: null property var registeredBar: null
@@ -56,13 +57,12 @@ Item {
readonly property bool vertical: bar ? bar.vertical : false readonly property bool vertical: bar ? bar.vertical : false
readonly property int barSize: bar ? bar.barSize : Style.bar.sizeHorizontal readonly property int barSize: bar ? bar.barSize : Style.bar.sizeHorizontal
readonly property real scaledHorizontalMargin: Style.spaceReal(horizontalMargin) readonly property real scaledHorizontalMargin: Style.spaceReal(horizontalMargin)
readonly property real scaledRightExtraMargin: Style.spaceReal(rightExtraMargin)
readonly property real scaledVerticalPadding: Style.spaceReal(verticalPadding) readonly property real scaledVerticalPadding: Style.spaceReal(verticalPadding)
readonly property bool tooltipHovered: visible && interactive && !concealed && mouseArea.containsMouse readonly property bool tooltipHovered: visible && interactive && !concealed && mouseArea.containsMouse
visible: text !== "" || keepSpace visible: hasVisualContent || keepSpace
opacity: text === "" || concealed ? 0 : (dimmed ? 0.45 : 1) opacity: !hasVisualContent || concealed ? 0 : (dimmed ? 0.45 : 1)
implicitWidth: fixedWidth > 0 ? fixedWidth : (vertical ? barSize : Math.max(12, label.implicitWidth + scaledHorizontalMargin * 2 + scaledRightExtraMargin)) 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) implicitHeight: fixedHeight > 0 ? fixedHeight : (vertical ? Math.max(12, label.implicitHeight + scaledVerticalPadding * 2) : barSize)
Behavior on opacity { Behavior on opacity {
@@ -71,8 +71,8 @@ Item {
Text { Text {
id: label id: label
visible: root.labelVisible
anchors.centerIn: parent anchors.centerIn: parent
anchors.horizontalCenterOffset: root.vertical ? 0 : -root.scaledRightExtraMargin / 2
text: root.text text: root.text
color: root.active && root.useActiveColor ? root.activeColor : root.foreground color: root.active && root.useActiveColor ? root.activeColor : root.foreground
font.family: root.fontFamily font.family: root.fontFamily
+2
View File
@@ -1,6 +1,7 @@
module qs.Ui module qs.Ui
BarIndicator 1.0 BarIndicator.qml BarIndicator 1.0 BarIndicator.qml
BarIconButton 1.0 BarIconButton.qml
BarWidget 1.0 BarWidget.qml BarWidget 1.0 BarWidget.qml
BorderOverlay 1.0 BorderOverlay.qml BorderOverlay 1.0 BorderOverlay.qml
BorderSurface 1.0 BorderSurface.qml BorderSurface 1.0 BorderSurface.qml
@@ -12,6 +13,7 @@ Dropdown 1.0 Dropdown.qml
KeyboardPanel 1.0 KeyboardPanel.qml KeyboardPanel 1.0 KeyboardPanel.qml
MultiSelect 1.0 MultiSelect.qml MultiSelect 1.0 MultiSelect.qml
NumberField 1.0 NumberField.qml NumberField 1.0 NumberField.qml
OpticalGlyph 1.0 OpticalGlyph.qml
Panel 1.0 Panel.qml Panel 1.0 Panel.qml
PanelActionButton 1.0 PanelActionButton.qml PanelActionButton 1.0 PanelActionButton.qml
PanelController 1.0 PanelController.qml PanelController 1.0 PanelController.qml
+2 -9
View File
@@ -1297,13 +1297,6 @@ Item {
readonly property bool hovered: moduleHover.hovered readonly property bool hovered: moduleHover.hovered
readonly property bool dragSource: root.barDragSource === slot readonly property bool dragSource: root.barDragSource === slot
readonly property bool panelOpen: root.activePopout === slot.activeItem 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 implicitWidth: activeItem && activeItem.visible ? (root.vertical ? root.barSize : activeItem.implicitWidth) : 0
implicitHeight: activeItem && activeItem.visible ? activeItem.implicitHeight : 0 implicitHeight: activeItem && activeItem.visible ? activeItem.implicitHeight : 0
width: implicitWidth width: implicitWidth
@@ -1377,9 +1370,9 @@ Item {
height: root.vertical ? Math.max(Style.space(10), Math.round(parent.height * 0.55)) : Style.space(2) height: root.vertical ? Math.max(Style.space(10), Math.round(parent.height * 0.55)) : Style.space(2)
x: root.vertical x: root.vertical
? (root.position === "left" ? parent.width - width - inset : inset) ? (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 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) : (root.position === "top" ? parent.height - height - inset : inset)
z: 50 z: 50
+1 -1
View File
@@ -34,7 +34,7 @@ BarWidget {
PwObjectTracker { objects: root.source ? [root.source] : [] } PwObjectTracker { objects: root.source ? [root.source] : [] }
WidgetButton { BarIconButton {
id: button id: button
anchors.fill: parent anchors.fill: parent
bar: root.bar bar: root.bar
+1 -2
View File
@@ -52,12 +52,11 @@ BarWidget {
onTriggered: root.refresh() onTriggered: root.refresh()
} }
WidgetButton { BarIconButton {
id: button id: button
anchors.fill: parent anchors.fill: parent
bar: root.bar bar: root.bar
text: "\uf021" text: "\uf021"
fontSize: Style.font.caption
tooltipText: "" tooltipText: ""
onPressed: root.runUpdate() onPressed: root.runUpdate()
} }
+5 -9
View File
@@ -24,9 +24,9 @@ BarWidget {
readonly property var drawerItems: bucket("drawer") readonly property var drawerItems: bucket("drawer")
readonly property var allItems: bucket("all") readonly property var allItems: bucket("all")
readonly property int drawerCount: drawerItems.length readonly property int drawerCount: drawerItems.length
readonly property int trayItemExtent: Style.space(16) readonly property int trayItemExtent: Style.bar.iconSlot
readonly property int trayItemGap: Style.space(9) readonly property int trayItemGap: 0
readonly property int trayJoinGap: Style.space(4) readonly property int trayJoinGap: 0
readonly property int drawerExtent: drawerCount > 0 ? drawerCount * trayItemExtent + (drawerCount - 1) * trayItemGap : 0 readonly property int drawerExtent: drawerCount > 0 ? drawerCount * trayItemExtent + (drawerCount - 1) * trayItemGap : 0
// Match Waybar's group/tray-expander drawer transition-duration. // Match Waybar's group/tray-expander drawer transition-duration.
readonly property int animationDuration: 600 readonly property int animationDuration: 600
@@ -182,15 +182,13 @@ BarWidget {
onHoveredChanged: root.expanded = hovered onHoveredChanged: root.expanded = hovered
} }
WidgetButton { BarIconButton {
id: expandIcon id: expandIcon
bar: root.bar bar: root.bar
width: implicitWidth width: implicitWidth
height: implicitHeight height: implicitHeight
x: root.drawerExtent - root.revealExtent x: root.drawerExtent - root.revealExtent
text: "\uf053" text: "\uf053"
horizontalMargin: 9
verticalPadding: 6
onPressed: function(button) { onPressed: function(button) {
if (button === Qt.RightButton) root.managePopupOpen = !root.managePopupOpen if (button === Qt.RightButton) root.managePopupOpen = !root.managePopupOpen
} }
@@ -266,7 +264,7 @@ BarWidget {
onHoveredChanged: root.expanded = hovered onHoveredChanged: root.expanded = hovered
} }
WidgetButton { BarIconButton {
id: expandIcon id: expandIcon
bar: root.bar bar: root.bar
width: implicitWidth width: implicitWidth
@@ -274,8 +272,6 @@ BarWidget {
y: root.drawerExtent - root.revealExtent y: root.drawerExtent - root.revealExtent
text: "\uf053" text: "\uf053"
textRotation: 90 textRotation: 90
horizontalMargin: 9
verticalPadding: 6
onPressed: function(button) { onPressed: function(button) {
if (button === Qt.RightButton) root.managePopupOpen = !root.managePopupOpen if (button === Qt.RightButton) root.managePopupOpen = !root.managePopupOpen
} }
+1 -4
View File
@@ -522,14 +522,11 @@ Panel {
onTriggered: root.refreshDisplayAudioModels() onTriggered: root.refreshDisplayAudioModels()
} }
WidgetButton { BarIconButton {
id: button id: button
anchors.fill: parent anchors.fill: parent
bar: root.bar bar: root.bar
text: root.outputIcon() 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) { onPressed: function(b) {
if (b === Qt.RightButton) root.toggleOutputMute() if (b === Qt.RightButton) root.toggleOutputMute()
else root.toggle() else root.toggle()
+1 -3
View File
@@ -469,13 +469,11 @@ Panel {
}) })
} }
WidgetButton { BarIconButton {
id: button id: button
anchors.fill: parent anchors.fill: parent
bar: root.bar bar: root.bar
text: root.icon 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) { onPressed: function(b) {
if (b === Qt.RightButton) root.toggleBluetooth() if (b === Qt.RightButton) root.toggleBluetooth()
else if (b === Qt.MiddleButton) root.bar.run("omarchy-launch-bluetooth") else if (b === Qt.MiddleButton) root.bar.run("omarchy-launch-bluetooth")
+13 -36
View File
@@ -139,48 +139,25 @@ Panel {
function status(): string { return dropbox.statusText } function status(): string { return dropbox.statusText }
} }
Item { BarIconButton {
id: button id: button
anchors.fill: parent anchors.fill: parent
implicitWidth: root.bar && root.bar.vertical ? root.bar.barSize : Style.space(27) bar: root.bar
implicitHeight: root.bar && root.bar.vertical ? Style.space(26) : (root.bar ? root.bar.barSize : Style.space(26)) iconComponent: Component {
Item {
property var registeredBar: null DropboxIcon {
anchors.centerIn: parent
function triggerPress(buttonCode) { iconSize: Style.space(12)
color: root.barIconColor
opacity: dropbox.authenticated ? 1.0 : 0.6
}
}
}
onPressed: function(buttonCode) {
if (buttonCode === Qt.RightButton) dropbox.refresh() if (buttonCode === Qt.RightButton) dropbox.refresh()
else if (buttonCode === Qt.MiddleButton) dropbox.login() else if (buttonCode === Qt.MiddleButton) dropbox.login()
else root.toggle() 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 { KeyboardPanel {
+1 -5
View File
@@ -337,15 +337,11 @@ Panel {
onRunningChanged: if (!running) root.refresh() onRunningChanged: if (!running) root.refresh()
} }
WidgetButton { BarIconButton {
id: button id: button
anchors.fill: parent anchors.fill: parent
bar: root.bar bar: root.bar
text: root.displays.length > 1 ? "󰍺" : "󰍹" 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() } onPressed: function(b) { root.toggle() }
onWheelMoved: function(delta) { onWheelMoved: function(delta) {
if (root.brightnessAvailable) root.setBrightness(root.brightnessPercent + (delta > 0 ? 5 : -5)) if (root.brightnessAvailable) root.setBrightness(root.brightnessPercent + (delta > 0 ? 5 : -5))
+1 -4
View File
@@ -707,14 +707,11 @@ Panel {
} }
} }
WidgetButton { BarIconButton {
id: button id: button
anchors.fill: parent anchors.fill: parent
bar: root.bar bar: root.bar
text: root.icon 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) { onPressed: function(b) {
if (root.opened) root.close() if (root.opened) root.close()
+1 -3
View File
@@ -249,13 +249,11 @@ Panel {
} }
} }
WidgetButton { BarIconButton {
id: button id: button
anchors.fill: parent anchors.fill: parent
bar: root.bar bar: root.bar
text: root.batteryIcon() text: root.batteryIcon()
fixedWidth: root.bar && root.bar.vertical ? -1 : Style.space(27)
fixedHeight: root.bar && root.bar.vertical ? Style.space(26) : -1
tooltipText: "" tooltipText: ""
onPressed: function(b) { if (root.batteryPresent) root.toggle() } onPressed: function(b) { if (root.batteryPresent) root.toggle() }
} }
+15 -42
View File
@@ -12,7 +12,6 @@ Panel {
ipcTarget: "omarchy.tailscale" ipcTarget: "omarchy.tailscale"
manageIpc: false manageIpc: false
readonly property real openIndicatorInlineOffset: bar && bar.vertical ? 0 : Style.spaceReal(1.5)
property string focusSection: "header" property string focusSection: "header"
property int headerIndex: 0 property int headerIndex: 0
property int accountIndex: 0 property int accountIndex: 0
@@ -360,53 +359,27 @@ Panel {
function status(): string { return tailscale.statusText } function status(): string { return tailscale.statusText }
} }
Item { BarIconButton {
id: button id: button
anchors.fill: parent anchors.fill: parent
implicitWidth: root.bar && root.bar.vertical ? root.bar.barSize : Style.space(27) bar: root.bar
implicitHeight: root.bar && root.bar.vertical ? Style.space(26) : (root.bar ? root.bar.barSize : Style.space(26)) iconComponent: Component {
Item {
property var registeredBar: null TailscaleIcon {
anchors.centerIn: parent
function triggerPress(buttonCode) { 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() if (buttonCode === Qt.RightButton) tailscale.toggleTailscale()
else if (buttonCode === Qt.MiddleButton) tailscale.refresh() else if (buttonCode === Qt.MiddleButton) tailscale.refresh()
else root.toggle() 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 { KeyboardPanel {
+102
View File
@@ -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"