Promote cornerRadius + focus tokens to qs.Commons.Style singleton

This commit is contained in:
Ryan Hughes
2026-05-17 23:08:08 -04:00
parent 2167bdbe95
commit 5e3659fb8d
8 changed files with 69 additions and 40 deletions
@@ -0,0 +1,51 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
// Shared structural style tokens for the shell. Color is the palette
// singleton; Style holds the *shape* and *focus-affordance* tokens that
// every panel surface and qs.Ui component should bind to so they stay
// in sync as the user toggles round/sharp corners or as themes change.
//
// `cornerRadius` is mirrored from ~/.local/state/omarchy/toggles/quickshell-menu.json.
// `omarchy style corners <round|sharp>` writes that file; we watch it and
// hot-reload the binding here so every consumer rerenders.
QtObject {
id: root
property int cornerRadius: 0
// Focus affordances. Deliberately distinct from "selected" (which uses an
// accent fill) so the keyboard cursor never reads as the chosen value.
// The settings panel originated these tokens; promoting them here means
// every Ui component picks them up uniformly.
readonly property color focusBorderColor: Color.accent
readonly property color focusFillColor: Qt.rgba(Color.accent.r, Color.accent.g, Color.accent.b, 0.22)
readonly property int focusBorderWidth: 3
// Convenience: the standard "hot" (hover or keyboard cursor) tint used by
// PillButton, PanelActionButton, etc. Foreground at 0.12 alpha matches the
// value PillButton was already painting before promotion.
readonly property color hotFill: Qt.rgba(Color.foreground.r, Color.foreground.g, Color.foreground.b, 0.12)
function loadStyleState(raw) {
try {
var s = JSON.parse(raw || "{}")
var n = Number(s.radius)
cornerRadius = isFinite(n) ? n : 0
} catch (e) {
cornerRadius = 0
}
}
property FileView styleStateFile: FileView {
id: styleStateFile
path: Quickshell.env("HOME") + "/.local/state/omarchy/toggles/quickshell-menu.json"
watchChanges: true
printErrors: false
onLoaded: root.loadStyleState(text())
onLoadFailed: root.loadStyleState("")
onFileChanged: reload()
}
}
@@ -1,2 +1,3 @@
module qs.Commons module qs.Commons
singleton Color 1.0 Color.qml singleton Color 1.0 Color.qml
singleton Style 1.0 Style.qml
@@ -1,4 +1,5 @@
import QtQuick import QtQuick
import qs.Commons
// Shared visual chrome for keyboard-and-mouse-navigable items inside a panel. // Shared visual chrome for keyboard-and-mouse-navigable items inside a panel.
// Contract: items must NOT read `containsMouse` for color/border. Mouse // Contract: items must NOT read `containsMouse` for color/border. Mouse
@@ -14,7 +15,7 @@ Rectangle {
property color foreground: "#cacccc" property color foreground: "#cacccc"
property color fill: Qt.rgba(foreground.r, foreground.g, foreground.b, 0.18) property color fill: Qt.rgba(foreground.r, foreground.g, foreground.b, 0.18)
radius: 4 radius: Style.cornerRadius
color: (hasCursor || current) ? fill : "transparent" color: (hasCursor || current) ? fill : "transparent"
border.width: hasCursor ? 1 : 0 border.width: hasCursor ? 1 : 0
border.color: foreground border.color: foreground
@@ -193,7 +193,7 @@ PanelWindow {
color: Color.popups.background color: Color.popups.background
border.color: Color.popups.border border.color: Color.popups.border
border.width: 2 border.width: 2
radius: 0 radius: Style.cornerRadius
opacity: root.open ? 1.0 : 0 opacity: root.open ? 1.0 : 0
Behavior on opacity { Behavior on opacity {
NumberAnimation { duration: 140; easing.type: Easing.OutCubic } NumberAnimation { duration: 140; easing.type: Easing.OutCubic }
@@ -1,4 +1,5 @@
import QtQuick import QtQuick
import qs.Commons
// Small (22×22 by default) icon button used at the right edge of panel rows // Small (22×22 by default) icon button used at the right edge of panel rows
// for inline actions — forget network, confirm passphrase, unpair device, // for inline actions — forget network, confirm passphrase, unpair device,
@@ -27,7 +28,7 @@ Rectangle {
implicitWidth: size implicitWidth: size
implicitHeight: size implicitHeight: size
radius: 4 radius: Style.cornerRadius
color: mouse.containsMouse && root.enabled color: mouse.containsMouse && root.enabled
? Qt.rgba(hoverColor.r, hoverColor.g, hoverColor.b, 0.20) ? Qt.rgba(hoverColor.r, hoverColor.g, hoverColor.b, 0.20)
@@ -1,5 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import qs.Commons
Rectangle { Rectangle {
id: root id: root
@@ -58,7 +59,7 @@ Rectangle {
implicitWidth: row.implicitWidth + horizontalPadding * 2 implicitWidth: row.implicitWidth + horizontalPadding * 2
implicitHeight: row.implicitHeight + verticalPadding * 2 implicitHeight: row.implicitHeight + verticalPadding * 2
radius: 4 radius: Style.cornerRadius
// Hot = mouse hover OR keyboard cursor. Pressed wins over both; otherwise // Hot = mouse hover OR keyboard cursor. Pressed wins over both; otherwise
// hot beats `active` (so a cursor on an already-active pill still reads // hot beats `active` (so a cursor on an already-active pill still reads
@@ -119,7 +119,7 @@ PopupWindow {
color: Color.popups.background color: Color.popups.background
border.color: Color.popups.border border.color: Color.popups.border
border.width: 2 border.width: 2
radius: 0 radius: Style.cornerRadius
opacity: root.open ? 1.0 : 0 opacity: root.open ? 1.0 : 0
Behavior on opacity { Behavior on opacity {
@@ -43,7 +43,6 @@ Item {
readonly property string home: Quickshell.env("HOME") readonly property string home: Quickshell.env("HOME")
readonly property string userConfigPath: home + "/.config/omarchy/shell.json" readonly property string userConfigPath: home + "/.config/omarchy/shell.json"
readonly property string defaultsPath: omarchyPath + "/default/quickshell/omarchy-shell/shell-defaults.json" readonly property string defaultsPath: omarchyPath + "/default/quickshell/omarchy-shell/shell-defaults.json"
readonly property string styleStatePath: home + "/.local/state/omarchy/toggles/quickshell-menu.json"
// ---------------- theme -------------------------------------------------- // ---------------- theme --------------------------------------------------
// Bar settings deliberately isn't a themable surface in shell.toml — it // Bar settings deliberately isn't a themable surface in shell.toml — it
@@ -54,22 +53,15 @@ Item {
property color urgent: Color.urgent property color urgent: Color.urgent
property string fontFamily: "monospace" property string fontFamily: "monospace"
// Source-of-truth for the shell-wide corner radius. Mirrors what the menu // Structural style tokens live on the shared Style singleton so toggling
// reads from quickshell-menu.json so `omarchy style corners <sharp|round>` // `omarchy style corners` and theme swaps update every consumer at once.
// flips both surfaces together. // Aliasing them as readonly properties keeps the existing inline component
property int cornerRadius: 0 // bindings (`root.cornerRadius`, `root.focusBorderColor`, ...) working
// without sprinkling Style.* across the file.
// ---------------- keyboard focus ----------------------------------------- readonly property int cornerRadius: Style.cornerRadius
// Keyboard navigation walks the bar form directly; this panel no longer has readonly property color focusBorderColor: Style.focusBorderColor
// side navigation because it only edits the bar. readonly property color focusFillColor: Style.focusFillColor
readonly property int focusBorderWidth: Style.focusBorderWidth
// Focus visuals — deliberately *different* from selected styling so the
// keyboard cursor never gets confused with the current value/choice. A
// selected control gets a 2px accent border; a focused control gets a 3px
// accent border plus a noticeably tinted accent background.
readonly property color focusBorderColor: accent
readonly property color focusFillColor: Qt.rgba(accent.r, accent.g, accent.b, 0.22)
readonly property int focusBorderWidth: 3
// Move activeFocus to a dedicated sink Item that lives outside the body // Move activeFocus to a dedicated sink Item that lives outside the body
// tree. Just clearing focus on the previously focused descendant isn't // tree. Just clearing focus on the previously focused descendant isn't
@@ -312,16 +304,6 @@ Item {
mutateSection(section, function(a) { a[index] = cloneJson(newEntry) }) mutateSection(section, function(a) { a[index] = cloneJson(newEntry) })
} }
function loadStyleState(raw) {
try {
var s = JSON.parse(raw || "{}")
var n = Number(s.radius)
cornerRadius = isFinite(n) ? n : 0
} catch (e) {
cornerRadius = 0
}
}
// ---------------- widget catalog ----------------------------------------- // ---------------- widget catalog -----------------------------------------
readonly property var legacyWidgetMeta: ({ readonly property var legacyWidgetMeta: ({
"omarchy": { name: "Omarchy menu", description: "Launches the Omarchy menu", category: "Compositor" }, "omarchy": { name: "Omarchy menu", description: "Launches the Omarchy menu", category: "Compositor" },
@@ -486,14 +468,6 @@ Item {
onFileChanged: reload() onFileChanged: reload()
} }
FileView {
path: root.styleStatePath
watchChanges: true
printErrors: false
onLoaded: root.loadStyleState(text())
onFileChanged: reload()
}
// ---------------- window ------------------------------------------------- // ---------------- window -------------------------------------------------
FloatingWindow { FloatingWindow {
id: window id: window