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
singleton Color 1.0 Color.qml
singleton Style 1.0 Style.qml
@@ -1,4 +1,5 @@
import QtQuick
import qs.Commons
// Shared visual chrome for keyboard-and-mouse-navigable items inside a panel.
// Contract: items must NOT read `containsMouse` for color/border. Mouse
@@ -14,7 +15,7 @@ Rectangle {
property color foreground: "#cacccc"
property color fill: Qt.rgba(foreground.r, foreground.g, foreground.b, 0.18)
radius: 4
radius: Style.cornerRadius
color: (hasCursor || current) ? fill : "transparent"
border.width: hasCursor ? 1 : 0
border.color: foreground
@@ -193,7 +193,7 @@ PanelWindow {
color: Color.popups.background
border.color: Color.popups.border
border.width: 2
radius: 0
radius: Style.cornerRadius
opacity: root.open ? 1.0 : 0
Behavior on opacity {
NumberAnimation { duration: 140; easing.type: Easing.OutCubic }
@@ -1,4 +1,5 @@
import QtQuick
import qs.Commons
// Small (22×22 by default) icon button used at the right edge of panel rows
// for inline actions — forget network, confirm passphrase, unpair device,
@@ -27,7 +28,7 @@ Rectangle {
implicitWidth: size
implicitHeight: size
radius: 4
radius: Style.cornerRadius
color: mouse.containsMouse && root.enabled
? Qt.rgba(hoverColor.r, hoverColor.g, hoverColor.b, 0.20)
@@ -1,5 +1,6 @@
import QtQuick
import QtQuick.Controls
import qs.Commons
Rectangle {
id: root
@@ -58,7 +59,7 @@ Rectangle {
implicitWidth: row.implicitWidth + horizontalPadding * 2
implicitHeight: row.implicitHeight + verticalPadding * 2
radius: 4
radius: Style.cornerRadius
// Hot = mouse hover OR keyboard cursor. Pressed wins over both; otherwise
// hot beats `active` (so a cursor on an already-active pill still reads
@@ -119,7 +119,7 @@ PopupWindow {
color: Color.popups.background
border.color: Color.popups.border
border.width: 2
radius: 0
radius: Style.cornerRadius
opacity: root.open ? 1.0 : 0
Behavior on opacity {
@@ -43,7 +43,6 @@ Item {
readonly property string home: Quickshell.env("HOME")
readonly property string userConfigPath: home + "/.config/omarchy/shell.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 --------------------------------------------------
// Bar settings deliberately isn't a themable surface in shell.toml — it
@@ -54,22 +53,15 @@ Item {
property color urgent: Color.urgent
property string fontFamily: "monospace"
// Source-of-truth for the shell-wide corner radius. Mirrors what the menu
// reads from quickshell-menu.json so `omarchy style corners <sharp|round>`
// flips both surfaces together.
property int cornerRadius: 0
// ---------------- keyboard focus -----------------------------------------
// Keyboard navigation walks the bar form directly; this panel no longer has
// side navigation because it only edits the bar.
// 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
// Structural style tokens live on the shared Style singleton so toggling
// `omarchy style corners` and theme swaps update every consumer at once.
// Aliasing them as readonly properties keeps the existing inline component
// bindings (`root.cornerRadius`, `root.focusBorderColor`, ...) working
// without sprinkling Style.* across the file.
readonly property int cornerRadius: Style.cornerRadius
readonly property color focusBorderColor: Style.focusBorderColor
readonly property color focusFillColor: Style.focusFillColor
readonly property int focusBorderWidth: Style.focusBorderWidth
// Move activeFocus to a dedicated sink Item that lives outside the body
// tree. Just clearing focus on the previously focused descendant isn't
@@ -312,16 +304,6 @@ Item {
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 -----------------------------------------
readonly property var legacyWidgetMeta: ({
"omarchy": { name: "Omarchy menu", description: "Launches the Omarchy menu", category: "Compositor" },
@@ -486,14 +468,6 @@ Item {
onFileChanged: reload()
}
FileView {
path: root.styleStatePath
watchChanges: true
printErrors: false
onLoaded: root.loadStyleState(text())
onFileChanged: reload()
}
// ---------------- window -------------------------------------------------
FloatingWindow {
id: window