From 877c5d02f89a25fe6566beeb5f780eae7973aba9 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Tue, 19 May 2026 15:34:17 -0400 Subject: [PATCH] Add color tokens for menu --- default/themed/shell.toml.tpl | 16 +++++++++++++--- shell/Commons/Color.qml | 31 ++++++++++++++++++++++++++----- shell/plugins/menu/Menu.qml | 25 ++++++++++++++----------- 3 files changed, 53 insertions(+), 19 deletions(-) diff --git a/default/themed/shell.toml.tpl b/default/themed/shell.toml.tpl index 8569cf6a..78e2bf5d 100644 --- a/default/themed/shell.toml.tpl +++ b/default/themed/shell.toml.tpl @@ -89,9 +89,19 @@ border = "{{ accent }}" countdown = "{{ accent }}" [menu] -background = "{{ background }}" -text = "{{ foreground }}" -selected = "{{ accent }}" +# Cards, rows, and selected-row treatment. Alpha companions (where present) +# go from 0 (invisible) to 1 (opaque). Defaults mirror the panel keyboard +# cursor: a subtle foreground-tinted fill on the selected row, no visible +# border, accent-colored text. Override any of these per-theme. +background = "{{ background }}" +text = "{{ foreground }}" +border = "{{ foreground }}" +border-alpha = 1.0 +selected-background = "{{ foreground }}" +selected-background-alpha = 0.08 +selected-text = "{{ accent }}" +selected-border = "{{ foreground }}" +selected-border-alpha = 0.25 [image-picker] # Drawn at ~70% alpha as a scrim diff --git a/shell/Commons/Color.qml b/shell/Commons/Color.qml index 50c209e8..176d09a8 100644 --- a/shell/Commons/Color.qml +++ b/shell/Commons/Color.qml @@ -30,6 +30,14 @@ QtObject { return (typeof v === "string" && v.length > 0) ? v : fallback } + function pickAlpha(key, fallback) { + var v = shellValues[key] + if (typeof v !== "string" || v.length === 0) return fallback + var n = Number(v) + if (!isFinite(n)) return fallback + return Math.max(0, Math.min(1, n)) + } + // Surface roles. Each property reads its shell.toml override if set, // otherwise falls back to a foundational palette token. readonly property QtObject bar: QtObject { @@ -55,7 +63,16 @@ QtObject { readonly property QtObject menu: QtObject { property color background: root.pick("menu.background", root.background) property color text: root.pick("menu.text", root.foreground) - property color selected: root.pick("menu.selected", root.accent) + property color border: root.pick("menu.border", root.foreground) + property real borderAlpha: root.pickAlpha("menu.border-alpha", 1.0) + // Defaults mirror the panel cursor: a subtle foreground-tint fill, + // no visible border, accent text. Themes override any of these + // (including the alpha companions) per surface. + property color selectedBackground: root.pick("menu.selected-background", root.pick("menu.selected", root.foreground)) + property real selectedBackgroundAlpha: root.pickAlpha("menu.selected-background-alpha", 0.08) + property color selectedText: root.pick("menu.selected-text", root.accent) + property color selectedBorder: root.pick("menu.selected-border", root.foreground) + property real selectedBorderAlpha: root.pickAlpha("menu.selected-border-alpha", 0.0) } readonly property QtObject imagePicker: QtObject { property color background: root.pick("image-picker.background", root.background) @@ -89,9 +106,11 @@ QtObject { if (!foundAccent && color4Value.length > 0) accent = color4Value } - // Walk shell.toml line-by-line. We only need string values for color keys, - // and the file is small, so no proper TOML parser. Accepts double- or - // single-quoted values and tolerates trailing inline comments. + // Walk shell.toml line-by-line. The file is small, so no proper TOML + // parser. Accepts double- or single-quoted strings for colors and bare + // numeric values (e.g. alpha companions like `selected-background-alpha + // = 0.08`), and tolerates trailing inline comments. Numbers are kept as + // strings here; pickAlpha() coerces and clamps when read. function loadShell(raw) { var parsed = {} var text = String(raw || "") @@ -103,7 +122,9 @@ QtObject { if (!line || line.charAt(0) === "#") continue var sectionMatch = line.match(/^\[([A-Za-z0-9_-]+)\]\s*(#.*)?$/) if (sectionMatch) { section = sectionMatch[1]; continue } - var kv = line.match(/^([A-Za-z0-9_-]+)\s*=\s*["']([^"']+)["']\s*(#.*)?$/) + var stringKv = line.match(/^([A-Za-z0-9_-]+)\s*=\s*["']([^"']+)["']\s*(#.*)?$/) + var numKv = line.match(/^([A-Za-z0-9_-]+)\s*=\s*(-?\d+(?:\.\d+)?)\s*(#.*)?$/) + var kv = stringKv || numKv if (!kv || !section) continue parsed[section + "." + kv[1]] = kv[2] } diff --git a/shell/plugins/menu/Menu.qml b/shell/plugins/menu/Menu.qml index ddeb6512..7d15edd9 100644 --- a/shell/plugins/menu/Menu.qml +++ b/shell/plugins/menu/Menu.qml @@ -62,11 +62,14 @@ Item { property var navStack: [] property var providersLoaded: ({}) property var providerQueue: [] - // Bound to the central [menu] section in shell.toml via Color.qml. - property color accent: Color.menu.selected + // Bound to the central [menu] section in shell.toml via Color.qml. Each + // surface color composes with its alpha companion at render time. property color background: Color.menu.background property color foreground: Color.menu.text - property color border: foreground + property color border: Color.alpha(Color.menu.border, Color.menu.borderAlpha) + property color selectedBackground: Color.alpha(Color.menu.selectedBackground, Color.menu.selectedBackgroundAlpha) + property color selectedText: Color.menu.selectedText + property color selectedBorder: Color.alpha(Color.menu.selectedBorder, Color.menu.selectedBorderAlpha) readonly property int cornerRadius: Style.cornerRadius property int contentMargin: Style.spacing.panelPadding property int headerHeight: Math.max(Style.space(34), Style.font.title + Style.spacing.controlPaddingY * 2) @@ -1078,16 +1081,16 @@ Item { width: ListView.view.width height: root.rowHeightForDetail(row.detail) radius: root.cornerRadius - color: row.hasCursor ? Style.hoverFillFor(root.foreground, root.accent) : "transparent" - border.color: row.hasCursor ? Style.hoverBorderFor(root.foreground, root.accent) : "transparent" - border.width: row.hasCursor ? Style.hoverBorderWidth : 0 + color: row.hasCursor ? root.selectedBackground : "transparent" + border.color: row.hasCursor ? root.selectedBorder : "transparent" + border.width: (row.hasCursor && Color.menu.selectedBorderAlpha > 0) ? Style.hoverBorderWidth : 0 Rectangle { visible: false width: Style.space(4) height: parent.height - Style.space(18) radius: Math.min(root.cornerRadius, Style.space(4)) - color: root.accent + color: root.selectedBackground anchors.left: parent.left anchors.leftMargin: Style.space(8) anchors.verticalCenter: parent.verticalCenter @@ -1096,7 +1099,7 @@ Item { Text { id: iconText text: row.icon - color: row.hasCursor ? Style.hoverStateColor(root.foreground, root.accent) : root.foreground + color: row.hasCursor ? root.selectedText : root.foreground opacity: row.kind === "back" ? 0.7 : 1 font.family: root.fontFamily font.pixelSize: Style.font.iconLarge @@ -1121,7 +1124,7 @@ Item { id: labelText width: parent.width text: row.label - color: row.hasCursor ? Style.hoverStateColor(root.foreground, root.accent) : root.foreground + color: row.hasCursor ? root.selectedText : root.foreground font.family: root.fontFamily font.pixelSize: Style.font.heading font.weight: Font.Medium @@ -1160,7 +1163,7 @@ Item { Text { text: row.kind === "menu" || row.kind === "link" ? "›" : "" - color: row.hasCursor ? Style.hoverStateColor(root.foreground, root.accent) : root.foreground + color: row.hasCursor ? root.selectedText : root.foreground opacity: row.kind === "menu" || row.kind === "link" ? 0.36 : 0 font.family: root.fontFamily font.pixelSize: Style.font.heading @@ -1190,7 +1193,7 @@ Item { Text { text: "󰈉" - color: root.accent + color: root.selectedBackground opacity: 0.8 font.family: root.fontFamily font.pixelSize: Style.font.displayLarge