Fix one-sided menu row borders

This commit is contained in:
Ryan Hughes
2026-06-04 12:11:41 -04:00
parent e5290b0a12
commit add570dac2
5 changed files with 69 additions and 18 deletions
+24 -12
View File
@@ -206,21 +206,33 @@ function ringPath(w, h, radius, widths) {
blrx: radius, blry: radius, blrx: radius, blry: radius,
}) })
var ix = widths.left || 0 // Shape's OddEven fill can collapse to the outer fill when the inner
var iy = widths.top || 0 // cutout touches the outer path on one or more zero-width sides. Keep the
var iw = w - ix - (widths.right || 0) // cutout strictly inside the outer path with a subpixel inset so one-sided
var ih = h - iy - (widths.bottom || 0) // borders (for example selected-border-width = "0 0 0 4") render as a
// strip instead of painting the whole row.
var epsilon = 0.001
var left = Math.max(0, widths.left || 0)
var top = Math.max(0, widths.top || 0)
var right = Math.max(0, widths.right || 0)
var bottom = Math.max(0, widths.bottom || 0)
var ix = Math.max(left, epsilon)
var iy = Math.max(top, epsilon)
var ir = Math.max(right, epsilon)
var ib = Math.max(bottom, epsilon)
var iw = w - ix - ir
var ih = h - iy - ib
if (iw <= 0 || ih <= 0) return outer if (iw <= 0 || ih <= 0) return outer
var inner = roundedRectPath(ix, iy, iw, ih, { var inner = roundedRectPath(ix, iy, iw, ih, {
tlrx: Math.max(0, radius - (widths.left || 0)), tlrx: Math.max(0, radius - left),
tlry: Math.max(0, radius - (widths.top || 0)), tlry: Math.max(0, radius - top),
trrx: Math.max(0, radius - (widths.right || 0)), trrx: Math.max(0, radius - right),
trry: Math.max(0, radius - (widths.top || 0)), trry: Math.max(0, radius - top),
brrx: Math.max(0, radius - (widths.right || 0)), brrx: Math.max(0, radius - right),
brry: Math.max(0, radius - (widths.bottom || 0)), brry: Math.max(0, radius - bottom),
blrx: Math.max(0, radius - (widths.left || 0)), blrx: Math.max(0, radius - left),
blry: Math.max(0, radius - (widths.bottom || 0)), blry: Math.max(0, radius - bottom),
}) })
return outer + " " + inner return outer + " " + inner
+4 -2
View File
@@ -43,6 +43,8 @@ Item {
property color selectedText: Color.launcher.selectedText property color selectedText: Color.launcher.selectedText
property color selectedBorder: Color.launcher.selectedBorder property color selectedBorder: Color.launcher.selectedBorder
property var selectedBorderSpec: Border.surfaceSpec("launcher", "selected-border", selectedBorder, 0) property var selectedBorderSpec: Border.surfaceSpec("launcher", "selected-border", selectedBorder, 0)
readonly property real rowReservedBorderLeft: Border.left(selectedBorderSpec)
readonly property real rowReservedBorderRight: Border.right(selectedBorderSpec)
property string fontFamily: Style.font.menuFamily property string fontFamily: Style.font.menuFamily
property int cardWidth: 644 property int cardWidth: 644
@@ -495,7 +497,7 @@ Item {
Item { Item {
id: iconSlot id: iconSlot
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: row.borderLeft + 14 anchors.leftMargin: root.rowReservedBorderLeft + 14
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: root.iconSlotWidth width: root.iconSlotWidth
height: parent.height height: parent.height
@@ -525,7 +527,7 @@ Item {
anchors.left: iconSlot.right anchors.left: iconSlot.right
anchors.leftMargin: 14 anchors.leftMargin: 14
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: row.borderRight + 14 anchors.rightMargin: root.rowReservedBorderRight + 14
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: row.name text: row.name
color: row.hasCursor ? root.selectedText : root.foreground color: row.hasCursor ? root.selectedText : root.foreground
+6 -4
View File
@@ -84,6 +84,8 @@ Item {
property color selectedText: Color.menu.selectedText property color selectedText: Color.menu.selectedText
property color selectedBorder: Color.menu.selectedBorder property color selectedBorder: Color.menu.selectedBorder
property var selectedBorderSpec: Border.surfaceSpec("menu", "selected-border", selectedBorder, 0) property var selectedBorderSpec: Border.surfaceSpec("menu", "selected-border", selectedBorder, 0)
readonly property real rowReservedBorderLeft: Border.left(selectedBorderSpec)
readonly property real rowReservedBorderRight: Border.right(selectedBorderSpec)
readonly property int cornerRadius: Style.cornerRadius readonly property int cornerRadius: Style.cornerRadius
property int contentMargin: Style.spacing.panelPadding property int contentMargin: Style.spacing.panelPadding
property int headerHeight: Math.max(Style.space(34), Style.font.title + Style.spacing.controlPaddingY * 2) property int headerHeight: Math.max(Style.space(34), Style.font.title + Style.spacing.controlPaddingY * 2)
@@ -933,7 +935,7 @@ Item {
radius: Math.min(root.cornerRadius, Style.space(4)) radius: Math.min(root.cornerRadius, Style.space(4))
color: root.selectedBackground color: root.selectedBackground
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: row.borderLeft + Style.space(8) anchors.leftMargin: root.rowReservedBorderLeft + Style.space(8)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
@@ -948,14 +950,14 @@ Item {
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: row.borderLeft + Style.space(8) anchors.leftMargin: root.rowReservedBorderLeft + Style.space(8)
y: contentColumn.y + labelText.y + (labelText.height - height) / 2 y: contentColumn.y + labelText.y + (labelText.height - height) / 2
} }
Column { Column {
id: contentColumn id: contentColumn
anchors.left: row.hasIcon ? iconText.right : parent.left anchors.left: row.hasIcon ? iconText.right : parent.left
anchors.leftMargin: row.hasIcon ? Style.space(6) : row.borderLeft + Style.space(18) anchors.leftMargin: row.hasIcon ? Style.space(6) : root.rowReservedBorderLeft + Style.space(18)
anchors.right: trail.left anchors.right: trail.left
anchors.rightMargin: Style.space(6) anchors.rightMargin: Style.space(6)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -988,7 +990,7 @@ Item {
id: trail id: trail
width: Style.space(14) width: Style.space(14)
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: row.borderRight + Style.space(8) anchors.rightMargin: root.rowReservedBorderRight + Style.space(8)
y: contentColumn.y + labelText.y + (labelText.height - height) / 2 y: contentColumn.y + labelText.y + (labelText.height - height) / 2
spacing: 0 spacing: 0
+4
View File
@@ -38,6 +38,10 @@ const pathData = geometry.ringPath(100, 50, 10, { top: 4, right: 2, bottom: 8, l
assert(pathData.includes('M 10 0'), 'border geometry emits outer rounded path') assert(pathData.includes('M 10 0'), 'border geometry emits outer rounded path')
assert(pathData.includes('M 10 4'), 'border geometry emits inset inner rounded path') assert(pathData.includes('M 10 4'), 'border geometry emits inset inner rounded path')
const oneSidedPath = geometry.ringPath(100, 50, 10, { top: 0, right: 0, bottom: 0, left: 4 })
assert(oneSidedPath.includes('M 10 0.001'), 'border geometry keeps zero-width top inside outer path')
assert(oneSidedPath.includes('99.999 10.001'), 'border geometry keeps zero-width right inside outer path')
const endpoints = geometry.gradientEndpoints(100, 50, 0) const endpoints = geometry.gradientEndpoints(100, 50, 0)
assertEqual(Math.round(endpoints.x1), 0, 'border geometry 0deg starts at left edge') assertEqual(Math.round(endpoints.x1), 0, 'border geometry 0deg starts at left edge')
assertEqual(Math.round(endpoints.x2), 100, 'border geometry 0deg ends at right edge') assertEqual(Math.round(endpoints.x2), 100, 'border geometry 0deg ends at right edge')
+31
View File
@@ -0,0 +1,31 @@
#!/bin/bash
source "$(dirname "$0")/base-test.sh"
run_node_test <<'JS'
const fs = require('fs')
const menuQml = fs.readFileSync(path.join(root, 'shell/plugins/menu/Menu.qml'), 'utf8')
const launcherQml = fs.readFileSync(path.join(root, 'shell/plugins/launcher/Launcher.qml'), 'utf8')
assert(
/rowReservedBorderLeft:\s*Border\.left\(selectedBorderSpec\)/.test(menuQml)
&& /rowReservedBorderRight:\s*Border\.right\(selectedBorderSpec\)/.test(menuQml),
'Menu rows reserve selected border insets'
)
assert(
!/anchors\.(left|right)Margin:[^\n]*\brow\.border(Left|Right)\b/.test(menuQml),
'Menu row content does not depend on current selected border state'
)
assert(
/rowReservedBorderLeft:\s*Border\.left\(selectedBorderSpec\)/.test(launcherQml)
&& /rowReservedBorderRight:\s*Border\.right\(selectedBorderSpec\)/.test(launcherQml),
'Launcher rows reserve selected border insets'
)
assert(
!/anchors\.(left|right)Margin:[^\n]*\brow\.border(Left|Right)\b/.test(launcherQml),
'Launcher row content does not depend on current selected border state'
)
JS