Align bar icons on a shared baseline

This commit is contained in:
David Heinemeier Hansson
2026-07-17 15:37:01 -07:00
parent 6e3b69b8da
commit 0c8c4ea26a
4 changed files with 33 additions and 24 deletions
+1
View File
@@ -343,6 +343,7 @@ QtObject {
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 iconSlot: root.barToken("icon-slot", 27)
readonly property int iconCanvas: root.barToken("icon-canvas", 16) readonly property int iconCanvas: root.barToken("icon-canvas", 16)
readonly property int iconFont: root.barToken("icon-font", 13)
} }
function refresh() { function refresh() {
+3 -2
View File
@@ -9,11 +9,12 @@ WidgetButton {
property real opticalSize: Style.bar.iconCanvas property real opticalSize: Style.bar.iconCanvas
property bool debugOpticalBounds: Quickshell.env("OMARCHY_DEBUG_BAR_ICONS") === "1" 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 opticalCenterErrorX: glyph.visible ? glyph.paintedCenterX - opticalCanvas.width / 2 : 0
readonly property real opticalCenterErrorY: glyph.visible ? glyph.paintedCenterY - opticalCanvas.height / 2 : 0 readonly property real glyphBaselineY: glyph.visible ? glyph.baselineY : 0
readonly property int glyphFontSize: glyph.visible ? glyph.renderedFontSize : 0
labelVisible: false labelVisible: false
hasVisualContent: text !== "" || iconComponent !== null hasVisualContent: text !== "" || iconComponent !== null
fontSize: Style.font.body fontSize: Style.bar.iconFont
fixedWidth: vertical ? -1 : Style.bar.iconSlot fixedWidth: vertical ? -1 : Style.bar.iconSlot
fixedHeight: vertical ? Style.bar.iconSlot : -1 fixedHeight: vertical ? Style.bar.iconSlot : -1
+15 -18
View File
@@ -8,43 +8,31 @@ Item {
property string fontFamily: Style.font.family property string fontFamily: Style.font.family
property real fontSize: Style.font.body property real fontSize: Style.font.body
property color color: Color.foreground property color color: Color.foreground
property real targetExtent: Math.min(width, height) * 0.82
property bool debugBounds: false property bool debugBounds: false
readonly property real baseTightWidth: Math.max(1, baseMetrics.tightBoundingRect.width) readonly property int renderedFontSize: Math.max(1, Math.round(fontSize))
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 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 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 paintedCenterX: glyph.x + glyphMetrics.tightBoundingRect.x + tightWidth / 2
readonly property real paintedCenterY: glyph.y + glyph.baselineOffset + glyphMetrics.tightBoundingRect.y + tightHeight / 2 readonly property real baselineY: glyph.y + glyph.baselineOffset
TextMetrics {
id: baseMetrics
font.family: root.fontFamily
font.pixelSize: root.fontSize
text: root.text
}
TextMetrics { TextMetrics {
id: glyphMetrics id: glyphMetrics
font.family: root.fontFamily font.family: root.fontFamily
font.pixelSize: root.normalizedFontSize font.pixelSize: root.renderedFontSize
text: root.text text: root.text
} }
Text { Text {
id: glyph id: glyph
// Keep the shared line box and baseline intact. Correcting only the
// horizontal painted bounds avoids per-glyph vertical drift.
anchors.centerIn: parent anchors.centerIn: parent
anchors.horizontalCenterOffset: root.horizontalCorrection anchors.horizontalCenterOffset: root.horizontalCorrection
anchors.verticalCenterOffset: root.verticalCorrection
text: root.text text: root.text
color: root.color color: root.color
font.family: root.fontFamily font.family: root.fontFamily
font.pixelSize: root.normalizedFontSize font.pixelSize: root.renderedFontSize
renderType: Text.NativeRendering renderType: Text.NativeRendering
} }
@@ -55,4 +43,13 @@ Item {
border.width: 1 border.width: 1
border.color: "#4488ff" border.color: "#4488ff"
} }
Rectangle {
visible: root.debugBounds
x: 0
y: Math.round(root.baselineY)
width: parent.width
height: 1
color: "#44ff88"
}
} }
+14 -4
View File
@@ -38,8 +38,12 @@ ShellRoot {
fail(name + " optical canvas is " + icon.opticalSize) fail(name + " optical canvas is " + icon.opticalSize)
return false return false
} }
if (Math.abs(icon.opticalCenterErrorX) > 0.5 || Math.abs(icon.opticalCenterErrorY) > 0.5) { if (Math.abs(icon.opticalCenterErrorX) > 0.5) {
fail(name + " painted bounds are over half a pixel off center by " + icon.opticalCenterErrorX + "," + icon.opticalCenterErrorY) fail(name + " painted bounds are over half a pixel off center by " + icon.opticalCenterErrorX)
return false
}
if (icon.glyphFontSize !== Style.bar.iconFont) {
fail(name + " font size is " + icon.glyphFontSize)
return false return false
} }
return true return true
@@ -51,6 +55,12 @@ ShellRoot {
if (!checkIcon(audio, "audio")) return if (!checkIcon(audio, "audio")) return
if (!checkIcon(monitor, "monitor")) return if (!checkIcon(monitor, "monitor")) return
if (!checkIcon(power, "power")) return if (!checkIcon(power, "power")) return
var baseline = bluetooth.glyphBaselineY
if (network.glyphBaselineY !== baseline || audio.glyphBaselineY !== baseline
|| monitor.glyphBaselineY !== baseline || power.glyphBaselineY !== baseline) {
fail("glyph baselines do not match")
return
}
if (vector.implicitWidth !== Style.bar.iconSlot || vector.opticalSize !== Style.bar.iconCanvas) { if (vector.implicitWidth !== Style.bar.iconSlot || vector.opticalSize !== Style.bar.iconCanvas) {
fail("vector icon does not share glyph geometry") fail("vector icon does not share glyph geometry")
return return
@@ -96,7 +106,7 @@ output=$(timeout 15 env \
if ! grep -q 'RESULT pass' <<<"$output"; then if ! grep -q 'RESULT pass' <<<"$output"; then
printf '%s\n' "$output" >&2 printf '%s\n' "$output" >&2
fail "bar icons share centered optical geometry" fail "bar icons share slot and baseline geometry"
fi fi
pass "bar icons share centered optical geometry" pass "bar icons share slot and baseline geometry"