diff --git a/default/themed/shell.toml.tpl b/default/themed/shell.toml.tpl index fcc41aab..b0a68d8b 100644 --- a/default/themed/shell.toml.tpl +++ b/default/themed/shell.toml.tpl @@ -16,6 +16,13 @@ scale-with-font = true size-horizontal = 26 size-vertical = 28 +[hyprland] +# Shared Hyprland-derived border tokens. Surface sections reference these so +# lock, notifications, popups, and menu-style cards stay aligned with the +# current Hyprland active-border gradient. +active-border = "{{ shell_gradient hyprland_active_border accent }}" +active-border-foreground = "{{ shell_gradient hyprland_active_border foreground }}" + [controls] # Shared state tokens for interactive control chrome (buttons, dropdowns, # tab strips, etc). @@ -120,7 +127,7 @@ base-size = 12 background = "{{ background }}" background-alpha = 1.0 text = "{{ foreground }}" -border = "{{ shell_gradient hyprland_active_border accent }}" +border = "hyprland.active-border" border-alpha = 1.0 # border-width = 2 @@ -130,7 +137,7 @@ border-alpha = 1.0 background = "{{ background }}" background-alpha = 0.97 text = "{{ foreground }}" -border = "{{ shell_gradient hyprland_active_border foreground }}" +border = "hyprland.active-border-foreground" border-alpha = 1.0 [notifications] @@ -139,7 +146,7 @@ background-alpha = 1.0 text = "{{ foreground }}" # Conventionally matches the Hyprland active-window border. Border accepts # either a solid color or the full active-border gradient. -border = "{{ shell_gradient hyprland_active_border accent }}" +border = "hyprland.active-border" border-alpha = 1.0 # border-width = 2 countdown = "{{ accent }}" @@ -152,14 +159,14 @@ countdown = "{{ accent }}" background = "{{ background }}" background-alpha = 0.95 text = "{{ foreground }}" -border = "{{ shell_gradient hyprland_active_border foreground }}" +border = "hyprland.active-border-foreground" border-alpha = 1.0 scrim = "{{ background }}" scrim-alpha = 0.5 selected-background = "{{ foreground }}" selected-background-alpha = 0.08 selected-text = "{{ accent }}" -selected-border = "{{ shell_gradient hyprland_active_border foreground }}" +selected-border = "hyprland.active-border-foreground" selected-border-alpha = 0.25 [menu] @@ -169,14 +176,14 @@ selected-border-alpha = 0.25 background = "{{ background }}" background-alpha = 1.0 text = "{{ foreground }}" -border = "{{ shell_gradient hyprland_active_border foreground }}" +border = "hyprland.active-border-foreground" border-alpha = 1.0 scrim = "{{ background }}" scrim-alpha = 0.5 selected-background = "{{ foreground }}" selected-background-alpha = 0.08 selected-text = "{{ accent }}" -selected-border = "{{ shell_gradient hyprland_active_border foreground }}" +selected-border = "hyprland.active-border-foreground" selected-border-alpha = 0.25 [polkit] @@ -189,7 +196,7 @@ background = "{{ background }}" background-alpha = 1.0 text = "{{ foreground }}" text-error = "{{ red }}" -border = "{{ shell_gradient hyprland_active_border accent }}" +border = "hyprland.active-border" border-error = "{{ red }}" border-alpha = 1.0 scrim = "{{ background }}" @@ -208,8 +215,8 @@ background-alpha = 0.8 text = "{{ foreground }}" placeholder = "{{ mix foreground background 34% }}" text-error = "{{ red }}" -border = "{{ foreground }}" -border-active = "{{ accent }}" +border = "hyprland.active-border" +border-active = "hyprland.active-border" border-error = "{{ red }}" border-alpha = 1.0 # selection is the text-selection tint inside the input field. diff --git a/shell/Commons/Border.qml b/shell/Commons/Border.qml index aba6eb85..948aa782 100644 --- a/shell/Commons/Border.qml +++ b/shell/Commons/Border.qml @@ -34,6 +34,18 @@ QtObject { return "" } + function resolveValueRef(raw) { + var s = String(raw || "").replace(/^\s+|\s+$/g, "") + var seen = {} + while (s.match(/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/) && !seen[s]) { + seen[s] = true + var next = Color.shellValues[s] + if (next === undefined || next === null || String(next).length === 0) break + s = String(next).replace(/^\s+|\s+$/g, "") + } + return s + } + function alpha(section, key, fallback) { var raw = value(section, key) if (String(raw).length === 0) return fallback @@ -128,7 +140,7 @@ QtObject { function surfaceSpec(section, token, fallbackColor, fallbackWidth, alphaKey) { var opacity = alpha(section, alphaKey || token + "-alpha", 1.0) var legacyGradientRaw = valueOr(section, token === "border" ? ["border-gradient"] : [token + "-gradient", "border-gradient"]) - var resolved = borderValue(value(section, token), fallbackColor, opacity, legacyGradientRaw) + var resolved = borderValue(resolveValueRef(value(section, token)), fallbackColor, opacity, legacyGradientRaw) return { color: resolved.color, @@ -137,6 +149,26 @@ QtObject { } } + function hyprlandActiveSpec(fallbackColor, fallbackWidth) { + var raw = value("hyprland", "active-border") + var opacity = alpha("hyprland", "active-border-alpha", 1.0) + + // Existing generated themes predate [hyprland] and already keep the active + // border under [notifications]. Use it as the compatibility source until + // the next theme refresh writes the shared token. + if (String(raw).length === 0) { + raw = value("notifications", "border") + opacity = alpha("notifications", "border-alpha", opacity) + } + + var resolved = borderValue(resolveValueRef(raw), fallbackColor, opacity, "") + return { + color: resolved.color, + widths: Geometry.parseWidthSpec(value("hyprland", "active-border-width"), fallbackWidth), + gradient: resolved.gradient, + } + } + function controlPrefix(state) { if (state === "hover" || state === "hot") return "hover-cursor" return state || "normal" diff --git a/shell/Commons/Color.qml b/shell/Commons/Color.qml index f5531188..4c184d73 100644 --- a/shell/Commons/Color.qml +++ b/shell/Commons/Color.qml @@ -47,6 +47,7 @@ QtObject { function flatColor(value, fallback) { var token = firstColorToken(value) var role = String(token || "").replace(/^\s+|\s+$/g, "").toLowerCase() + if (root.shellValues[role] && root.shellValues[role] !== token) return flatColor(root.shellValues[role], fallback) if (role === "foreground" || role === "text") return root.foreground if (role === "accent") return root.accent if (role === "urgent") return root.urgent diff --git a/shell/plugins/lock/LockView.qml b/shell/plugins/lock/LockView.qml index 7295df0d..cd723514 100644 --- a/shell/plugins/lock/LockView.qml +++ b/shell/plugins/lock/LockView.qml @@ -25,9 +25,10 @@ Item { readonly property int passwordDotFontSize: Math.round(Style.font.heading * 1.33) readonly property int passwordDotLetterSpacing: Math.round(Style.font.heading * 0.19) readonly property bool showPasswordCursor: inputEnabled && !authenticatingPassword && failureMessage.length === 0 - readonly property string borderToken: failureMessage.length > 0 ? "border-error" : (authenticatingPassword ? "border-active" : "border") - readonly property color borderFallback: failureMessage.length > 0 ? Color.lock.borderError : (authenticatingPassword ? Color.lock.borderActive : Color.lock.border) - readonly property var inputBorderSpec: Border.surfaceSpec("lock", borderToken, borderFallback, root.outlineThickness, "border-alpha") + readonly property bool errorState: failureMessage.length > 0 + readonly property var inputBorderSpec: errorState + ? Border.surfaceSpec("lock", "border-error", Color.lock.borderError, root.outlineThickness, "border-alpha") + : Border.hyprlandActiveSpec(Color.accent, root.outlineThickness) signal submitPassword(string password) signal passwordTextEdited(string password) @@ -107,7 +108,7 @@ Item { anchors.centerIn: parent color: Color.lock.background borderSpec: root.inputBorderSpec - radius: 0 + radius: Style.cornerRadius clip: true TextInput {