From 8b2bb217d692e8c743c5fee49d6e969aab6e5e34 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Mon, 18 May 2026 20:13:12 -0400 Subject: [PATCH] Standardize shell UI theme tokens --- default/hypr/apps/omarchy-shell.lua | 4 + default/themed/shell.toml.tpl | 47 ++- docs/omarchy-shell.md | 93 ++++- shell/Commons/Style.qml | 395 +++++++++++++++--- shell/Ui/Button.qml | 72 ++-- shell/Ui/ButtonGroup.qml | 82 +++- shell/Ui/CursorSurface.qml | 38 +- shell/Ui/Dropdown.qml | 61 +-- shell/Ui/KeyboardPanel.qml | 34 +- shell/Ui/NumberField.qml | 35 +- shell/Ui/PanelActionButton.qml | 25 +- shell/Ui/PanelSlider.qml | 18 +- shell/Ui/PanelToolTip.qml | 12 +- shell/Ui/PopupCard.qml | 37 +- shell/Ui/SearchableDropdown.qml | 76 ++-- shell/Ui/TextField.qml | 34 +- shell/Ui/Toggle.qml | 70 ++-- shell/Ui/WidgetButton.qml | 9 +- shell/plugins/bar/Bar.qml | 36 +- shell/plugins/bar/widgets/activeWindow.qml | 6 +- shell/plugins/bar/widgets/audioPanel.qml | 96 ++--- shell/plugins/bar/widgets/bluetoothPanel.qml | 34 +- shell/plugins/bar/widgets/calendar.qml | 22 +- shell/plugins/bar/widgets/lockKeys.qml | 8 +- shell/plugins/bar/widgets/media.qml | 46 +- shell/plugins/bar/widgets/monitorPanel.qml | 32 +- shell/plugins/bar/widgets/networkPanel.qml | 70 ++-- .../bar/widgets/notificationCenter.qml | 71 ++-- shell/plugins/bar/widgets/systemStats.qml | 12 +- shell/plugins/bar/widgets/weatherFlyout.qml | 54 ++- .../clipboard-picker/ClipboardPicker.qml | 47 ++- shell/plugins/dev-gallery/GalleryPanel.qml | 252 ++++++----- shell/plugins/emoji-picker/EmojiPicker.qml | 23 +- shell/plugins/image-picker/ImagePicker.qml | 8 +- shell/plugins/menu/Menu.qml | 67 +-- shell/plugins/notifications/Service.qml | 14 +- .../components/NotificationCard.qml | 30 +- shell/plugins/osd/Osd.qml | 22 +- shell/plugins/polkit/PolkitAgent.qml | 22 +- shell/plugins/settings/SettingsPanel.qml | 320 +++++++++----- .../plugins/settings/components/NDropdown.qml | 46 +- shell/shell.qml | 7 + shell/ui/settings/DynamicSettingsForm.qml | 8 +- 43 files changed, 1622 insertions(+), 873 deletions(-) diff --git a/default/hypr/apps/omarchy-shell.lua b/default/hypr/apps/omarchy-shell.lua index 62d9f834..9fa499de 100644 --- a/default/hypr/apps/omarchy-shell.lua +++ b/default/hypr/apps/omarchy-shell.lua @@ -17,6 +17,10 @@ hl.window_rule({ match = { class = "^org.quickshell$", title = "^Omarchy Bar Set hl.window_rule({ match = { class = "^org.quickshell$", title = "^Omarchy Bar Settings$" }, center = true }) hl.window_rule({ match = { class = "^org.quickshell$", title = "^Omarchy Bar Settings$" }, size = { 760, 620 } }) +-- Dev gallery is the main shell workbench; open it maximized like +-- SUPER+ALT+F so component previews have the whole workspace. +hl.window_rule({ match = { class = "^org.quickshell$", title = "^Omarchy shell – dev gallery$" }, maximize = true }) + -- Per-widget settings dialog opens as a smaller FloatingWindow off the -- bar settings panel; keep it floating with its own default size. hl.window_rule({ match = { class = "^org.quickshell$", title = "^Widget settings " }, float = true }) diff --git a/default/themed/shell.toml.tpl b/default/themed/shell.toml.tpl index 8c5bec3a..2ee95dce 100644 --- a/default/themed/shell.toml.tpl +++ b/default/themed/shell.toml.tpl @@ -13,21 +13,46 @@ size-horizontal = 26 size-vertical = 28 [style] -# State alphas used by every interactive surface in the kit (Button, -# Toggle, TextField, etc.). Foreground-tinted unless noted. -border-width = 1 # idle 1px border on inputs and bordered buttons -focus-border-width = 3 # accent ring on Tab focus -idle-border-alpha = 0.4 # alpha for the idle 1px foreground border -hot-fill-alpha = 0.08 # cursor / hover fill -selected-fill-alpha = 0.18 # selected / active / current fill -pressed-fill-alpha = 0.22 # button pressed -focus-fill-alpha = 0.22 # accent fill behind Tab focus ring +# Shared control state tokens. See docs/omarchy-shell.md#interactive-states. +# Colors accept palette roles (foreground/accent/urgent/background) or hex. + +# Normal: idle control chrome. +normal-color = "foreground" +normal-fill-alpha = 0.04 +normal-border-width = 1 +normal-border-alpha = 0.4 + +# Hover-cursor: mouse hover and the panel keyboard cursor. +hover-cursor-color = "foreground" +hover-cursor-fill-alpha = 0.08 +hover-cursor-border-width = 1 +hover-cursor-border-alpha = 1.0 + +# Selected: persistent chosen/current state. +selected-color = "foreground" +selected-fill-alpha = 0.18 +selected-border-width = 0 +selected-border-alpha = 1.0 + +# Focus: Qt activeFocus; inherit hover-cursor unless intentionally different. +focus-color = "hover-cursor" +focus-fill-alpha = "hover-cursor" +focus-border-width = "hover-cursor" +focus-border-alpha = "hover-cursor" + +# Momentary fills. +pressed-fill-alpha = 0.22 +selection-fill-alpha = 0.35 + +[spacing] +# Multiplies shared margins, gaps, and padding. See docs/omarchy-shell.md#spacing. +scale = 1.0 [font] # base-size is the rem root for the type scale. Every Style.font. # derives from it (e.g. body = base, subtitle ≈ base * 1.083, -# heading ≈ base * 1.333). Clamped 11..13 by the shell — row heights are -# fixed until we ship matching spacing tokens, so growth would clip. +# heading ≈ base * 1.333). Clamped 11..13 by the shell because some +# row heights remain fixed, so larger type can clip. base-size = 12 # Per-token overrides, in px. Uncomment any to pin a specific size without # affecting the rest of the scale. Useful for stylistic emphasis (a diff --git a/docs/omarchy-shell.md b/docs/omarchy-shell.md index cec2e279..127fc38c 100644 --- a/docs/omarchy-shell.md +++ b/docs/omarchy-shell.md @@ -112,10 +112,97 @@ The shell exposes these tokens to QML via two singletons in - `Color` — palette (`foreground`, `background`, `accent`, `urgent`) and per-surface roles (`Color.bar.*`, `Color.popups.*`, `Color.notifications.*`, `Color.menu.*`, `Color.imagePicker.*`). -- `Style` — structural tokens (`cornerRadius`, focus affordances), +- `Style` — structural tokens (`cornerRadius`), shared interactive + state tokens/helpers, spacing (`Style.spacing.*` / `Style.space(px)`), the type scale (`Style.font.*`), and bar dimensions (`Style.bar.sizeHorizontal` / `Style.bar.sizeVertical`). +### Interactive states + +`[style]` standardizes reusable control chrome around four states: +`normal`, `hover-cursor`, `selected`, and `focus`. State colors can be +palette roles (`foreground`, `accent`, `urgent`, `background`) or hex +strings. Fill/border alphas are applied to that state's color. + +Focus inherits `hover-cursor` by default so mouse hover, the panel +keyboard cursor, and Qt activeFocus read as the same state. Use the +literal value `"hover-cursor"` on `focus-*` tokens to keep that +inheritance; set an explicit color/number only when a theme intentionally +wants focus to differ. + +| State | Color token | Fill alpha | Border width | Border alpha | +|-------|-------------|------------|--------------|--------------| +| Normal idle chrome | `normal-color` | `normal-fill-alpha` | `normal-border-width` | `normal-border-alpha` | +| Hover / keyboard cursor | `hover-cursor-color` | `hover-cursor-fill-alpha` | `hover-cursor-border-width` | `hover-cursor-border-alpha` | +| Persistent selected/current | `selected-color` | `selected-fill-alpha` | `selected-border-width` | `selected-border-alpha` | +| Qt activeFocus | `focus-color` | `focus-fill-alpha` | `focus-border-width` | `focus-border-alpha` | + +Border widths are the theme-level on/off switches for state borders; set +a width to `0` to keep the fill while removing that state border. The +default theme keeps selected borders off globally (`selected-border-width += 0`); explicitly bordered controls can still keep their normal border +when selected. + +```toml +[style] +# Accent-tinted cursor/focus, foreground-tinted selected state. +hover-cursor-color = "accent" +focus-color = "hover-cursor" +selected-color = "foreground" + +# Keep selected fills but remove selected-state borders. +selected-border-width = 0 +``` + +Momentary fills use `pressed-fill-alpha` for button press feedback and +`selection-fill-alpha` for text selection. Themes may also provide +`pressed-color` or `selection-color`; they fall back to hover-cursor and +foreground respectively. + +### Spacing + +`[spacing] scale` multiplies the shell's shared margins, gaps, and +padding. The default is `1.0`; values above `1.0` create more breathing +room while values below `1.0` make controls denser. + +```toml +[spacing] +scale = 1.15 # roomier panels and controls +``` + +QML components should prefer semantic tokens where possible: + +| Token | Default use | +|-------|-------------| +| `Style.spacing.controlPaddingX` / `controlPaddingY` | Button and tooltip padding | +| `Style.spacing.inputPaddingY` | Text-field vertical padding | +| `Style.spacing.controlHeight` / `popupRowHeight` | Dropdown and number-field row heights | +| `Style.spacing.dropdownWidth` / `searchableDropdownWidth` / `numberFieldWidth` | Default field widths | +| `Style.spacing.searchablePopupMinHeight` | Minimum searchable dropdown popup height | +| `Style.spacing.controlGap` | Gap between icon and label inside controls | +| `Style.spacing.labelGap` | Label-to-control and compact list gaps | +| `Style.spacing.rowGap` / `rowPaddingX` | Form rows and list row content | +| `Style.spacing.panelGap` / `panelPadding` | Panel section spacing and interior padding | +| `Style.spacing.popupPadding` | Popout interior padding | + +Popout placement deliberately follows Hyprland's `general:gaps_out` +(`Style.gapsOut`) so panels align with tiled windows. Use a theme's +`hyprland.lua` to change that outer alignment; `[spacing]` controls the +interior breathing room. + +For one-off proportional constants, use `Style.space(px)` to preserve the +old default at scale `1.0` while still responding to the theme scale. Use +`Style.spaceReal(px)` only for fractional geometry that should not be +rounded, such as bar widget text margins. Themes can override any semantic +token directly in `[spacing]`, e.g.: + +```toml +[spacing] +scale = 1.0 +panel-padding = 22 +row-gap = 10 +``` + ### Typography `[font] base-size` is the rem root for the scale. Every @@ -157,8 +244,8 @@ Recognized override keys: `base-size`, `caption`, `body-small`, `body`, `subtitle`, `title`, `heading`, `display`, `display-large`, `icon-small`, `icon`, `icon-large`. -`base-size` is clamped to **11..13** because row heights and the bar -cross-axis size are fixed; per-token overrides aren't clamped. The +`base-size` is clamped to **11..13** because some row heights and the bar +cross-axis size remain fixed; per-token overrides aren't clamped. The shell font family is the fontconfig `monospace` alias — themes don't set it, the user does via `omarchy font set `. diff --git a/shell/Commons/Style.qml b/shell/Commons/Style.qml index 8bb51117..32c7d372 100644 --- a/shell/Commons/Style.qml +++ b/shell/Commons/Style.qml @@ -5,8 +5,9 @@ import Quickshell.Io // Shared structural style tokens for the shell. Color is the palette // singleton; Style holds everything else themes can influence — corner -// rounding, focus affordances, typography scale, and bar dimensions — -// so panels and qs.Ui components have a single source of truth. +// rounding, state affordances, spacing, typography scale, and bar +// dimensions — so panels and qs.Ui components have a single source of +// truth. // // `cornerRadius` mirrors Hyprland's `decoration:rounding`. Themes ship // their own rounding via theme/hyprland.lua; the user toggle via @@ -15,58 +16,268 @@ import Quickshell.Io // up the change here by re-running `hyprctl getoption` whenever either // of those input files changes. // -// Typography and bar size come from `theme/shell.toml`. `[font] base-size` -// is the rem root; every `Style.font.` derives from it via the -// scale multipliers below unless the theme pins that specific token. -// `[bar] size-horizontal` / `size-vertical` set the cross-axis dimension -// for top/bottom and left/right bars respectively. +// Typography, spacing, and bar size come from `theme/shell.toml`. +// `[font] base-size` is the rem root; every `Style.font.` derives +// from it via the scale multipliers below unless the theme pins that +// specific token. `[spacing] scale` multiplies shared margins, gaps, and +// padding while preserving each component's proportions. `[bar] +// size-horizontal` / `size-vertical` set the cross-axis dimension for +// top/bottom and left/right bars respectively. QtObject { id: root property int cornerRadius: 0 + property int gapsOut: 10 - // ---------------------------------------------------------- state alphas + // ---------------------------------------------------------- state tokens // - // Foreground-tinted fills + border alphas used by every interactive - // surface in the kit. Themes can override these via [style] in - // shell.toml; otherwise the kit defaults below apply. + // Shared interactive-state tokens for every reusable surface in the kit. + // The vocabulary is intentionally small: + // normal — idle control chrome + // hover-cursor — mouse hover OR panel keyboard cursor (`hasCursor`) + // selected — persistent chosen/current state + // focus — actual Qt activeFocus, defaulting to hover-cursor + // + // Each state has a color token plus fill/border alphas. Color tokens may + // be palette roles (`foreground`, `accent`, `urgent`, `background`) or + // hex colors. Border widths are the on/off switch themes can use: set a + // state width to 0 to remove that state border everywhere. Legacy + // `border-width`, `idle-border-alpha`, `hover-*`, and `hot-fill-alpha` + // remain supported aliases for existing theme shell.toml files. property var styleOverrides: ({}) - function styleNum(key, fallback) { + function keyList(keys) { + return (typeof keys === "string") ? [keys] : keys + } + + function styleRawNum(key) { var v = styleOverrides[key] var n = Number(v) - return isFinite(n) ? n : fallback + return isFinite(n) ? n : null } - readonly property int borderWidth: Math.max(0, Math.round(styleNum("border-width", 1))) - readonly property int focusBorderWidth: Math.max(1, Math.round(styleNum("focus-border-width", 3))) - - readonly property real idleBorderAlpha: styleNum("idle-border-alpha", 0.4) - readonly property real hotFillAlpha: styleNum("hot-fill-alpha", 0.08) - readonly property real selectedFillAlpha: styleNum("selected-fill-alpha", 0.18) - readonly property real pressedFillAlpha: styleNum("pressed-fill-alpha", 0.22) - readonly property real focusFillAlpha: styleNum("focus-fill-alpha", 0.22) - - function alphaForeground(a) { - return Qt.rgba(Color.foreground.r, Color.foreground.g, Color.foreground.b, a) + function styleRawNumAny(keys) { + var list = keyList(keys) + for (var i = 0; i < list.length; i++) { + var n = styleRawNum(list[i]) + if (n !== null) return n + } + return null } - function alphaAccent(a) { - return Qt.rgba(Color.accent.r, Color.accent.g, Color.accent.b, a) + function styleNum(keys, fallback) { + var n = styleRawNumAny(keys) + return n === null ? fallback : n } - // Focus affordances. Deliberately distinct from "selected" (which uses an - // accent fill) so the keyboard cursor never reads as the chosen value. - readonly property color focusBorderColor: Color.accent - readonly property color focusFillColor: alphaAccent(focusFillAlpha) + function clampAlpha(value) { + var n = Number(value) + if (!isFinite(n)) return 0 + return Math.max(0, Math.min(1, n)) + } - // Convenience fills used by panel rows and pills. Hot is hover/keyboard - // cursor; selected/active is the persistent chosen/current state. - readonly property color hotFill: alphaForeground(hotFillAlpha) - readonly property color selectedFill: alphaForeground(selectedFillAlpha) - readonly property color pressedFill: alphaForeground(pressedFillAlpha) - readonly property color idleBorderColor: alphaForeground(idleBorderAlpha) - readonly property color selectedAccentFill: alphaAccent(selectedFillAlpha) + function styleAlpha(keys, fallback) { + return clampAlpha(styleNum(keys, fallback)) + } + + function styleString(keys, fallback) { + var list = keyList(keys) + for (var i = 0; i < list.length; i++) { + var v = styleOverrides[list[i]] + if (typeof v !== "string") continue + v = v.replace(/^\s+|\s+$/g, "") + if (v.length > 0) return v + } + return fallback + } + + readonly property string normalColorToken: styleString("normal-color", "foreground") + readonly property string hoverColorToken: styleString(["hover-cursor-color", "hover-color"], "foreground") + readonly property string selectedColorToken: styleString("selected-color", "foreground") + readonly property string pressedColorToken: styleString("pressed-color", hoverColorToken) + readonly property string focusColorToken: styleString("focus-color", hoverColorToken) + readonly property string selectionColorToken: styleString("selection-color", "foreground") + + readonly property int normalBorderWidth: Math.max(0, Math.round(styleNum(["normal-border-width", "border-width"], 1))) + readonly property int hoverBorderWidth: Math.max(0, Math.round(styleNum(["hover-cursor-border-width", "hover-border-width"], normalBorderWidth))) + readonly property int selectedBorderWidth: Math.max(0, Math.round(styleNum("selected-border-width", 0))) + readonly property int focusBorderWidth: Math.max(0, Math.round(styleNum("focus-border-width", hoverBorderWidth))) + + // Back-compat names used by older components / third-party plugins. + readonly property int borderWidth: normalBorderWidth + readonly property int hoverCursorBorderWidth: hoverBorderWidth + + readonly property real normalFillAlpha: styleAlpha("normal-fill-alpha", 0.04) + readonly property real hoverFillAlpha: styleAlpha(["hover-cursor-fill-alpha", "hover-fill-alpha", "hot-fill-alpha"], 0.08) + readonly property real selectedFillAlpha: styleAlpha("selected-fill-alpha", 0.18) + readonly property real pressedFillAlpha: styleAlpha("pressed-fill-alpha", 0.22) + readonly property real focusFillAlpha: styleAlpha("focus-fill-alpha", hoverFillAlpha) + readonly property real selectionFillAlpha: styleAlpha("selection-fill-alpha", 0.35) + + readonly property real normalBorderAlpha: styleAlpha(["normal-border-alpha", "idle-border-alpha"], 0.4) + readonly property real hoverBorderAlpha: styleAlpha(["hover-cursor-border-alpha", "hover-border-alpha"], 1.0) + readonly property real selectedBorderAlpha: styleAlpha("selected-border-alpha", 1.0) + readonly property real focusBorderAlpha: styleAlpha("focus-border-alpha", hoverBorderAlpha) + + // Back-compat names used by older components / third-party plugins. + readonly property real idleBorderAlpha: normalBorderAlpha + readonly property real hotFillAlpha: hoverFillAlpha + readonly property real hoverCursorFillAlpha: hoverFillAlpha + readonly property real hoverCursorBorderAlpha: hoverBorderAlpha + + function alpha(c, opacity) { + var a = clampAlpha(opacity) + if (!c) return Qt.rgba(0, 0, 0, a) + return Qt.rgba(c.r, c.g, c.b, a) + } + + function colorFromHex(value, fallback) { + var s = String(value || "").replace(/^\s+|\s+$/g, "") + var shortHex = s.match(/^#([0-9A-Fa-f]{3})$/) + if (shortHex) { + var sh = shortHex[1] + return Qt.rgba( + parseInt(sh.charAt(0) + sh.charAt(0), 16) / 255, + parseInt(sh.charAt(1) + sh.charAt(1), 16) / 255, + parseInt(sh.charAt(2) + sh.charAt(2), 16) / 255, + 1) + } + var hex = s.match(/^#([0-9A-Fa-f]{6})([0-9A-Fa-f]{2})?$/) + if (!hex) return fallback + var h = hex[1] + return Qt.rgba( + parseInt(h.substr(0, 2), 16) / 255, + parseInt(h.substr(2, 2), 16) / 255, + parseInt(h.substr(4, 2), 16) / 255, + hex[2] ? parseInt(hex[2], 16) / 255 : 1) + } + + function resolveStateColor(token, foreground, accent, urgent, fallback) { + var fb = fallback || foreground || Color.foreground + var s = String(token || "").replace(/^\s+|\s+$/g, "") + var role = s.toLowerCase() + if (role === "foreground" || role === "text") return foreground || Color.foreground + if (role === "accent") return accent || Color.accent + if (role === "urgent") return urgent || Color.urgent + if (role === "background") return Color.background + if (role === "transparent") return Qt.rgba(0, 0, 0, 0) + return colorFromHex(s, fb) + } + + function normalStateColor(foreground, accent, urgent) { + return resolveStateColor(normalColorToken, foreground, accent, urgent, foreground || Color.foreground) + } + + function hoverStateColor(foreground, accent, urgent) { + return resolveStateColor(hoverColorToken, foreground, accent, urgent, foreground || Color.foreground) + } + + function selectedStateColor(foreground, accent, urgent) { + return resolveStateColor(selectedColorToken, foreground, accent, urgent, foreground || Color.foreground) + } + + function pressedStateColor(foreground, accent, urgent) { + return resolveStateColor(pressedColorToken, foreground, accent, urgent, hoverStateColor(foreground, accent, urgent)) + } + + function focusStateColor(foreground, accent, urgent) { + var role = String(focusColorToken || "").replace(/^\s+|\s+$/g, "").toLowerCase() + if (role === "hover" || role === "hover-cursor" || role === "inherit") + return hoverStateColor(foreground, accent, urgent) + return resolveStateColor(focusColorToken, foreground, accent, urgent, hoverStateColor(foreground, accent, urgent)) + } + + function selectionStateColor(foreground, accent, urgent) { + return resolveStateColor(selectionColorToken, foreground, accent, urgent, foreground || Color.foreground) + } + + function normalFillFor(foreground, accent, urgent) { return alpha(normalStateColor(foreground, accent, urgent), normalFillAlpha) } + function hoverFillFor(foreground, accent, urgent) { return alpha(hoverStateColor(foreground, accent, urgent), hoverFillAlpha) } + function selectedFillFor(foreground, accent, urgent) { return alpha(selectedStateColor(foreground, accent, urgent), selectedFillAlpha) } + function pressedFillFor(foreground, accent, urgent) { return alpha(pressedStateColor(foreground, accent, urgent), pressedFillAlpha) } + function focusFillFor(foreground, accent, urgent) { return alpha(focusStateColor(foreground, accent, urgent), focusFillAlpha) } + function selectionFillFor(foreground, accent, urgent) { return alpha(selectionStateColor(foreground, accent, urgent), selectionFillAlpha) } + + function normalBorderFor(foreground, accent, urgent) { return alpha(normalStateColor(foreground, accent, urgent), normalBorderAlpha) } + function hoverBorderFor(foreground, accent, urgent) { return alpha(hoverStateColor(foreground, accent, urgent), hoverBorderAlpha) } + function selectedBorderFor(foreground, accent, urgent) { return alpha(selectedStateColor(foreground, accent, urgent), selectedBorderAlpha) } + function focusBorderFor(foreground, accent, urgent) { return alpha(focusStateColor(foreground, accent, urgent), focusBorderAlpha) } + + // Convenience colors used by panel rows and pills. `hot*` remains as a + // compatibility alias for the hover/cursor state. + readonly property color normalFill: normalFillFor(Color.foreground, Color.accent, Color.urgent) + readonly property color hoverFill: hoverFillFor(Color.foreground, Color.accent, Color.urgent) + readonly property color hotFill: hoverFill + readonly property color selectedFill: selectedFillFor(Color.foreground, Color.accent, Color.urgent) + readonly property color pressedFill: pressedFillFor(Color.foreground, Color.accent, Color.urgent) + readonly property color focusFillColor: focusFillFor(Color.foreground, Color.accent, Color.urgent) + readonly property color normalBorderColor: normalBorderFor(Color.foreground, Color.accent, Color.urgent) + readonly property color idleBorderColor: normalBorderColor + readonly property color hoverBorderColor: hoverBorderFor(Color.foreground, Color.accent, Color.urgent) + readonly property color selectedBorderColor: selectedBorderFor(Color.foreground, Color.accent, Color.urgent) + readonly property color focusBorderColor: focusBorderFor(Color.foreground, Color.accent, Color.urgent) + readonly property color selectedAccentFill: alpha(Color.accent, selectedFillAlpha) + readonly property color selectionFill: selectionFillFor(Color.foreground, Color.accent, Color.urgent) + + // ---------------------------------------------------------- spacing + // + // The spacing scale is the shell equivalent of rem for margins, gaps, + // and padding. Components keep their existing proportions by asking for + // the old pixel value through `Style.space(px)` (or `spaceReal(px)` for + // fractional geometry); themes can make the shell denser or roomier with + // a single `[spacing] scale` value. + property real spacingScale: 1.0 + property var spacingOverrides: ({}) + + function spaceReal(px) { + var n = Number(px) + if (!isFinite(n) || n <= 0) return 0 + return n * spacingScale + } + + function space(px) { + var n = spaceReal(px) + if (n <= 0) return 0 + return Math.max(1, Math.round(n)) + } + + function spacingToken(key, fallback) { + var v = spacingOverrides[key] + var n = Number(v) + return (isFinite(n) && n >= 0) ? Math.round(n) : space(fallback) + } + + readonly property QtObject spacing: QtObject { + readonly property real scale: root.spacingScale + + readonly property int hairline: root.space(1) + readonly property int xxs: root.spacingToken("xxs", 2) + readonly property int xs: root.spacingToken("xs", 3) + readonly property int sm: root.spacingToken("sm", 4) + readonly property int md: root.spacingToken("md", 6) + readonly property int lg: root.spacingToken("lg", 8) + readonly property int xl: root.spacingToken("xl", 10) + readonly property int xxl: root.spacingToken("xxl", 12) + readonly property int xxxl: root.spacingToken("xxxl", 14) + readonly property int huge: root.spacingToken("huge", 18) + + readonly property int controlGap: root.spacingToken("control-gap", 8) + readonly property int controlPaddingX: root.spacingToken("control-padding-x", 10) + readonly property int controlPaddingY: root.spacingToken("control-padding-y", 6) + readonly property int inputPaddingY: root.spacingToken("input-padding-y", 7) + readonly property int controlHeight: root.spacingToken("control-height", 28) + readonly property int popupRowHeight: root.spacingToken("popup-row-height", 28) + readonly property int dropdownWidth: root.spacingToken("dropdown-width", 240) + readonly property int searchableDropdownWidth: root.spacingToken("searchable-dropdown-width", 260) + readonly property int numberFieldWidth: root.spacingToken("number-field-width", 120) + readonly property int searchablePopupMinHeight: root.spacingToken("searchable-popup-min-height", 220) + readonly property int rowGap: root.spacingToken("row-gap", 8) + readonly property int rowPaddingX: root.spacingToken("row-padding-x", 12) + readonly property int labelGap: root.spacingToken("label-gap", 4) + readonly property int panelGap: root.spacingToken("panel-gap", 14) + readonly property int panelPadding: root.spacingToken("panel-padding", 18) + readonly property int popupPadding: root.spacingToken("popup-padding", 14) + } // ---------------------------------------------------------- typography // @@ -82,13 +293,13 @@ QtObject { // read `resolvedFontFamily` when you want to *display* what's drawing. property string resolvedFontFamily: "monospace" - // Clamped 11..13 by loadShell — bar height and row heights are fixed - // until we ship matching spacing tokens, so unbounded growth clips. + // Clamped 11..13 by loadShell — some row heights remain fixed, so + // unbounded type growth can still clip even with scalable spacing. property int fontBaseSize: 12 // Parsed maps populated by loadShell. Keep them as plain dicts so // reassigning the whole property fires reactive bindings. styleOverrides - // is declared up top next to the helpers that consume it. + // and spacingOverrides are declared near the helpers that consume them. property var fontOverrides: ({}) property var barOverrides: ({}) @@ -134,6 +345,28 @@ QtObject { function refresh() { hyprctlProc.running = true + gapsOutProc.running = true + } + + property bool themeReloadSuspended: false + + function suspendThemeReloads() { + themeReloadSuspended = true + } + + function resumeThemeReloads() { + themeReloadSuspended = false + } + + function scheduleRefresh() { + if (themeReloadSuspended) return + refreshTimer.restart() + } + + function reloadTheme() { + if (themeReloadSuspended) return + shellTomlFile.reload() + scheduleRefresh() } function applyRoundingJson(raw) { @@ -146,15 +379,30 @@ QtObject { } } - // Parse [font] base-size + per-token overrides and [bar] size-* keys - // out of shell.toml. Color.qml owns the quoted-string side of the same - // file; we handle the unquoted numeric tokens for [font], [bar] sizes, - // and [style] (alphas + border widths). + function applyGapsOutJson(raw) { + try { + var json = JSON.parse(raw || "{}") + var css = String(json.css || "") + var parts = css.match(/-?\d+(?:\.\d+)?/g) || [] + var n = parts.length > 0 ? Number(parts[0]) : Number(json.int) + if (isFinite(n) && n >= 0) gapsOut = Math.round(n) + } catch (e) { + // hyprctl missing / Hyprland not running — leave the previous value. + } + } + + // Parse [font] base-size + per-token overrides, [bar] size-* keys, + // [style] state colors / alphas / border widths, and [spacing] scale + + // token overrides out of shell.toml. Color.qml owns the quoted-string + // side of the surface color sections; Style owns quoted strings only + // inside [style]. function loadShell(raw) { var fontOut = {} var barOut = {} var styleOut = {} + var spacingOut = {} var nextBase = 12 + var nextSpacingScale = 1.0 var text = String(raw || "") if (text) { var lines = text.split("\n") @@ -164,19 +412,28 @@ QtObject { if (!line || line.charAt(0) === "#") continue var sectionMatch = line.match(/^\[([A-Za-z0-9_-]+)\]\s*(#.*)?$/) if (sectionMatch) { section = sectionMatch[1]; continue } - // Accept ints OR floats (alphas are 0..1). - var kv = line.match(/^([A-Za-z0-9_-]+)\s*=\s*(-?\d+(?:\.\d+)?)\s*(#.*)?$/) + // Accept ints/floats for numeric tokens and quoted/bare words for + // [style] color roles / inheritance sentinels (e.g. "foreground", + // "accent", "hover-cursor", "#c0caf5"). + var numKv = line.match(/^([A-Za-z0-9_-]+)\s*=\s*(-?\d+(?:\.\d+)?)\s*(#.*)?$/) + var stringKv = line.match(/^([A-Za-z0-9_-]+)\s*=\s*["']([^"']+)["']\s*(#.*)?$/) + var bareKv = line.match(/^([A-Za-z0-9_-]+)\s*=\s*([A-Za-z][A-Za-z0-9_-]*)\s*(#.*)?$/) + var kv = numKv || stringKv || bareKv if (!kv) continue var key = kv[1] - var raw = kv[2] - if (section === "font") { - var ival = parseInt(raw, 10) + var rawValue = kv[2] + if (section === "font" && numKv) { + var ival = parseInt(rawValue, 10) if (key === "base-size") nextBase = ival else fontOut[key] = ival - } else if (section === "bar" && (key === "size-horizontal" || key === "size-vertical")) { - barOut[key] = parseInt(raw, 10) + } else if (section === "bar" && numKv && (key === "size-horizontal" || key === "size-vertical")) { + barOut[key] = parseInt(rawValue, 10) + } else if (section === "spacing" && numKv) { + var fval = parseFloat(rawValue) + if (key === "scale") nextSpacingScale = fval + else spacingOut[key] = fval } else if (section === "style") { - styleOut[key] = parseFloat(raw) + styleOut[key] = numKv ? parseFloat(rawValue) : rawValue } } } @@ -184,9 +441,12 @@ QtObject { // that wants display-large = 64 should be allowed to ship it. if (nextBase < 11) nextBase = 11 if (nextBase > 13) nextBase = 13 + if (!isFinite(nextSpacingScale) || nextSpacingScale < 0) nextSpacingScale = 1.0 + spacingScale = nextSpacingScale fontBaseSize = nextBase fontOverrides = fontOut barOverrides = barOut + spacingOverrides = spacingOut styleOverrides = styleOut } @@ -199,6 +459,15 @@ QtObject { } } + property Process gapsOutProc: Process { + id: gapsOutProc + command: ["hyprctl", "-j", "getoption", "general:gaps_out"] + stdout: StdioCollector { + waitForEnd: true + onStreamFinished: root.applyGapsOutJson(text) + } + } + // Resolve the fontconfig alias to a concrete family name. `omarchy font // set ` rewrites ~/.config/fontconfig/fonts.conf and restarts the // shell, but rerun on file change anyway so manual edits propagate too. @@ -247,15 +516,12 @@ QtObject { path: Quickshell.env("HOME") + "/.config/omarchy/current/theme.name" watchChanges: true printErrors: false - onFileChanged: { - refreshTimer.restart() - shellTomlFile.reload() - } + onFileChanged: root.reloadTheme() } - // `omarchy style corners ` creates or removes this flag file. - // Hyprland reloads its config when sourced files change, then hyprctl - // reflects the new effective rounding value. + // `omarchy style corners ` and `omarchy toggle window-gaps` + // create/remove these flag files. Hyprland reloads its config when sourced + // files change, then hyprctl reflects the new effective values. property FileView roundedCornersToggle: FileView { path: Quickshell.env("HOME") + "/.local/state/omarchy/toggles/hypr/rounded-corners.lua" watchChanges: true @@ -265,6 +531,15 @@ QtObject { onLoadFailed: refreshTimer.restart() } + property FileView windowNoGapsToggle: FileView { + path: Quickshell.env("HOME") + "/.local/state/omarchy/toggles/hypr/window-no-gaps.lua" + watchChanges: true + printErrors: false + onFileChanged: refreshTimer.restart() + onLoaded: refreshTimer.restart() + onLoadFailed: refreshTimer.restart() + } + property FileView shellTomlFile: FileView { id: shellTomlFile path: Quickshell.env("HOME") + "/.config/omarchy/current/theme/shell.toml" @@ -272,7 +547,7 @@ QtObject { printErrors: false onLoaded: root.loadShell(text()) onLoadFailed: root.loadShell("") - onFileChanged: reload() + onFileChanged: root.reloadTheme() } Component.onCompleted: { diff --git a/shell/Ui/Button.qml b/shell/Ui/Button.qml index e95e491d..84ae0d72 100644 --- a/shell/Ui/Button.qml +++ b/shell/Ui/Button.qml @@ -6,11 +6,11 @@ import qs.Commons // States compose independently and are applied in priority order: // // pressed (mouse down) pressed fill -// activeFocus (Tab focus) accent ring + accent fill -// selected accent fill + accent border -// active foreground tint fill (highlighted) -// hasCursor || hover hot fill -// idle transparent or 1px border if `bordered` +// activeFocus (Tab focus) focus fill + focus border token +// hasCursor || hover hover-cursor fill (+ border if `bordered`) +// selected selected fill + optional selected border +// active selected fill +// idle transparent or normal border if `bordered` // // All fills/borders come from `qs.Commons.Style` tokens, so themes // control the look via [style] in shell.toml. @@ -41,8 +41,8 @@ Rectangle { property real fontSize: Style.font.body property real iconSize: Style.font.icon property real iconRotation: 0 - property real horizontalPadding: 10 - property real verticalPadding: 6 + property real horizontalPadding: Style.spacing.controlPaddingX + property real verticalPadding: Style.spacing.controlPaddingY property bool leftAlign: false // Tooltip palette. Auto-rendered if tooltipText is set. @@ -64,32 +64,32 @@ Rectangle { readonly property bool hot: mouseArea.containsMouse || hasCursor readonly property bool _showFocusRing: focusable && activeFocus + readonly property color _selectedColor: Style.selectedStateColor(root.foreground, root.accent) - color: mouseArea.pressed ? Style.pressedFill - : _showFocusRing ? Style.focusFillColor - : selected ? Style.selectedAccentFill - : hot ? Style.hotFill - : active ? Style.selectedFill + color: mouseArea.pressed ? Style.pressedFillFor(root.foreground, root.accent) + : _showFocusRing ? Style.focusFillFor(root.foreground, root.accent) + : hot ? Style.hoverFillFor(root.foreground, root.accent) + : selected ? Style.selectedFillFor(root.foreground, root.accent) + : active ? Style.selectedFillFor(root.foreground, root.accent) : background - // Border color follows the same precedence as fill: focus ring wins, - // then selected, then cursor on bordered (paints accent so the chip - // structure clearly reads as "cursor is here"), then plain bordered - // (foreground), then nothing. - border.color: _showFocusRing ? Style.focusBorderColor - : selected ? accent - : (bordered && hot) ? Style.focusBorderColor - : bordered ? foreground - : Style.idleBorderColor + // Border follows the same state precedence as fill. Buttons stay + // borderless at rest unless `bordered` is set, but hover-cursor/focus + // always use the shared cursor border so the keyboard target is visible + // and consistent with the rest of the kit. Selected borders are off by + // default for plain buttons; explicitly bordered buttons keep their + // normal border when selected unless selected-border-width opts in to a + // dedicated selected border. + border.color: _showFocusRing ? Style.focusBorderFor(root.foreground, root.accent) + : hot ? Style.hoverBorderFor(root.foreground, root.accent) + : selected ? (Style.selectedBorderWidth > 0 ? Style.selectedBorderFor(root.foreground, root.accent) : Style.normalBorderFor(root.foreground, root.accent)) + : bordered ? Style.normalBorderFor(root.foreground, root.accent) + : "transparent" - // selected+hot thickens to the focus-ring width so the cursor remains - // visible on the chosen option (otherwise selected's accent fill+border - // masks any hot fill). bordered+hot also thickens so the chip cursor - // reads as a deliberate state change rather than a faint tint. border.width: _showFocusRing ? Style.focusBorderWidth - : selected ? (hot ? Style.focusBorderWidth : Math.max(Style.borderWidth, 2)) - : (bordered && hot) ? Style.focusBorderWidth - : bordered ? Style.borderWidth + : hot ? Style.hoverBorderWidth + : selected ? (Style.selectedBorderWidth > 0 ? Style.selectedBorderWidth : (bordered ? Style.normalBorderWidth : 0)) + : bordered ? Style.normalBorderWidth : 0 Behavior on color { ColorAnimation { duration: 120 } } @@ -102,7 +102,7 @@ Rectangle { background: Rectangle { color: root.tooltipBackground border.color: root.tooltipForeground - border.width: 1 + border.width: Math.max(1, Style.normalBorderWidth) radius: 0 opacity: 0.97 } @@ -111,10 +111,10 @@ Rectangle { color: root.tooltipForeground font.family: root.fontFamily font.pixelSize: Style.font.bodySmall - leftPadding: 10 - rightPadding: 10 - topPadding: 6 - bottomPadding: 6 + leftPadding: Style.spacing.controlPaddingX + rightPadding: Style.spacing.controlPaddingX + topPadding: Style.spacing.controlPaddingY + bottomPadding: Style.spacing.controlPaddingY } } @@ -124,12 +124,12 @@ Rectangle { anchors.left: root.leftAlign ? parent.left : undefined anchors.leftMargin: root.leftAlign ? root.horizontalPadding : 0 anchors.horizontalCenter: root.leftAlign ? undefined : parent.horizontalCenter - spacing: 8 + spacing: Style.spacing.controlGap Text { visible: root.iconText !== "" text: root.iconText - color: root.selected ? root.accent : root.foreground + color: root.selected ? root._selectedColor : root.foreground font.family: root.fontFamily font.pixelSize: root.iconSize rotation: root.iconRotation @@ -140,7 +140,7 @@ Rectangle { Text { visible: root.text !== "" text: root.text - color: root.selected ? root.accent : root.foreground + color: root.selected ? root._selectedColor : root.foreground font.family: root.fontFamily font.pixelSize: root.fontSize font.bold: root.selected diff --git a/shell/Ui/ButtonGroup.qml b/shell/Ui/ButtonGroup.qml index 7298166d..f4870dea 100644 --- a/shell/Ui/ButtonGroup.qml +++ b/shell/Ui/ButtonGroup.qml @@ -8,10 +8,18 @@ import qs.Commons // `options` is either a plain string[] (label == value) or an array of // { value, label, icon?, tooltip? } objects. Mixing is fine. // -// Panels with their own keyboard cursor model bind `cursorIndex` to the -// currently-focused option (-1 = no cursor) and listen on `hovered` to -// keep that state synced with the mouse. Forms that don't care about -// the panel cursor model can leave both alone. +// Keyboard navigation. The group itself is a single Tab stop, not one +// stop per chip — so in a form that walks `activeFocusOnTab` items with +// Tab / j / k, the cursor enters the group as a unit. Once focused, +// h / l / Left / Right walks between chips and Enter / Space activates +// the current one. The selected chip is the default landing point so +// users see their existing choice on arrival. +// +// Panel-cursor consumers (the bar widget panels) drive `cursorIndex` +// directly and listen on `hovered` to sync the mouse — Tab focus and +// `cursorIndex` are independent; either one paints the chip's hot +// state, and the bar widget panels never give Tab focus to a +// ButtonGroup so they only see the cursorIndex path. Row { id: root @@ -22,16 +30,24 @@ Row { property color accent: Color.accent property string fontFamily: Style.font.family property real fontSize: Style.font.body - property bool focusable: false + property bool focusable: true - // -1 disables the cursor highlight (the form case). Set from a panel - // to drive Button.hasCursor on the matching index. + // -1 disables the external cursor highlight (the panel-cursor case). + // Driven by a containing panel; the group's own Tab-focus h/l + // tracking is internal and lives in _focusedIndex. property int cursorIndex: -1 + // Internal: which chip h / l / Left / Right is currently sitting on + // when the group itself has Tab focus. Reset to the selected option + // each time focus arrives so the user sees their existing choice. + property int _focusedIndex: -1 + signal changed(string value) signal hovered(int index, bool isHovered) - spacing: 6 + spacing: Style.spacing.md + + activeFocusOnTab: focusable function optionValue(o) { return (o && typeof o === "object") ? String(o.value) : String(o) @@ -46,6 +62,46 @@ Row { return (o && typeof o === "object" && o.tooltip) ? String(o.tooltip) : "" } + function selectedOptionIndex() { + for (var i = 0; i < options.length; i++) + if (optionValue(options[i]) === value) return i + return -1 + } + + function activateFocused() { + if (_focusedIndex < 0 || _focusedIndex >= options.length) return + var v = optionValue(options[_focusedIndex]) + root.changed(v) + } + + onActiveFocusChanged: { + if (activeFocus) { + var idx = selectedOptionIndex() + _focusedIndex = idx < 0 ? 0 : idx + } else { + _focusedIndex = -1 + } + } + + Keys.priority: Keys.BeforeItem + Keys.onPressed: function(event) { + if (event.key === Qt.Key_Left || event.key === Qt.Key_H + || event.text === "h") { + _focusedIndex = Math.max(0, (_focusedIndex < 0 ? 0 : _focusedIndex) - 1) + event.accepted = true + } else if (event.key === Qt.Key_Right || event.key === Qt.Key_L + || event.text === "l") { + var max = options.length - 1 + var next = (_focusedIndex < 0 ? 0 : _focusedIndex) + 1 + _focusedIndex = Math.min(max, next) + event.accepted = true + } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter + || event.key === Qt.Key_Space) { + activateFocused() + event.accepted = true + } + } + Repeater { model: root.options @@ -56,17 +112,19 @@ Row { iconText: root.optionIcon(modelData) tooltipText: root.optionTooltip(modelData) selected: root.optionValue(modelData) === root.value + // Chip lights up when either the external panel cursor lands here + // or the group has Tab focus and h/l has walked to this index. hasCursor: root.cursorIndex === index - // Every chip carries an idle border so the group reads as a row of - // distinct options. selected paints accent; the cursor recolors the - // chip's border to accent via Button's bordered+hot path. + || (root.activeFocus && root._focusedIndex === index) + // Every chip carries the standard bordered-button chrome so the + // group reads as a row of distinct options. selected / hover-cursor / + // focus are all painted by Button from Style's shared state tokens. bordered: true foreground: root.foreground background: root.background accent: root.accent fontFamily: root.fontFamily fontSize: root.fontSize - focusable: root.focusable onClicked: root.changed(root.optionValue(modelData)) onHovered: function(h) { root.hovered(index, h) } } diff --git a/shell/Ui/CursorSurface.qml b/shell/Ui/CursorSurface.qml index 77e66f2b..77b25426 100644 --- a/shell/Ui/CursorSurface.qml +++ b/shell/Ui/CursorSurface.qml @@ -7,35 +7,39 @@ import qs.Commons // `hasCursor` / `current`. That's what guarantees a single highlight on // screen at any time across both keyboard and mouse interaction. // -// Two cursor visuals are supported: -// -// default (outline: false) — paint a tinted fill across the row when -// hasCursor is true. Use for narrow text rows (wifi networks, audio -// devices, menu items) where fill reads cleanly. -// -// outline: true — paint an accent border instead of a fill. Use for -// wide content rows where a fill would obscure the row's chrome -// (slider rows in audio / monitor panels). The fill / currentFill -// props are ignored in this mode. +// Cursor paint is always the shared hover-cursor fill plus optional +// hover-cursor border. `outline` remains as a compatibility flag for +// callers that used to request border-only rows, but slider rows still +// receive the same hover-cursor background as every other row. Rectangle { id: root property bool hasCursor: false property bool current: false property bool outline: false + property bool bordered: false property color foreground: Color.foreground - property color fill: Qt.rgba(foreground.r, foreground.g, foreground.b, 0.08) - property color currentFill: Qt.rgba(foreground.r, foreground.g, foreground.b, 0.18) + property color accent: Color.accent + property color fill: Style.hoverFillFor(foreground, accent) + property color currentFill: Style.selectedFillFor(foreground, accent) radius: Style.cornerRadius - color: root.outline - ? "transparent" - : (hasCursor ? fill : (current ? currentFill : "transparent")) + color: hasCursor ? fill : (current ? currentFill : "transparent") - border.color: root.outline && hasCursor ? Style.focusBorderColor : foreground - border.width: root.outline && hasCursor ? Style.focusBorderWidth : 0 + border.color: root.hasCursor + ? Style.hoverBorderFor(root.foreground, root.accent) + : (root.current + ? Style.selectedBorderFor(root.foreground, root.accent) + : (root.bordered + ? Style.normalBorderFor(root.foreground, root.accent) + : "transparent")) + border.width: root.hasCursor + ? Style.hoverBorderWidth + : (root.current + ? Style.selectedBorderWidth + : (root.bordered ? Style.normalBorderWidth : 0)) Behavior on color { ColorAnimation { duration: 60 } diff --git a/shell/Ui/Dropdown.qml b/shell/Ui/Dropdown.qml index bfac99e2..820fe545 100644 --- a/shell/Ui/Dropdown.qml +++ b/shell/Ui/Dropdown.qml @@ -27,12 +27,12 @@ Item { property color popupBorder: Color.popups.border property color accent: Color.accent property string fontFamily: Style.font.family - property int rowHeight: 28 - property int popupRowHeight: 28 + property int rowHeight: Style.spacing.controlHeight + property int popupRowHeight: Style.spacing.popupRowHeight property bool showLabel: true - // Panel-cursor flag. When true, the trigger renders the same focus ring - // as Tab-focus so a panel's keyboard cursor lands here identically. + // Panel-cursor flag. When true, the trigger renders the shared + // hover-cursor state. Active Qt focus defaults to the same visuals. // Emits `hovered(bool)` on pointer enter/leave so the panel can keep // its cursor state in sync with the mouse. property bool hasCursor: false @@ -62,12 +62,12 @@ Item { return value } - implicitWidth: 240 - implicitHeight: showLabel && label !== "" ? rowHeight + 18 : rowHeight + implicitWidth: Style.spacing.dropdownWidth + implicitHeight: showLabel && label !== "" ? rowHeight + Style.spacing.huge : rowHeight Column { anchors.fill: parent - spacing: 4 + spacing: Style.spacing.labelGap Text { visible: root.showLabel && root.label !== "" @@ -84,18 +84,27 @@ Item { height: root.rowHeight radius: Style.cornerRadius - readonly property bool _focused: trigger.activeFocus || root.hasCursor + readonly property bool _focused: trigger.activeFocus + readonly property bool _hot: triggerHover.hovered || root.hasCursor - color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, - trigger._focused ? 0.08 : 0.04) + color: trigger._focused + ? Style.focusFillFor(root.foreground, root.accent) + : (trigger._hot + ? Style.hoverFillFor(root.foreground, root.accent) + : Style.normalFillFor(root.foreground, root.accent)) border.color: trigger._focused - ? Style.focusBorderColor - : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.4) - border.width: trigger._focused ? Style.focusBorderWidth : 1 + ? Style.focusBorderFor(root.foreground, root.accent) + : (trigger._hot + ? Style.hoverBorderFor(root.foreground, root.accent) + : Style.normalBorderFor(root.foreground, root.accent)) + border.width: trigger._focused + ? Style.focusBorderWidth + : (trigger._hot ? Style.hoverBorderWidth : Style.normalBorderWidth) activeFocusOnTab: true HoverHandler { + id: triggerHover onHoveredChanged: root.hovered(hovered) } @@ -113,8 +122,8 @@ Item { anchors.left: parent.left anchors.right: chevron.left anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 10 - anchors.rightMargin: 6 + anchors.leftMargin: Style.spacing.controlPaddingX + anchors.rightMargin: Style.spacing.md text: root.currentLabel() color: root.foreground font.family: root.fontFamily @@ -126,7 +135,7 @@ Item { id: chevron anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.rightMargin: 8 + anchors.rightMargin: Style.spacing.controlGap text: "󰅀" color: Qt.darker(root.foreground, 1.2) font.family: root.fontFamily @@ -145,17 +154,17 @@ Item { Popup { id: popup x: 0 - y: trigger.height + 2 + y: trigger.height + Style.spacing.xxs width: trigger.width - implicitHeight: Math.min(root.options.length * root.popupRowHeight + Math.max(0, root.options.length - 1) * 4 + 2, - root.popupRowHeight * 8 + 7 * 4 + 2) - padding: 1 + implicitHeight: Math.min(root.options.length * root.popupRowHeight + Math.max(0, root.options.length - 1) * Style.spacing.labelGap + Style.spacing.xxs, + root.popupRowHeight * 8 + 7 * Style.spacing.labelGap + Style.spacing.xxs) + padding: Style.spacing.hairline focus: true background: Rectangle { color: root.background border.color: root.popupBorder - border.width: 1 + border.width: Style.normalBorderWidth radius: Style.cornerRadius } @@ -166,7 +175,7 @@ Item { contentItem: ListView { id: optionList - spacing: 4 + spacing: Style.spacing.labelGap Keys.priority: Keys.BeforeItem Keys.onPressed: function(event) { @@ -207,17 +216,17 @@ Item { width: optionList.width height: root.popupRowHeight color: index === optionList.currentIndex - ? Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.14) + ? Style.hoverFillFor(root.foreground, root.accent) : "transparent" Text { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 10 - anchors.rightMargin: 10 + anchors.leftMargin: Style.spacing.controlPaddingX + anchors.rightMargin: Style.spacing.controlPaddingX text: root.optionLabel(modelData) - color: index === optionList.currentIndex ? root.accent : root.foreground + color: index === optionList.currentIndex ? Style.hoverStateColor(root.foreground, root.accent) : root.foreground font.family: root.fontFamily font.pixelSize: Style.font.body elide: Text.ElideRight diff --git a/shell/Ui/KeyboardPanel.qml b/shell/Ui/KeyboardPanel.qml index a3228351..b0b20645 100644 --- a/shell/Ui/KeyboardPanel.qml +++ b/shell/Ui/KeyboardPanel.qml @@ -38,12 +38,12 @@ PanelWindow { required property Item anchorItem required property QtObject bar property var owner: null - property int margin: 10 - property int padding: 14 + property int margin: Style.gapsOut + property int padding: Style.spacing.popupPadding property int contentWidth: 280 property int contentHeight: 200 property bool open: false - property int gap: 10 // distance between bar edge and panel + property int gap: Style.gapsOut // distance between bar edge and panel default property alias contentItem: contentHolder.children @@ -123,6 +123,32 @@ PanelWindow { readonly property real anchorH: anchorItem ? anchorItem.height : 0 readonly property real screenW: screen ? screen.width : 0 readonly property real screenH: screen ? screen.height : 0 + readonly property real availableCardWidth: screenW > 0 + ? Math.max(120, screenW - ((barPos === "left" || barPos === "right") ? barW + gap + margin : margin * 2)) + : 0 + readonly property real availableCardHeight: screenH > 0 + ? Math.max(120, screenH - ((barPos === "top" || barPos === "bottom") ? barH + gap + margin : margin * 2)) + : 0 + + function fittedContentWidth(width, cap) { + var desired = Math.max(1, Number(width) || 1) + var maxWidth = root.availableCardWidth > 0 ? root.availableCardWidth : desired + if (cap !== undefined && Number(cap) > 0) maxWidth = Math.min(maxWidth, Number(cap)) + return Math.round(Math.min(desired, maxWidth)) + } + + function fittedContentHeight(implicitHeight, cap) { + var desired = Math.max(root.padding * 2, (Number(implicitHeight) || 0) + root.padding * 2) + var maxHeight = root.availableCardHeight > 0 ? root.availableCardHeight : desired + if (cap !== undefined && Number(cap) > 0) maxHeight = Math.min(maxHeight, Number(cap)) + return Math.round(Math.min(desired, maxHeight)) + } + + function cappedContentHeight(height) { + var desired = Math.max(root.padding * 2, Number(height) || root.padding * 2) + var maxHeight = root.availableCardHeight > 0 ? root.availableCardHeight : desired + return Math.round(Math.min(desired, maxHeight)) + } // Desired top-left of the card in screen coordinates. For the // perpendicular axis (away-from-bar) we anchor to the bar window's edge @@ -192,7 +218,7 @@ PanelWindow { height: root.contentHeight color: Color.popups.background border.color: Color.popups.border - border.width: 2 + border.width: Math.max(1, Style.space(2)) radius: Style.cornerRadius opacity: root.open ? 1.0 : 0 Behavior on opacity { diff --git a/shell/Ui/NumberField.qml b/shell/Ui/NumberField.qml index 63b4c3ca..27a3cc5c 100644 --- a/shell/Ui/NumberField.qml +++ b/shell/Ui/NumberField.qml @@ -14,14 +14,15 @@ Column { property color accent: Color.accent property string fontFamily: Style.font.family property real fontSize: Style.font.body - property real fieldWidth: 120 + property real fieldWidth: Style.spacing.numberFieldWidth property bool hasCursor: false + property bool _hovered: false property alias field: spin signal modified(int value) signal hovered(bool on) - spacing: 6 + spacing: Style.spacing.md Text { visible: root.label !== "" @@ -34,6 +35,7 @@ Column { QQC.SpinBox { id: spin width: root.fieldWidth + implicitHeight: Math.max(Style.spacing.controlHeight, root.fontSize + Style.spacing.controlPaddingY * 2) from: root.from to: root.to stepSize: root.stepSize @@ -45,16 +47,29 @@ Column { onValueModified: root.modified(value) background: Rectangle { - readonly property bool _hot: spin.activeFocus || (root.hasCursor && !spin.activeFocus) - color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, _hot ? 0.10 : 0.05) - border.color: _hot - ? Style.focusBorderColor - : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.3) - border.width: _hot ? Style.focusBorderWidth : 1 + readonly property bool _focused: spin.activeFocus + readonly property bool _hot: root._hovered || root.hasCursor + + color: _focused + ? Style.focusFillFor(root.foreground, root.accent) + : (_hot + ? Style.hoverFillFor(root.foreground, root.accent) + : Style.normalFillFor(root.foreground, root.accent)) + border.color: _focused + ? Style.focusBorderFor(root.foreground, root.accent) + : (_hot + ? Style.hoverBorderFor(root.foreground, root.accent) + : Style.normalBorderFor(root.foreground, root.accent)) + border.width: _focused + ? Style.focusBorderWidth + : (_hot ? Style.hoverBorderWidth : Style.normalBorderWidth) radius: Style.cornerRadius HoverHandler { - onHoveredChanged: root.hovered(hovered) + onHoveredChanged: { + root._hovered = hovered + root.hovered(hovered) + } } } @@ -62,7 +77,7 @@ Column { text: spin.displayText font: spin.font color: root.foreground - selectionColor: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.35) + selectionColor: Style.selectionFillFor(root.foreground, root.accent) selectedTextColor: root.foreground horizontalAlignment: Qt.AlignHCenter verticalAlignment: Qt.AlignVCenter diff --git a/shell/Ui/PanelActionButton.qml b/shell/Ui/PanelActionButton.qml index 675046a2..7004fb80 100644 --- a/shell/Ui/PanelActionButton.qml +++ b/shell/Ui/PanelActionButton.qml @@ -13,13 +13,13 @@ import qs.Commons // here because action buttons are not cursor targets — the row they live // in is. // -// Set `focusable: true` to make the button keyboard-tabbable with an -// accent focus ring (Style.focusBorderColor / FillColor / Width). Use this +// Set `focusable: true` to make the button keyboard-tabbable with the +// shared hover-cursor/focus tokens. Use this // in form contexts (the bar settings widget cards) where Tab walks a list // of controls; leave it false for the right-edge actions on panel rows // where the row's CursorSurface owns the keyboard cursor. // -// Set `hasCursor: true` to have the button render the same fill as a +// Set `hasCursor: true` to have the button render the same hover state as // mouse hover — so a panel's keyboard cursor lands on it identically. // Use this when a PanelActionButton is itself the cursor target (rather // than living inside a CursorSurface row). Emits `hovered(bool)` on @@ -34,10 +34,11 @@ Rectangle { property color panelBackground: Color.background property string fontFamily: Style.font.family property real fontSize: Style.font.icon - property real size: 22 + property real size: Math.max(Style.space(22), fontSize + Style.spacing.sm * 2) property bool focusable: false property bool hasCursor: false + property bool bordered: false signal clicked() signal hovered(bool isHovered) @@ -55,12 +56,20 @@ Rectangle { readonly property bool _hot: (mouse.containsMouse || root.hasCursor) && root.enabled color: _showFocusRing - ? Style.focusFillColor + ? Style.focusFillFor(hoverColor, hoverColor) : (_hot - ? Qt.rgba(hoverColor.r, hoverColor.g, hoverColor.b, 0.20) + ? Style.hoverFillFor(hoverColor, hoverColor) : "transparent") - border.width: _showFocusRing ? Style.focusBorderWidth : 0 - border.color: _showFocusRing ? Style.focusBorderColor : "transparent" + border.width: _showFocusRing + ? Style.focusBorderWidth + : (_hot && bordered + ? Style.hoverBorderWidth + : (bordered ? Style.normalBorderWidth : 0)) + border.color: _showFocusRing + ? Style.focusBorderFor(hoverColor, hoverColor) + : (_hot && bordered + ? Style.hoverBorderFor(hoverColor, hoverColor) + : Style.normalBorderFor(foreground, Color.accent)) Behavior on color { ColorAnimation { duration: 60 } } diff --git a/shell/Ui/PanelSlider.qml b/shell/Ui/PanelSlider.qml index 827d30a8..710d72d3 100644 --- a/shell/Ui/PanelSlider.qml +++ b/shell/Ui/PanelSlider.qml @@ -1,4 +1,5 @@ import QtQuick +import qs.Commons Item { id: root @@ -9,11 +10,12 @@ Item { property real maximum: 1 property real step: 0.05 property bool integer: false - property color trackColor: bar ? Qt.rgba(bar.foreground.r, bar.foreground.g, bar.foreground.b, 0.18) : "#333" + property color trackColor: bar ? Style.selectedFillFor(bar.foreground, Color.accent) : "#333" property color fillColor: bar ? bar.foreground : "#cacccc" property color knobColor: bar ? bar.foreground : "#cacccc" property bool dragging: false - property real trackHeight: 4 + property real trackHeight: Math.max(4, Math.round(Style.spacing.controlHeight * 0.11)) + property real knobSize: Math.max(14, Math.round(Style.spacing.controlHeight * 0.38)) property real liveValue: value onValueChanged: if (!dragging) liveValue = value @@ -21,8 +23,8 @@ Item { signal moved(real value) signal released(real value) - implicitWidth: 200 - implicitHeight: 22 + implicitWidth: Style.space(200) + implicitHeight: Math.max(Style.space(22), knobSize + Style.spacing.md) readonly property real range: Math.max(0.0001, maximum - minimum) readonly property real progress: Math.max(0, Math.min(1, (liveValue - minimum) / range)) @@ -54,12 +56,12 @@ Item { Rectangle { id: knob - width: 14 - height: 14 - radius: 7 + width: root.knobSize + height: root.knobSize + radius: root.knobSize / 2 color: root.knobColor border.color: root.bar ? root.bar.background : "#101315" - border.width: 2 + border.width: Math.max(1, Style.space(2)) anchors.verticalCenter: track.verticalCenter x: Math.max(0, Math.min(track.width - width, track.width * root.progress - width / 2)) scale: mouseArea.containsMouse || root.dragging ? 1.15 : 1.0 diff --git a/shell/Ui/PanelToolTip.qml b/shell/Ui/PanelToolTip.qml index 7b561712..933121da 100644 --- a/shell/Ui/PanelToolTip.qml +++ b/shell/Ui/PanelToolTip.qml @@ -27,8 +27,8 @@ ToolTip { background: Rectangle { color: root.panelBackground - border.color: root.panelForeground - border.width: 1 + border.color: Style.normalBorderFor(root.panelForeground, Color.accent) + border.width: Style.normalBorderWidth radius: 0 opacity: 0.97 } @@ -38,9 +38,9 @@ ToolTip { color: root.panelForeground font.family: root.fontFamily font.pixelSize: root.fontSize - leftPadding: 10 - rightPadding: 10 - topPadding: 6 - bottomPadding: 6 + leftPadding: Style.spacing.controlPaddingX + rightPadding: Style.spacing.controlPaddingX + topPadding: Style.spacing.controlPaddingY + bottomPadding: Style.spacing.controlPaddingY } } diff --git a/shell/Ui/PopupCard.qml b/shell/Ui/PopupCard.qml index 8c8de624..e40c877b 100644 --- a/shell/Ui/PopupCard.qml +++ b/shell/Ui/PopupCard.qml @@ -9,8 +9,8 @@ PopupWindow { required property Item anchorItem required property QtObject bar property var owner: null - property int margin: 10 - property int padding: 14 + property int margin: Style.gapsOut + property int padding: Style.spacing.popupPadding property int contentWidth: 280 property int contentHeight: 200 property color borderColor: Color.popups.border @@ -22,7 +22,38 @@ PopupWindow { readonly property var coordinatorKey: owner || root readonly property var anchorWindow: anchorItem ? anchorItem.QsWindow.window : null + readonly property var popupScreen: anchorWindow ? anchorWindow.screen : null readonly property bool containsMouse: cardHover.hovered + readonly property real screenW: popupScreen ? popupScreen.width : 0 + readonly property real screenH: popupScreen ? popupScreen.height : 0 + readonly property real barW: anchorWindow ? anchorWindow.width : 0 + readonly property real barH: anchorWindow ? anchorWindow.height : 0 + readonly property real availableCardWidth: screenW > 0 + ? Math.max(120, screenW - ((bar && (bar.position === "left" || bar.position === "right")) ? barW : 0) - root.margin * 2) + : 0 + readonly property real availableCardHeight: screenH > 0 + ? Math.max(120, screenH - ((bar && (bar.position === "top" || bar.position === "bottom")) ? barH : 0) - root.margin * 2) + : 0 + + function fittedContentWidth(width, cap) { + var desired = Math.max(1, Number(width) || 1) + var maxWidth = root.availableCardWidth > 0 ? root.availableCardWidth : desired + if (cap !== undefined && Number(cap) > 0) maxWidth = Math.min(maxWidth, Number(cap)) + return Math.round(Math.min(desired, maxWidth)) + } + + function fittedContentHeight(implicitHeight, cap) { + var desired = Math.max(root.padding * 2, (Number(implicitHeight) || 0) + root.padding * 2) + var maxHeight = root.availableCardHeight > 0 ? root.availableCardHeight : desired + if (cap !== undefined && Number(cap) > 0) maxHeight = Math.min(maxHeight, Number(cap)) + return Math.round(Math.min(desired, maxHeight)) + } + + function cappedContentHeight(height) { + var desired = Math.max(root.padding * 2, Number(height) || root.padding * 2) + var maxHeight = root.availableCardHeight > 0 ? root.availableCardHeight : desired + return Math.round(Math.min(desired, maxHeight)) + } function closePopout() { if (owner && "closePopout" in owner) owner.closePopout() @@ -119,7 +150,7 @@ PopupWindow { anchors.fill: parent color: Color.popups.background border.color: root.borderColor - border.width: 2 + border.width: Math.max(1, Style.space(2)) radius: Style.cornerRadius opacity: root.open ? 1.0 : 0 diff --git a/shell/Ui/SearchableDropdown.qml b/shell/Ui/SearchableDropdown.qml index e0036913..a230c4cd 100644 --- a/shell/Ui/SearchableDropdown.qml +++ b/shell/Ui/SearchableDropdown.qml @@ -30,13 +30,13 @@ Item { property color popupBorder: Color.popups.border property color accent: Color.accent property string fontFamily: Style.font.family - property int rowHeight: 28 - property int popupRowHeight: 28 - property int popupMinHeight: 220 + property int rowHeight: Style.spacing.controlHeight + property int popupRowHeight: Style.spacing.popupRowHeight + property int popupMinHeight: Style.spacing.searchablePopupMinHeight property bool showLabel: true - // Panel-cursor flag. When true, the trigger renders the same focus ring - // as Tab-focus so a panel's keyboard cursor lands here identically. + // Panel-cursor flag. When true, the trigger renders the shared + // hover-cursor state. Active Qt focus defaults to the same visuals. // Emits `hovered(bool)` on pointer enter/leave so the panel can keep // its cursor state in sync with the mouse. property bool hasCursor: false @@ -84,12 +84,12 @@ Item { onOptionsChanged: recomputeFiltered() - implicitWidth: 260 - implicitHeight: showLabel && label !== "" ? rowHeight + 18 : rowHeight + implicitWidth: Style.spacing.searchableDropdownWidth + implicitHeight: showLabel && label !== "" ? rowHeight + Style.spacing.huge : rowHeight Column { anchors.fill: parent - spacing: 4 + spacing: Style.spacing.labelGap Text { visible: root.showLabel && root.label !== "" @@ -106,18 +106,27 @@ Item { height: root.rowHeight radius: Style.cornerRadius - readonly property bool _focused: trigger.activeFocus || root.hasCursor + readonly property bool _focused: trigger.activeFocus + readonly property bool _hot: triggerHover.hovered || root.hasCursor - color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, - trigger._focused ? 0.08 : 0.04) + color: trigger._focused + ? Style.focusFillFor(root.foreground, root.accent) + : (trigger._hot + ? Style.hoverFillFor(root.foreground, root.accent) + : Style.normalFillFor(root.foreground, root.accent)) border.color: trigger._focused - ? Style.focusBorderColor - : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.4) - border.width: trigger._focused ? Style.focusBorderWidth : 1 + ? Style.focusBorderFor(root.foreground, root.accent) + : (trigger._hot + ? Style.hoverBorderFor(root.foreground, root.accent) + : Style.normalBorderFor(root.foreground, root.accent)) + border.width: trigger._focused + ? Style.focusBorderWidth + : (trigger._hot ? Style.hoverBorderWidth : Style.normalBorderWidth) activeFocusOnTab: true HoverHandler { + id: triggerHover onHoveredChanged: root.hovered(hovered) } @@ -135,8 +144,8 @@ Item { anchors.left: parent.left anchors.right: chevron.left anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 10 - anchors.rightMargin: 6 + anchors.leftMargin: Style.spacing.controlPaddingX + anchors.rightMargin: Style.spacing.md text: root.currentLabel() || root.triggerLabel || root.placeholderText color: (root.currentLabel() || root.triggerLabel) ? root.foreground : Qt.darker(root.foreground, 1.5) font.family: root.fontFamily @@ -148,7 +157,7 @@ Item { id: chevron anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.rightMargin: 8 + anchors.rightMargin: Style.spacing.controlGap text: "󰅀" color: Qt.darker(root.foreground, 1.2) font.family: root.fontFamily @@ -167,18 +176,18 @@ Item { QQC.Popup { id: popup x: 0 - y: trigger.height + 2 + y: trigger.height + Style.spacing.xxs width: trigger.width implicitHeight: Math.max(root.popupMinHeight, - Math.min(resultList.contentHeight + 50, - root.popupRowHeight * 6 + 5 * 4 + 50)) - padding: 1 + Math.min(resultList.contentHeight + Style.space(50), + root.popupRowHeight * 6 + 5 * Style.spacing.labelGap + Style.space(50))) + padding: Style.spacing.hairline focus: true background: Rectangle { color: root.background border.color: root.popupBorder - border.width: 1 + border.width: Style.normalBorderWidth radius: Style.cornerRadius } @@ -193,13 +202,14 @@ Item { spacing: 0 Item { + id: searchHeader width: parent.width - height: 38 + height: root.popupRowHeight + Style.spacing.controlPaddingX TextField { id: searchField anchors.fill: parent - anchors.margins: 6 + anchors.margins: Style.spacing.md placeholderText: root.placeholderText foreground: root.foreground accent: root.accent @@ -234,12 +244,12 @@ Item { Rectangle { width: parent.width height: 1 - color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10) + color: Style.alpha(root.foreground, 0.10) } Item { width: parent.width - height: popup.height - 38 - 2 - 1 + height: popup.height - searchHeader.height - Style.spacing.xxs - 1 Text { anchors.centerIn: parent @@ -253,7 +263,7 @@ Item { ListView { id: resultList anchors.fill: parent - spacing: 4 + spacing: Style.spacing.labelGap clip: true boundsBehavior: Flickable.StopAtBounds model: root.filtered @@ -294,9 +304,9 @@ Item { required property var modelData required property int index width: resultList.width - height: Math.max(root.popupRowHeight, rowContent.implicitHeight + 12) + height: Math.max(root.popupRowHeight, rowContent.implicitHeight + Style.spacing.rowPaddingX) color: index === resultList.currentIndex - ? Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.14) + ? Style.hoverFillFor(root.foreground, root.accent) : "transparent" Column { @@ -304,13 +314,13 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 10 - anchors.rightMargin: 10 - spacing: 2 + anchors.leftMargin: Style.spacing.controlPaddingX + anchors.rightMargin: Style.spacing.controlPaddingX + spacing: Style.spacing.xxs Text { text: root.optionLabel(modelData) - color: index === resultList.currentIndex ? root.accent : root.foreground + color: index === resultList.currentIndex ? Style.hoverStateColor(root.foreground, root.accent) : root.foreground font.family: root.fontFamily font.pixelSize: Style.font.body elide: Text.ElideRight diff --git a/shell/Ui/TextField.qml b/shell/Ui/TextField.qml index 2e5692e5..ff3765d0 100644 --- a/shell/Ui/TextField.qml +++ b/shell/Ui/TextField.qml @@ -9,9 +9,8 @@ import qs.Commons // // Defaults bind to qs.Commons.Color so a caller with no theme overrides // just works; foreground / accent / selectionTint can be overridden per -// instance. Focus styling uses Style.focusBorderColor (the same accent -// ring Toggle and Button paint) so keyboard cursor and form focus -// chrome stay consistent across the shell. +// instance. activeFocus and mouse hover / panel cursor use the same +// hover-cursor defaults, so text inputs match Button, Toggle, and Dropdown. // // Sizing is driven by font.pixelSize + verticalPadding. The default 30px // implicitHeight fits dialog forms; inline callers (wifi's row-embedded @@ -21,20 +20,20 @@ TextField { property color foreground: Color.foreground property color accent: Color.accent - property color selectionTint: Qt.rgba(foreground.r, foreground.g, foreground.b, 0.35) + property color selectionTint: Style.selectionFillFor(foreground, accent) property bool password: false - property real horizontalPadding: 10 - property real verticalPadding: 7 + property real horizontalPadding: Style.spacing.controlPaddingX + property real verticalPadding: Style.spacing.inputPaddingY // Panel-cursor flag. When true (and the field isn't already focused), - // the background paints the same accent ring as activeFocus so the - // panel's keyboard cursor lands here identically to a mouse hover. + // the background paints the shared hover/cursor state. // For mouse-enter/leave the consumer reads QQC TextField's inherited // `hovered` property (via onHoveredChanged) — we don't add a sibling // signal because the inherited property would shadow it. property bool hasCursor: false - readonly property bool _focused: activeFocus || hasCursor + readonly property bool _focused: activeFocus + readonly property bool _hot: hovered || hasCursor echoMode: password ? TextInput.Password : TextInput.Normal font.family: Style.font.family @@ -50,12 +49,19 @@ TextField { bottomPadding: verticalPadding background: Rectangle { - color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, - root._focused ? 0.08 : 0.04) + color: root._focused + ? Style.focusFillFor(root.foreground, root.accent) + : (root._hot + ? Style.hoverFillFor(root.foreground, root.accent) + : Style.normalFillFor(root.foreground, root.accent)) border.color: root._focused - ? Style.focusBorderColor - : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.18) - border.width: root._focused ? Style.focusBorderWidth : 1 + ? Style.focusBorderFor(root.foreground, root.accent) + : (root._hot + ? Style.hoverBorderFor(root.foreground, root.accent) + : Style.normalBorderFor(root.foreground, root.accent)) + border.width: root._focused + ? Style.focusBorderWidth + : (root._hot ? Style.hoverBorderWidth : Style.normalBorderWidth) radius: Style.cornerRadius } } diff --git a/shell/Ui/Toggle.qml b/shell/Ui/Toggle.qml index e4019ca5..dde2f18e 100644 --- a/shell/Ui/Toggle.qml +++ b/shell/Ui/Toggle.qml @@ -6,10 +6,8 @@ import qs.Commons // flip `checked` in response (the component is stateless about the actual // value so it composes cleanly with model-driven UI). // -// Cursor and focus styling match the rest of the kit: hasCursor (panel -// keyboard cursor / mouse hover) is a fill only, mirroring CursorSurface; -// activeFocus (Tab focus) adds the accent border ring on top via the -// shared Style tokens. +// Cursor and focus styling match the rest of the kit: hasCursor / mouse +// hover and activeFocus share the hover-cursor defaults. // // `rounded` auto-detects from Style.cornerRadius so the switch follows // the theme: pill shape on round-corners themes, square on sharp. @@ -23,8 +21,7 @@ Rectangle { // Panel-cursor flag. Same role as Button.hasCursor: // panels with their own keyboard cursor bind this to drive the highlight - // separately from activeFocus. Renders as a tinted fill only — the accent - // border ring is reserved for Tab focus (activeFocus). + // separately from activeFocus. Visuals use the same hover-cursor tokens. property bool hasCursor: false // Switch shape follows the theme by default: pill on round, square on sharp. @@ -45,17 +42,30 @@ Rectangle { Keys.onEnterPressed: root.clicked() Keys.onSpacePressed: root.clicked() - implicitHeight: Math.max(54, content.implicitHeight + 18) - implicitWidth: 240 + readonly property int trackHeight: Math.max(22, Math.round(Style.spacing.controlHeight * 0.55)) + readonly property int trackWidth: Math.max(42, Math.round(trackHeight * 1.9)) + readonly property int knobSize: Math.max(16, Math.round(trackHeight * 0.72)) + readonly property int knobInset: Math.max(2, Math.round((trackHeight - knobSize) / 2)) + + implicitHeight: Math.max(54, content.implicitHeight + Style.spacing.huge) + implicitWidth: Style.space(240) radius: Style.cornerRadius + readonly property bool _hot: hasCursor || mouse.containsMouse + color: activeFocus - ? Style.focusFillColor - : ((hasCursor || mouse.containsMouse) ? Qt.rgba(foreground.r, foreground.g, foreground.b, 0.08) : Qt.rgba(foreground.r, foreground.g, foreground.b, 0.03)) + ? Style.focusFillFor(foreground, accent) + : (_hot + ? Style.hoverFillFor(foreground, accent) + : Style.normalFillFor(foreground, accent)) border.color: activeFocus - ? Style.focusBorderColor - : Qt.rgba(foreground.r, foreground.g, foreground.b, 0.12) - border.width: activeFocus ? Style.focusBorderWidth : 1 + ? Style.focusBorderFor(foreground, accent) + : (_hot + ? Style.hoverBorderFor(foreground, accent) + : Style.normalBorderFor(foreground, accent)) + border.width: activeFocus + ? Style.focusBorderWidth + : (_hot ? Style.hoverBorderWidth : Style.normalBorderWidth) Behavior on color { ColorAnimation { duration: 100 } } @@ -64,13 +74,13 @@ Rectangle { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 12 - anchors.rightMargin: 12 - spacing: 12 + anchors.leftMargin: Style.spacing.rowPaddingX + anchors.rightMargin: Style.spacing.rowPaddingX + spacing: Style.spacing.rowPaddingX Column { width: parent.width - track.width - parent.spacing - spacing: 3 + spacing: Style.spacing.xs anchors.verticalCenter: parent.verticalCenter Text { @@ -96,27 +106,27 @@ Rectangle { Rectangle { id: track - width: 42 - height: 22 + width: root.trackWidth + height: root.trackHeight radius: root.rounded ? height / 2 : 0 color: root.checked - ? Qt.rgba(root.accent.r, root.accent.g, root.accent.b, 0.35) - : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.12) + ? Style.selectedFillFor(root.foreground, root.accent) + : Style.normalFillFor(root.foreground, root.accent) border.color: root.checked - ? root.accent - : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.28) - border.width: 1 + ? Style.selectedBorderFor(root.foreground, root.accent) + : Style.normalBorderFor(root.foreground, root.accent) + border.width: root.checked ? Style.selectedBorderWidth : Style.normalBorderWidth anchors.verticalCenter: parent.verticalCenter Behavior on color { ColorAnimation { duration: 120 } } Rectangle { - width: 16 - height: 16 - radius: root.rounded ? 8 : 0 - x: root.checked ? track.width - width - 3 : 3 - y: 3 - color: root.checked ? root.accent : Qt.darker(root.foreground, 1.25) + width: root.knobSize + height: root.knobSize + radius: root.rounded ? height / 2 : 0 + x: root.checked ? track.width - width - root.knobInset : root.knobInset + y: root.knobInset + color: root.checked ? Style.selectedStateColor(root.foreground, root.accent) : Qt.darker(root.foreground, 1.25) Behavior on x { NumberAnimation { duration: 120; easing.type: Easing.OutCubic } } Behavior on color { ColorAnimation { duration: 120 } } diff --git a/shell/Ui/WidgetButton.qml b/shell/Ui/WidgetButton.qml index 0eb933f3..0b960421 100644 --- a/shell/Ui/WidgetButton.qml +++ b/shell/Ui/WidgetButton.qml @@ -25,11 +25,14 @@ Item { readonly property bool vertical: bar ? bar.vertical : false readonly property int barSize: bar ? bar.barSize : Style.bar.sizeHorizontal + readonly property real scaledHorizontalMargin: Style.spaceReal(horizontalMargin) + readonly property real scaledRightExtraMargin: Style.spaceReal(rightExtraMargin) + readonly property real scaledVerticalPadding: Style.spaceReal(verticalPadding) visible: text !== "" || keepSpace opacity: text === "" ? 0 : 1 - implicitWidth: fixedWidth > 0 ? fixedWidth : (vertical ? barSize : Math.max(12, label.implicitWidth + horizontalMargin * 2 + rightExtraMargin)) - implicitHeight: fixedHeight > 0 ? fixedHeight : (vertical ? Math.max(12, label.implicitHeight + verticalPadding * 2) : barSize) + implicitWidth: fixedWidth > 0 ? fixedWidth : (vertical ? barSize : Math.max(12, label.implicitWidth + scaledHorizontalMargin * 2 + scaledRightExtraMargin)) + implicitHeight: fixedHeight > 0 ? fixedHeight : (vertical ? Math.max(12, label.implicitHeight + scaledVerticalPadding * 2) : barSize) Behavior on opacity { NumberAnimation { duration: 140; easing.type: Easing.OutCubic } @@ -38,7 +41,7 @@ Item { Text { id: label anchors.centerIn: parent - anchors.horizontalCenterOffset: root.vertical ? 0 : -root.rightExtraMargin / 2 + anchors.horizontalCenterOffset: root.vertical ? 0 : -root.scaledRightExtraMargin / 2 text: root.text color: root.active ? root.activeColor : root.foreground font.family: root.fontFamily diff --git a/shell/plugins/bar/Bar.qml b/shell/plugins/bar/Bar.qml index 360e9570..8e8761f9 100644 --- a/shell/plugins/bar/Bar.qml +++ b/shell/plugins/bar/Bar.qml @@ -731,13 +731,13 @@ Item { LeftModules { anchors.left: parent.left - anchors.leftMargin: 8 + anchors.leftMargin: Style.space(8) anchors.verticalCenter: parent.verticalCenter } RightModules { anchors.right: parent.right - anchors.rightMargin: 8 + anchors.rightMargin: Style.space(8) anchors.verticalCenter: parent.verticalCenter } } @@ -753,13 +753,13 @@ Item { LeftModules { anchors.top: parent.top - anchors.topMargin: 8 + anchors.topMargin: Style.space(8) anchors.horizontalCenter: parent.horizontalCenter } RightModules { anchors.bottom: parent.bottom - anchors.bottomMargin: 8 + anchors.bottomMargin: Style.space(8) anchors.horizontalCenter: parent.horizontalCenter } } @@ -1136,8 +1136,8 @@ Item { component WorkspacesModule: GridLayout { columns: root.vertical ? 1 : root.workspaceIds().length - columnSpacing: root.vertical ? 0 : 2 - rowSpacing: root.vertical ? 2 : 0 + columnSpacing: root.vertical ? 0 : Style.space(2) + rowSpacing: root.vertical ? Style.space(2) : 0 Repeater { model: root.workspaceIds() @@ -1357,7 +1357,7 @@ Item { id: trayIcons x: trayRoot.drawerExtent - trayRoot.revealExtent anchors.verticalCenter: parent.verticalCenter - spacing: 17 + spacing: Style.space(17) layer.enabled: true Repeater { @@ -1372,8 +1372,8 @@ Item { id: pinnedRow x: drawerArea.x + horizontalTrayRoot.drawerBlockWidth anchors.verticalCenter: parent.verticalCenter - spacing: 17 - leftPadding: trayRoot.pinnedItems.length > 0 && trayRoot.allItems.length > 0 ? 6 : 0 + spacing: Style.space(17) + leftPadding: trayRoot.pinnedItems.length > 0 && trayRoot.allItems.length > 0 ? Style.space(6) : 0 Repeater { model: trayRoot.pinnedItems TrayItem {} @@ -1441,7 +1441,7 @@ Item { id: trayIcons y: trayRoot.drawerExtent - trayRoot.revealExtent anchors.horizontalCenter: parent.horizontalCenter - spacing: 17 + spacing: Style.space(17) layer.enabled: true Repeater { @@ -1456,8 +1456,8 @@ Item { id: pinnedCol y: drawerArea.y + verticalTrayRoot.drawerBlockHeight anchors.horizontalCenter: parent.horizontalCenter - spacing: 17 - topPadding: trayRoot.pinnedItems.length > 0 && trayRoot.allItems.length > 0 ? 6 : 0 + spacing: Style.space(17) + topPadding: trayRoot.pinnedItems.length > 0 && trayRoot.allItems.length > 0 ? Style.space(6) : 0 Repeater { model: trayRoot.pinnedItems TrayItem {} @@ -1472,13 +1472,13 @@ Item { owner: trayRoot bar: root open: trayRoot.managePopupOpen - contentWidth: 300 - contentHeight: manageColumn.implicitHeight + 28 + contentWidth: managePopup.fittedContentWidth(Style.space(300)) + contentHeight: managePopup.fittedContentHeight(manageColumn.implicitHeight) Column { id: manageColumn anchors.fill: parent - spacing: 8 + spacing: Style.space(8) Text { text: "Tray icons" @@ -1541,9 +1541,9 @@ Item { Text { anchors.verticalCenter: parent.verticalCenter anchors.left: rowIcon.right - anchors.leftMargin: 10 + anchors.leftMargin: Style.space(10) anchors.right: rowHideBtn.left - anchors.rightMargin: 8 + anchors.rightMargin: Style.space(8) text: rowRoot.displayName color: root.foreground font.family: root.fontFamily @@ -1569,7 +1569,7 @@ Item { id: rowHideBtn anchors.verticalCenter: parent.verticalCenter anchors.right: rowPinBtn.left - anchors.rightMargin: 6 + anchors.rightMargin: Style.space(6) iconText: "" text: rowRoot.isHidden ? "Show" : "Hide" foreground: root.foreground diff --git a/shell/plugins/bar/widgets/activeWindow.qml b/shell/plugins/bar/widgets/activeWindow.qml index 963a34f2..c0963ad5 100644 --- a/shell/plugins/bar/widgets/activeWindow.qml +++ b/shell/plugins/bar/widgets/activeWindow.qml @@ -22,7 +22,7 @@ Item { readonly property bool vertical: bar ? bar.vertical : false visible: title !== "" && !vertical - implicitWidth: visible ? Math.min(maxLabelWidth, labelText.implicitWidth) + 16 : 0 + implicitWidth: visible ? Math.min(maxLabelWidth, labelText.implicitWidth) + Style.spacing.controlPaddingX * 2 : 0 implicitHeight: bar ? bar.barSize : 26 Behavior on implicitWidth { @@ -31,8 +31,8 @@ Item { Item { anchors.fill: parent - anchors.leftMargin: 8 - anchors.rightMargin: 8 + anchors.leftMargin: Style.space(8) + anchors.rightMargin: Style.space(8) clip: true Text { diff --git a/shell/plugins/bar/widgets/audioPanel.qml b/shell/plugins/bar/widgets/audioPanel.qml index 45cb7920..f609f0f2 100644 --- a/shell/plugins/bar/widgets/audioPanel.qml +++ b/shell/plugins/bar/widgets/audioPanel.qml @@ -98,10 +98,10 @@ Item { property int selectedIndex: -1 readonly property color hoverFill: bar - ? Qt.rgba(bar.foreground.r, bar.foreground.g, bar.foreground.b, 0.08) + ? Style.hoverFillFor(bar.foreground, Color.accent) : "transparent" readonly property color selectedFill: bar - ? Qt.rgba(bar.foreground.r, bar.foreground.g, bar.foreground.b, 0.18) + ? Style.selectedFillFor(bar.foreground, Color.accent) : "transparent" function sectionCount(section) { @@ -376,8 +376,8 @@ Item { owner: root bar: root.bar open: root.popupOpen - contentWidth: 370 - contentHeight: Math.min(560, panelColumn.implicitHeight + 28) + contentWidth: panel.fittedContentWidth(Style.space(370)) + contentHeight: panel.fittedContentHeight(panelColumn.implicitHeight, Style.space(560)) PanelKeyCatcher { id: keyCatcher @@ -414,16 +414,16 @@ Item { Column { id: panelColumn width: scrollArea.availableWidth - spacing: 14 + spacing: Style.space(14) // ---- Output ---- Column { width: parent.width - spacing: 6 + spacing: Style.space(6) Row { width: parent.width - spacing: 8 + spacing: Style.space(8) PanelSectionHeader { text: "Output" @@ -439,7 +439,7 @@ Item { font.family: root.bar.fontFamily font.pixelSize: Style.font.bodySmall elide: Text.ElideRight - width: parent.width - 70 + width: parent.width - Style.space(70) anchors.verticalCenter: parent.verticalCenter } } @@ -450,7 +450,7 @@ Item { CursorSurface { id: outputSliderRow width: parent.width - height: outputSliderInner.implicitHeight + 8 + height: outputSliderInner.implicitHeight + Style.spacing.controlGap hasCursor: root.focusSection === "output" && root.selectedIndex === -1 onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(outputSliderRow) foreground: root.bar.foreground @@ -459,9 +459,9 @@ Item { Row { id: outputSliderInner anchors.fill: parent - anchors.leftMargin: 6 - anchors.rightMargin: 6 - spacing: 8 + anchors.leftMargin: Style.space(6) + anchors.rightMargin: Style.space(6) + spacing: Style.space(8) Text { id: outputIconText @@ -469,7 +469,7 @@ Item { color: root.bar.foreground font.family: root.bar.fontFamily font.pixelSize: Style.font.heading - width: 22 + width: Style.space(22) horizontalAlignment: Text.AlignHCenter anchors.verticalCenter: parent.verticalCenter opacity: root.outputMuted ? 0.5 : 1.0 @@ -484,7 +484,7 @@ Item { PanelSlider { id: outputSlider bar: root.bar - width: parent.width - outputIconText.width - outputPercent.width - 16 + width: parent.width - outputIconText.width - outputPercent.width - Style.space(16) anchors.verticalCenter: parent.verticalCenter minimum: 0 maximum: 1 @@ -502,7 +502,7 @@ Item { color: root.bar.foreground font.family: root.bar.fontFamily font.pixelSize: Style.font.bodySmall - width: 36 + width: Style.space(36) horizontalAlignment: Text.AlignRight anchors.verticalCenter: parent.verticalCenter opacity: root.outputMuted ? 0.5 : 1.0 @@ -537,12 +537,12 @@ Item { // ---- Input ---- Column { width: parent.width - spacing: 6 + spacing: Style.space(6) visible: root.audioSources.length > 0 || !!root.source Row { width: parent.width - spacing: 8 + spacing: Style.space(8) PanelSectionHeader { text: "Input" @@ -558,7 +558,7 @@ Item { font.family: root.bar.fontFamily font.pixelSize: Style.font.bodySmall elide: Text.ElideRight - width: parent.width - 56 + width: parent.width - Style.space(56) anchors.verticalCenter: parent.verticalCenter } } @@ -567,7 +567,7 @@ Item { id: inputSliderRow visible: !!root.source width: parent.width - height: inputSliderInner.implicitHeight + 8 + height: inputSliderInner.implicitHeight + Style.spacing.controlGap hasCursor: root.focusSection === "input" && root.selectedIndex === -1 onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(inputSliderRow) foreground: root.bar.foreground @@ -576,9 +576,9 @@ Item { Row { id: inputSliderInner anchors.fill: parent - anchors.leftMargin: 6 - anchors.rightMargin: 6 - spacing: 8 + anchors.leftMargin: Style.space(6) + anchors.rightMargin: Style.space(6) + spacing: Style.space(8) Text { id: inputIconText @@ -586,7 +586,7 @@ Item { color: root.bar.foreground font.family: root.bar.fontFamily font.pixelSize: Style.font.heading - width: 22 + width: Style.space(22) horizontalAlignment: Text.AlignHCenter anchors.verticalCenter: parent.verticalCenter opacity: root.inputMuted ? 0.5 : 1.0 @@ -601,7 +601,7 @@ Item { PanelSlider { id: inputSlider bar: root.bar - width: parent.width - inputIconText.width - inputPercent.width - 16 + width: parent.width - inputIconText.width - inputPercent.width - Style.space(16) anchors.verticalCenter: parent.verticalCenter minimum: 0 maximum: 1 @@ -619,7 +619,7 @@ Item { color: root.bar.foreground font.family: root.bar.fontFamily font.pixelSize: Style.font.bodySmall - width: 36 + width: Style.space(36) horizontalAlignment: Text.AlignRight anchors.verticalCenter: parent.verticalCenter opacity: root.inputMuted ? 0.5 : 1.0 @@ -654,7 +654,7 @@ Item { // ---- Per-app streams ---- Column { width: parent.width - spacing: 6 + spacing: Style.space(6) visible: root.audioStreams.length > 0 PanelSectionHeader { @@ -698,23 +698,23 @@ Item { foreground: root.bar.foreground fill: root.hoverFill currentFill: root.selectedFill - implicitHeight: sinkInner.implicitHeight + 10 + implicitHeight: sinkInner.implicitHeight + Style.spacing.xl Row { id: sinkInner anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 6 - anchors.rightMargin: 6 - spacing: 8 + anchors.leftMargin: Style.space(6) + anchors.rightMargin: Style.space(6) + spacing: Style.space(8) Text { text: root.sinkGlyph(sinkRow.node) color: root.bar.foreground font.family: root.bar.fontFamily font.pixelSize: Style.font.title - width: 22 + width: Style.space(22) horizontalAlignment: Text.AlignHCenter anchors.verticalCenter: parent.verticalCenter } @@ -725,7 +725,7 @@ Item { font.family: root.bar.fontFamily font.pixelSize: Style.font.body elide: Text.ElideRight - width: parent.width - 22 - 14 - 16 + width: parent.width - Style.space(22) - Style.space(14) - Style.space(16) anchors.verticalCenter: parent.verticalCenter } @@ -734,7 +734,7 @@ Item { color: root.bar.foreground font.family: root.bar.fontFamily font.pixelSize: Style.font.subtitle - width: 14 + width: Style.space(14) horizontalAlignment: Text.AlignRight anchors.verticalCenter: parent.verticalCenter } @@ -765,23 +765,23 @@ Item { foreground: root.bar.foreground fill: root.hoverFill currentFill: root.selectedFill - implicitHeight: sourceInner.implicitHeight + 10 + implicitHeight: sourceInner.implicitHeight + Style.spacing.xl Row { id: sourceInner anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 6 - anchors.rightMargin: 6 - spacing: 8 + anchors.leftMargin: Style.space(6) + anchors.rightMargin: Style.space(6) + spacing: Style.space(8) Text { text: root.sourceGlyph(sourceRow.node) color: root.bar.foreground font.family: root.bar.fontFamily font.pixelSize: Style.font.title - width: 22 + width: Style.space(22) horizontalAlignment: Text.AlignHCenter anchors.verticalCenter: parent.verticalCenter } @@ -792,7 +792,7 @@ Item { font.family: root.bar.fontFamily font.pixelSize: Style.font.body elide: Text.ElideRight - width: parent.width - 22 - 14 - 16 + width: parent.width - Style.space(22) - Style.space(14) - Style.space(16) anchors.verticalCenter: parent.verticalCenter } @@ -801,7 +801,7 @@ Item { color: root.bar.foreground font.family: root.bar.fontFamily font.pixelSize: Style.font.subtitle - width: 14 + width: Style.space(14) horizontalAlignment: Text.AlignRight anchors.verticalCenter: parent.verticalCenter } @@ -836,20 +836,20 @@ Item { foreground: root.bar.foreground fill: root.hoverFill currentFill: root.selectedFill - implicitHeight: streamColumn.implicitHeight + 8 + implicitHeight: streamColumn.implicitHeight + Style.spacing.rowGap Column { id: streamColumn anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 6 - anchors.rightMargin: 6 - spacing: 2 + anchors.leftMargin: Style.space(6) + anchors.rightMargin: Style.space(6) + spacing: Style.space(2) Row { width: parent.width - spacing: 6 + spacing: Style.space(6) Text { id: streamMuteIcon @@ -857,7 +857,7 @@ Item { color: root.bar.foreground font.family: root.bar.fontFamily font.pixelSize: Style.font.body - width: 14 + width: Style.space(14) horizontalAlignment: Text.AlignHCenter anchors.verticalCenter: parent.verticalCenter opacity: streamRow.streamMuted ? 0.5 : 1.0 @@ -878,7 +878,7 @@ Item { font.family: root.bar.fontFamily font.pixelSize: Style.font.bodySmall elide: Text.ElideRight - width: parent.width - streamMuteIcon.width - streamPct.width - 12 + width: parent.width - streamMuteIcon.width - streamPct.width - Style.space(12) anchors.verticalCenter: parent.verticalCenter } @@ -888,7 +888,7 @@ Item { color: Qt.darker(root.bar.foreground, 1.5) font.family: root.bar.fontFamily font.pixelSize: Style.font.bodySmall - width: 36 + width: Style.space(36) horizontalAlignment: Text.AlignRight anchors.verticalCenter: parent.verticalCenter } diff --git a/shell/plugins/bar/widgets/bluetoothPanel.qml b/shell/plugins/bar/widgets/bluetoothPanel.qml index 3ee6d4d3..137fd90c 100644 --- a/shell/plugins/bar/widgets/bluetoothPanel.qml +++ b/shell/plugins/bar/widgets/bluetoothPanel.qml @@ -110,10 +110,10 @@ Item { property string focusedKnownAddress: "" readonly property color hoverFill: bar - ? Qt.rgba(bar.foreground.r, bar.foreground.g, bar.foreground.b, 0.08) + ? Style.hoverFillFor(bar.foreground, Color.accent) : "transparent" readonly property color selectedFill: bar - ? Qt.rgba(bar.foreground.r, bar.foreground.g, bar.foreground.b, 0.18) + ? Style.selectedFillFor(bar.foreground, Color.accent) : "transparent" function sectionCount(section) { @@ -362,8 +362,8 @@ Item { owner: root bar: root.bar open: root.popupOpen - contentWidth: 320 - contentHeight: column.implicitHeight + 28 + contentWidth: panel.fittedContentWidth(Style.space(320)) + contentHeight: panel.fittedContentHeight(column.implicitHeight) PanelKeyCatcher { id: keyCatcher @@ -379,7 +379,7 @@ Item { Column { id: column anchors.fill: parent - spacing: 10 + spacing: Style.space(10) // Header: title left, on/off toggle + actions right. Item { @@ -389,7 +389,7 @@ Item { Row { anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter - spacing: 8 + spacing: Style.space(8) PanelSectionHeader { id: titleText @@ -412,7 +412,7 @@ Item { Row { anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - spacing: 4 + spacing: Style.space(4) HeaderPill { pillIndex: 0 @@ -447,7 +447,7 @@ Item { Flickable { id: deviceFlick width: parent.width - height: Math.min(deviceList.implicitHeight, 400) + height: Math.min(deviceList.implicitHeight, Style.space(400)) contentWidth: width contentHeight: deviceList.implicitHeight clip: true @@ -458,7 +458,7 @@ Item { Column { id: deviceList width: parent.width - spacing: 10 + spacing: Style.space(10) // Paired / known devices. Repeater { @@ -526,8 +526,8 @@ Item { tooltipForeground: root.bar.foreground foreground: root.bar.foreground fontFamily: root.bar.fontFamily - horizontalPadding: 6 - verticalPadding: 4 + horizontalPadding: Style.spacing.md + verticalPadding: Style.spacing.labelGap iconSize: 14 enabled: pillEnabled opacity: pillEnabled ? 1 : 0.4 @@ -622,7 +622,7 @@ Item { return Qt.darker(root.bar.foreground, 1.5) } - implicitHeight: rowContent.implicitHeight + 12 + implicitHeight: rowContent.implicitHeight + Style.spacing.rowPaddingX MouseArea { id: rowMouse @@ -664,8 +664,8 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 10 - anchors.rightMargin: 10 + anchors.leftMargin: Style.space(10) + anchors.rightMargin: Style.space(10) implicitHeight: Math.max(deviceIcon.implicitHeight, info.implicitHeight, disconnectBtn.implicitHeight) Text { @@ -705,11 +705,11 @@ Item { Column { id: info - spacing: 1 + spacing: Style.space(1) anchors.left: deviceIcon.right - anchors.leftMargin: 10 + anchors.leftMargin: Style.space(10) anchors.right: disconnectBtn.visible ? disconnectBtn.left : parent.right - anchors.rightMargin: disconnectBtn.visible ? 8 : 0 + anchors.rightMargin: disconnectBtn.visible ? Style.space(8) : 0 anchors.verticalCenter: parent.verticalCenter Text { diff --git a/shell/plugins/bar/widgets/calendar.qml b/shell/plugins/bar/widgets/calendar.qml index 45503505..a069e812 100644 --- a/shell/plugins/bar/widgets/calendar.qml +++ b/shell/plugins/bar/widgets/calendar.qml @@ -83,12 +83,12 @@ Item { bar: root.bar owner: root open: root.popupOpen - contentWidth: 300 - contentHeight: header.implicitHeight + grid.implicitHeight + 36 + contentWidth: popup.fittedContentWidth(Style.space(300)) + contentHeight: popup.fittedContentHeight(header.implicitHeight + grid.implicitHeight + Style.spacing.rowGap) Column { anchors.fill: parent - spacing: 8 + spacing: Style.space(8) Item { id: header @@ -101,8 +101,8 @@ Item { anchors.verticalCenter: parent.verticalCenter iconText: "󰅁" foreground: root.bar.foreground - horizontalPadding: 8 - verticalPadding: 4 + horizontalPadding: Style.spacing.controlGap + verticalPadding: Style.spacing.labelGap onClicked: root.shiftMonth(-1) } @@ -121,8 +121,8 @@ Item { anchors.verticalCenter: parent.verticalCenter iconText: "󰅂" foreground: root.bar.foreground - horizontalPadding: 8 - verticalPadding: 4 + horizontalPadding: Style.spacing.controlGap + verticalPadding: Style.spacing.labelGap onClicked: root.shiftMonth(1) } } @@ -130,8 +130,8 @@ Item { Grid { id: grid columns: 7 - rowSpacing: 4 - columnSpacing: 4 + rowSpacing: Style.space(4) + columnSpacing: Style.space(4) width: parent.width Repeater { @@ -140,7 +140,7 @@ Item { Item { required property string modelData width: (grid.width - grid.columnSpacing * 6) / 7 - height: 18 + height: Math.max(Style.space(18), Style.font.caption + Style.spacing.xxs) Text { anchors.centerIn: parent @@ -177,7 +177,7 @@ Item { } width: (grid.width - grid.columnSpacing * 6) / 7 - height: 28 + height: Math.max(Style.space(28), Style.font.body + Style.spacing.sm) radius: 4 color: isToday ? root.bar.foreground : "transparent" border.color: isToday ? root.bar.foreground : "transparent" diff --git a/shell/plugins/bar/widgets/lockKeys.qml b/shell/plugins/bar/widgets/lockKeys.qml index 71e803de..87b5f648 100644 --- a/shell/plugins/bar/widgets/lockKeys.qml +++ b/shell/plugins/bar/widgets/lockKeys.qml @@ -61,8 +61,8 @@ Item { readonly property bool vertical: bar ? bar.vertical : false - implicitWidth: vertical ? (bar ? bar.barSize : 28) : (lay.item ? lay.item.implicitWidth + 8 : 0) - implicitHeight: vertical ? (lay.item ? lay.item.implicitHeight + 8 : 0) : (bar ? bar.barSize : 26) + implicitWidth: vertical ? (bar ? bar.barSize : Style.bar.sizeVertical) : (lay.item ? lay.item.implicitWidth + Style.spacing.controlGap : 0) + implicitHeight: vertical ? (lay.item ? lay.item.implicitHeight + Style.spacing.controlGap : 0) : (bar ? bar.barSize : Style.bar.sizeHorizontal) Loader { id: lay @@ -73,7 +73,7 @@ Item { Component { id: rowLayout Row { - spacing: 4 + spacing: Style.space(4) LockGlyph { glyph: "A"; active: root.capsOn; visible: !root.hideWhenOff || root.capsOn } LockGlyph { glyph: "1"; active: root.numOn; visible: !root.hideWhenOff || root.numOn } LockGlyph { glyph: "S"; active: root.scrollOn; visible: !root.hideWhenOff || root.scrollOn } @@ -83,7 +83,7 @@ Item { Component { id: colLayout Column { - spacing: 2 + spacing: Style.space(2) LockGlyph { glyph: "A"; active: root.capsOn; visible: !root.hideWhenOff || root.capsOn } LockGlyph { glyph: "1"; active: root.numOn; visible: !root.hideWhenOff || root.numOn } LockGlyph { glyph: "S"; active: root.scrollOn; visible: !root.hideWhenOff || root.scrollOn } diff --git a/shell/plugins/bar/widgets/media.qml b/shell/plugins/bar/widgets/media.qml index 4bc52110..655be058 100644 --- a/shell/plugins/bar/widgets/media.qml +++ b/shell/plugins/bar/widgets/media.qml @@ -39,13 +39,13 @@ Item { property real maxLabelWidth: 180 visible: hasMedia - implicitWidth: hasMedia ? row.implicitWidth + 14 : 0 + implicitWidth: hasMedia ? row.implicitWidth + Style.space(14) : 0 implicitHeight: bar ? bar.barSize : 26 Row { id: row anchors.centerIn: parent - spacing: 6 + spacing: Style.space(6) Text { id: glyph @@ -119,29 +119,29 @@ Item { bar: root.bar owner: root open: root.popupOpen - contentWidth: 320 - contentHeight: column.implicitHeight + 28 + contentWidth: popup.fittedContentWidth(Style.space(320)) + contentHeight: popup.fittedContentHeight(column.implicitHeight) Column { id: column anchors.fill: parent - spacing: 10 + spacing: Style.space(10) Row { - spacing: 10 + spacing: Style.space(10) width: parent.width Rectangle { - width: 64 - height: 64 - radius: 4 - color: Qt.rgba(root.bar.foreground.r, root.bar.foreground.g, root.bar.foreground.b, 0.08) - border.color: Qt.rgba(root.bar.foreground.r, root.bar.foreground.g, root.bar.foreground.b, 0.2) - border.width: 1 + width: Style.space(64) + height: Style.space(64) + radius: Style.spacing.labelGap + color: Style.normalFillFor(root.bar.foreground, Color.accent) + border.color: Style.normalBorderFor(root.bar.foreground, Color.accent) + border.width: Style.normalBorderWidth Image { anchors.fill: parent - anchors.margins: 2 + anchors.margins: Style.space(2) fillMode: Image.PreserveAspectCrop asynchronous: true source: root.activePlayer && root.activePlayer.trackArtUrl ? root.activePlayer.trackArtUrl : "" @@ -159,8 +159,8 @@ Item { } Column { - spacing: 4 - width: parent.width - 74 + spacing: Style.space(4) + width: parent.width - Style.space(74) Text { text: root.title || "Nothing playing" @@ -196,13 +196,13 @@ Item { Row { anchors.horizontalCenter: parent.horizontalCenter - spacing: 6 + spacing: Style.space(6) Button { iconText: "󰒮" foreground: root.bar.foreground - horizontalPadding: 10 - verticalPadding: 6 + horizontalPadding: Style.spacing.controlPaddingX + verticalPadding: Style.spacing.controlPaddingY enabled: root.activePlayer && root.activePlayer.canGoPrevious opacity: enabled ? 1.0 : 0.4 onClicked: if (root.activePlayer) root.activePlayer.previous() @@ -211,9 +211,9 @@ Item { Button { iconText: root.activePlayer && root.activePlayer.isPlaying ? "󰏤" : "󰐊" foreground: root.bar.foreground - horizontalPadding: 14 - verticalPadding: 6 - iconSize: 18 + horizontalPadding: Style.spacing.panelGap + verticalPadding: Style.spacing.controlPaddingY + iconSize: Style.font.iconLarge enabled: root.activePlayer && root.activePlayer.canTogglePlaying opacity: enabled ? 1.0 : 0.4 onClicked: if (root.activePlayer) root.activePlayer.togglePlaying() @@ -222,8 +222,8 @@ Item { Button { iconText: "󰒭" foreground: root.bar.foreground - horizontalPadding: 10 - verticalPadding: 6 + horizontalPadding: Style.spacing.controlPaddingX + verticalPadding: Style.spacing.controlPaddingY enabled: root.activePlayer && root.activePlayer.canGoNext opacity: enabled ? 1.0 : 0.4 onClicked: if (root.activePlayer) root.activePlayer.next() diff --git a/shell/plugins/bar/widgets/monitorPanel.qml b/shell/plugins/bar/widgets/monitorPanel.qml index a72b7705..4548cdea 100644 --- a/shell/plugins/bar/widgets/monitorPanel.qml +++ b/shell/plugins/bar/widgets/monitorPanel.qml @@ -367,8 +367,8 @@ Item { owner: root bar: root.bar open: root.popupOpen - contentWidth: 320 - contentHeight: Math.min(560, panelColumn.implicitHeight + 28) + contentWidth: panel.fittedContentWidth(Style.space(320)) + contentHeight: panel.fittedContentHeight(panelColumn.implicitHeight, Style.space(560)) PanelKeyCatcher { id: keyCatcher @@ -393,12 +393,12 @@ Item { Column { id: panelColumn width: scrollArea.availableWidth - spacing: 14 + spacing: Style.space(14) // ---- Brightness ---- Column { width: parent.width - spacing: 6 + spacing: Style.space(6) PanelSectionHeader { text: "Brightness" @@ -411,7 +411,7 @@ Item { id: brightnessRow visible: root.brightnessAvailable width: parent.width - height: brightnessInner.implicitHeight + 8 + height: brightnessInner.implicitHeight + Style.spacing.controlGap hasCursor: root.focusSection === "brightness" && root.selectedIndex === -1 onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(brightnessRow) foreground: root.bar.foreground @@ -420,16 +420,16 @@ Item { Row { id: brightnessInner anchors.fill: parent - anchors.leftMargin: 6 - anchors.rightMargin: 6 - spacing: 8 + anchors.leftMargin: Style.space(6) + anchors.rightMargin: Style.space(6) + spacing: Style.space(8) Text { text: "󰃠" color: root.bar.foreground font.family: root.bar.fontFamily font.pixelSize: Style.font.heading - width: 22 + width: Style.space(22) horizontalAlignment: Text.AlignHCenter anchors.verticalCenter: parent.verticalCenter } @@ -437,7 +437,7 @@ Item { PanelSlider { id: brightnessSlider bar: root.bar - width: parent.width - 22 - brightnessLabel.width - 16 + width: parent.width - Style.space(22) - brightnessLabel.width - Style.space(16) anchors.verticalCenter: parent.verticalCenter minimum: 1 maximum: 100 @@ -457,7 +457,7 @@ Item { color: root.bar.foreground font.family: root.bar.fontFamily font.pixelSize: Style.font.bodySmall - width: 36 + width: Style.space(36) horizontalAlignment: Text.AlignRight anchors.verticalCenter: parent.verticalCenter } @@ -483,7 +483,7 @@ Item { // ---- Scale ---- Column { width: parent.width - spacing: 6 + spacing: Style.space(6) PanelSectionHeader { text: "Scale" @@ -494,7 +494,7 @@ Item { Row { width: parent.width - spacing: 6 + spacing: Style.space(6) Repeater { model: root.scaleValues @@ -503,7 +503,7 @@ Item { required property string modelData required property int index - width: (panelColumn.width - 30) / 6 + width: (panelColumn.width - Style.space(30)) / 6 text: modelData + "x" foreground: root.bar.foreground background: "transparent" @@ -512,7 +512,7 @@ Item { fontFamily: root.bar.fontFamily fontSize: Style.font.bodySmall horizontalPadding: 0 - verticalPadding: 6 + verticalPadding: Style.spacing.controlPaddingY active: root.normalizeScale(root.monitorScale) === root.normalizeScale(modelData) hasCursor: root.focusSection === "scale" && root.selectedIndex === index onClicked: root.setScale(modelData) @@ -530,7 +530,7 @@ Item { // ---- Monitors ---- Column { width: parent.width - spacing: 6 + spacing: Style.space(6) visible: root.displays.length > 0 PanelSectionHeader { diff --git a/shell/plugins/bar/widgets/networkPanel.qml b/shell/plugins/bar/widgets/networkPanel.qml index 6679e42c..6e48e43d 100644 --- a/shell/plugins/bar/widgets/networkPanel.qml +++ b/shell/plugins/bar/widgets/networkPanel.qml @@ -69,8 +69,8 @@ Item { // Mouse hover and keyboard nav both mutate this state at the root; items // never read containsMouse for visuals. See CursorSurface for the // shared chrome (fill / border) shared by NetworkRow and DnsProviderPill. - readonly property color hoverFill: bar ? Qt.rgba(bar.foreground.r, bar.foreground.g, bar.foreground.b, 0.08) : "transparent" - readonly property color selectedFill: bar ? Qt.rgba(bar.foreground.r, bar.foreground.g, bar.foreground.b, 0.18) : "transparent" + readonly property color hoverFill: bar ? Style.hoverFillFor(bar.foreground, Color.accent) : "transparent" + readonly property color selectedFill: bar ? Style.selectedFillFor(bar.foreground, Color.accent) : "transparent" // The panel below is its own layer-shell with Exclusive keyboard focus, // so Hyprland grants focus when the surface is mapped (popupOpen flips @@ -599,8 +599,8 @@ iwctl station "$station" get-networks rssi-dbms 2>/dev/null \\ owner: root bar: root.bar open: root.popupOpen - contentWidth: 340 - contentHeight: column.implicitHeight + 28 + contentWidth: panel.fittedContentWidth(Style.space(340)) + contentHeight: panel.fittedContentHeight(column.implicitHeight) // Catches all unhandled keys for keyboard navigation. AfterItem priority // lets the passphrase TextField (a child via focus chain) get its keys @@ -647,7 +647,7 @@ iwctl station "$station" get-networks rssi-dbms 2>/dev/null \\ anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top - spacing: 12 + spacing: Style.space(12) // Header — interface name + type, refresh on the right. Item { @@ -658,7 +658,7 @@ iwctl station "$station" get-networks rssi-dbms 2>/dev/null \\ id: headerInfo anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter - spacing: 10 + spacing: Style.space(10) Text { text: root.icon @@ -669,7 +669,7 @@ iwctl station "$station" get-networks rssi-dbms 2>/dev/null \\ } Column { - spacing: 2 + spacing: Style.space(2) anchors.verticalCenter: parent.verticalCenter Text { @@ -706,9 +706,9 @@ iwctl station "$station" get-networks rssi-dbms 2>/dev/null \\ tooltipBackground: root.bar.background tooltipForeground: root.bar.foreground foreground: root.bar.foreground - horizontalPadding: 8 - verticalPadding: 4 - iconSize: 14 + horizontalPadding: Style.spacing.controlGap + verticalPadding: Style.spacing.labelGap + iconSize: Style.font.icon active: root.scanning onClicked: root.refresh() } @@ -724,8 +724,8 @@ iwctl station "$station" get-networks rssi-dbms 2>/dev/null \\ visible: !!root.info.iface width: parent.width columns: 2 - columnSpacing: 14 - rowSpacing: 4 + columnSpacing: Style.space(14) + rowSpacing: Style.space(4) // IP address. Text { @@ -815,7 +815,7 @@ iwctl station "$station" get-networks rssi-dbms 2>/dev/null \\ Column { width: parent.width - spacing: 8 + spacing: Style.space(8) PanelSectionHeader { text: "DNS provider" @@ -825,7 +825,7 @@ iwctl station "$station" get-networks rssi-dbms 2>/dev/null \\ Row { width: parent.width - spacing: 6 + spacing: Style.space(6) DnsProviderPill { provider: "DHCP" @@ -879,8 +879,8 @@ iwctl station "$station" get-networks rssi-dbms 2>/dev/null \\ id: networkList visible: root.wifiStationAvailable width: parent.width - height: Math.min(contentHeight, 240) - spacing: 4 + height: Math.min(contentHeight, Style.space(240)) + spacing: Style.space(4) clip: true boundsBehavior: Flickable.StopAtBounds interactive: contentHeight > height @@ -925,8 +925,8 @@ iwctl station "$station" get-networks rssi-dbms 2>/dev/null \\ tooltipBackground: root.bar.background tooltipForeground: root.bar.foreground fontFamily: root.bar.fontFamily - horizontalPadding: 10 - verticalPadding: 6 + horizontalPadding: Style.spacing.controlPaddingX + verticalPadding: Style.spacing.controlPaddingY // Map the panel's domain semantics onto Button's structural props: // `current DNS` is the pill's `active` fill; the keyboard cursor lights @@ -982,7 +982,7 @@ iwctl station "$station" get-networks rssi-dbms 2>/dev/null \\ return Qt.darker(root.bar.foreground, 1.5) } - implicitHeight: rowBody.implicitHeight + (isPasswordOpen ? passwordPanel.implicitHeight + 6 : 0) + implicitHeight: rowBody.implicitHeight + (isPasswordOpen ? passwordPanel.implicitHeight + Style.spacing.md : 0) MouseArea { id: rowMouse @@ -1040,9 +1040,9 @@ iwctl known-networks list 2>/dev/null \\ anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top - anchors.leftMargin: 10 - anchors.rightMargin: 10 - implicitHeight: Math.max(networkIcon.implicitHeight, networkInfo.implicitHeight, forgetBtn.implicitHeight) + 12 + anchors.leftMargin: Style.space(10) + anchors.rightMargin: Style.space(10) + implicitHeight: Math.max(networkIcon.implicitHeight, networkInfo.implicitHeight, forgetBtn.implicitHeight) + Style.spacing.rowPaddingX Text { id: networkIcon @@ -1072,12 +1072,12 @@ iwctl known-networks list 2>/dev/null \\ // Shows a lock glyph on the right for protected networks that // aren't currently connected. Once connected, the forget X takes // its place (and 'protected' is implied by the fact we're on it). - // Same 22-wide right-anchored centered geometry as forgetBtn so the - // glyph centers line up across rows. + // Same action-sized right-anchored centered geometry as forgetBtn so + // the glyph centers line up across rows. Text { id: lockIndicator visible: row.isProtected && !row.isConnected - width: 22 + width: Style.space(22) anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter horizontalAlignment: Text.AlignHCenter @@ -1089,13 +1089,13 @@ iwctl known-networks list 2>/dev/null \\ Column { id: networkInfo - spacing: 1 + spacing: Style.space(1) anchors.left: networkIcon.right - anchors.leftMargin: 10 + anchors.leftMargin: Style.space(10) anchors.right: forgetBtn.visible ? forgetBtn.left : lockIndicator.visible ? lockIndicator.left : parent.right - anchors.rightMargin: (forgetBtn.visible || lockIndicator.visible) ? 8 : 0 + anchors.rightMargin: (forgetBtn.visible || lockIndicator.visible) ? Style.space(8) : 0 anchors.verticalCenter: parent.verticalCenter Text { @@ -1133,24 +1133,24 @@ iwctl known-networks list 2>/dev/null \\ anchors.left: parent.left anchors.right: parent.right anchors.top: rowMouse.bottom - anchors.leftMargin: 10 - anchors.rightMargin: 10 - anchors.topMargin: 4 - implicitHeight: pwField.implicitHeight + 8 + anchors.leftMargin: Style.space(10) + anchors.rightMargin: Style.space(10) + anchors.topMargin: Style.space(4) + implicitHeight: pwField.implicitHeight + Style.spacing.rowGap TextField { id: pwField anchors.left: parent.left anchors.right: connectPwBtn.left anchors.verticalCenter: parent.verticalCenter - anchors.rightMargin: 6 + anchors.rightMargin: Style.space(6) password: true placeholderText: "Passphrase" font.family: root.bar.fontFamily font.pixelSize: Style.font.body foreground: root.bar.foreground - horizontalPadding: 8 - verticalPadding: 6 + horizontalPadding: Style.spacing.controlGap + verticalPadding: Style.spacing.controlPaddingY enabled: !row.isBusy onAccepted: { diff --git a/shell/plugins/bar/widgets/notificationCenter.qml b/shell/plugins/bar/widgets/notificationCenter.qml index b21b0d69..7dc75c4f 100644 --- a/shell/plugins/bar/widgets/notificationCenter.qml +++ b/shell/plugins/bar/widgets/notificationCenter.qml @@ -64,8 +64,8 @@ Item { // rest of the notification stack). readonly property color colForeground: Color.foreground readonly property color colDim: Qt.darker(Color.foreground, 1.4) - readonly property color colBorder: Qt.rgba(Color.foreground.r, Color.foreground.g, Color.foreground.b, 0.18) - readonly property color colSurface: Qt.rgba(Color.foreground.r, Color.foreground.g, Color.foreground.b, 0.06) + readonly property color colBorder: Style.normalBorderFor(Color.foreground, Color.accent) + readonly property color colSurface: Style.normalFillFor(Color.foreground, Color.accent) readonly property color colAccent: Color.accent readonly property int cardRadius: notificationService ? notificationService.cornerRadius : 0 @@ -109,17 +109,17 @@ Item { bar: root.bar owner: root open: root.popupOpen - contentWidth: 440 - contentHeight: 540 + contentWidth: popup.fittedContentWidth(Style.space(440)) + contentHeight: popup.cappedContentHeight(Style.space(540)) ColumnLayout { anchors.fill: parent - spacing: 10 + spacing: Style.space(10) // ----------------------------------------- header RowLayout { Layout.fillWidth: true - spacing: 8 + spacing: Style.space(8) Text { text: "Notifications" @@ -133,18 +133,18 @@ Item { Rectangle { id: dndPill - Layout.preferredHeight: 24 - Layout.preferredWidth: dndLabel.implicitWidth + dndGlyph.implicitWidth + 18 - radius: Math.min(12, root.cardRadius + 6) + Layout.preferredHeight: Math.max(Style.space(24), Style.font.bodySmall + Style.spacing.controlPaddingY * 2) + Layout.preferredWidth: dndLabel.implicitWidth + dndGlyph.implicitWidth + Style.space(18) + radius: Math.min(Style.space(12), root.cardRadius + Style.space(6)) color: dndOn ? root.colAccent : root.colSurface border.color: dndOn ? root.colAccent : root.colBorder - border.width: 1 + border.width: Style.normalBorderWidth readonly property bool dndOn: !!root.notificationService && root.notificationService.doNotDisturb Row { anchors.centerIn: parent - spacing: 4 + spacing: Style.space(4) Text { id: dndGlyph @@ -190,7 +190,7 @@ Item { readonly property bool isActive: root.activeTab === modelData.key Layout.fillWidth: true - Layout.preferredHeight: 30 + Layout.preferredHeight: Math.max(Style.space(30), Style.font.body + Style.spacing.controlPaddingY * 2) color: "transparent" Text { @@ -206,7 +206,7 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom - height: 2 + height: Math.max(1, Style.space(2)) color: parent.isActive ? root.colAccent : root.colBorder opacity: parent.isActive ? 1 : 0.4 } @@ -225,17 +225,17 @@ Item { Layout.fillWidth: true visible: (root.activeTab === "pending" && root.pendingCount > 0) || (root.activeTab === "past" && root.pastCount > 0) - spacing: 8 + spacing: Style.space(8) Item { Layout.fillWidth: true } Rectangle { - Layout.preferredWidth: actionLabel.implicitWidth + 16 - Layout.preferredHeight: 22 - radius: Math.min(6, root.cardRadius) + Layout.preferredWidth: actionLabel.implicitWidth + Style.space(16) + Layout.preferredHeight: Math.max(Style.space(22), Style.font.bodySmall + Style.spacing.controlPaddingY * 2) + radius: Math.min(Style.space(6), root.cardRadius) color: actionArea.containsMouse ? root.colBorder : "transparent" border.color: root.colBorder - border.width: 1 + border.width: Style.normalBorderWidth Text { id: actionLabel @@ -266,7 +266,7 @@ Item { Layout.fillWidth: true Layout.fillHeight: true clip: true - spacing: 8 + spacing: Style.space(8) readonly property bool onPending: root.activeTab === "pending" model: !root.notificationService ? null @@ -291,11 +291,11 @@ Item { readonly property string sanitizedBody: root.sanitizeBody(body, app, appIcon) width: listView.width - implicitHeight: rowContent.implicitHeight + 20 + implicitHeight: rowContent.implicitHeight + Style.spacing.panelGap radius: root.cardRadius color: "transparent" border.color: root.colBorder - border.width: 1 + border.width: Style.normalBorderWidth MouseArea { anchors.fill: parent @@ -308,13 +308,14 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 12 - anchors.rightMargin: 12 - spacing: 10 + anchors.leftMargin: Style.space(12) + anchors.rightMargin: Style.space(12) + spacing: Style.space(10) Item { - Layout.preferredWidth: 32 - Layout.preferredHeight: 32 + id: imageSlot + Layout.preferredWidth: Style.space(32) + Layout.preferredHeight: Style.space(32) Layout.alignment: Qt.AlignVCenter // Hide on icon load failure so unresolved themed-icon names // don't render Qt's broken-image placeholder. @@ -325,8 +326,8 @@ Item { anchors.fill: parent source: rowCard.hasMedia ? rowCard.image : rowCard.smallIconSource fillMode: rowCard.hasMedia ? Image.PreserveAspectCrop : Image.PreserveAspectFit - sourceSize.width: 32 * Screen.devicePixelRatio - sourceSize.height: 32 * Screen.devicePixelRatio + sourceSize.width: imageSlot.width * Screen.devicePixelRatio + sourceSize.height: imageSlot.height * Screen.devicePixelRatio asynchronous: true smooth: true } @@ -334,7 +335,7 @@ Item { ColumnLayout { Layout.fillWidth: true - spacing: 2 + spacing: Style.space(2) Text { Layout.fillWidth: true @@ -364,8 +365,8 @@ Item { } Rectangle { - Layout.preferredWidth: 18 - Layout.preferredHeight: 18 + Layout.preferredWidth: Style.space(18) + Layout.preferredHeight: Style.space(18) Layout.alignment: Qt.AlignVCenter radius: Math.min(4, root.cardRadius) color: rowCloseArea.containsMouse ? root.colBorder : "transparent" @@ -402,16 +403,14 @@ Item { ColumnLayout { anchors.centerIn: parent - spacing: 6 + spacing: Style.space(6) Text { Layout.alignment: Qt.AlignHCenter text: "󰂚" font.family: root.bar ? root.bar.fontFamily : "" color: root.colBorder - // Deliberately oversized empty-state glyph; doesn't follow the - // Style.font.* scale because it's a one-off decorative element. - font.pixelSize: 36 + font.pixelSize: Style.font.displayLarge } Text { @@ -420,8 +419,6 @@ Item { ? "Nothing waiting for you" : "Nothing recent" font.family: root.bar ? root.bar.fontFamily : "" - ? "Nothing waiting for you" - : "No past notifications" color: root.colDim font.pixelSize: Style.font.body } diff --git a/shell/plugins/bar/widgets/systemStats.qml b/shell/plugins/bar/widgets/systemStats.qml index fdba597f..c23024fc 100644 --- a/shell/plugins/bar/widgets/systemStats.qml +++ b/shell/plugins/bar/widgets/systemStats.qml @@ -177,13 +177,13 @@ Item { bar: root.bar open: root.popupOpen triggerMode: "hover" - contentWidth: 320 - contentHeight: detailColumn.implicitHeight + 28 + contentWidth: popup.fittedContentWidth(Style.space(320)) + contentHeight: popup.fittedContentHeight(detailColumn.implicitHeight) Column { id: detailColumn anchors.fill: parent - spacing: 10 + spacing: Style.space(10) Text { text: "System" @@ -213,7 +213,7 @@ Item { Row { width: parent.width - spacing: 6 + spacing: Style.space(6) Text { text: "Load" color: Qt.darker(root.bar.foreground, 1.5) @@ -239,7 +239,7 @@ Item { property color barFg: "#cacccc" property string fontFamily: "JetBrainsMono Nerd Font" - spacing: 4 + spacing: Style.space(4) Row { width: parent.width @@ -261,7 +261,7 @@ Item { Canvas { id: detailCanvas width: parent.width - height: 40 + height: Style.space(40) property var history: detail.history onHistoryChanged: requestPaint() diff --git a/shell/plugins/bar/widgets/weatherFlyout.qml b/shell/plugins/bar/widgets/weatherFlyout.qml index 4e31b6ce..ec32a87a 100644 --- a/shell/plugins/bar/widgets/weatherFlyout.qml +++ b/shell/plugins/bar/widgets/weatherFlyout.qml @@ -67,7 +67,7 @@ Item { readonly property string reportHumidity: current ? (current.humidity + "%") : "" visible: label !== "" - implicitWidth: button.implicitWidth + 8 + implicitWidth: button.implicitWidth + Style.spacing.controlGap implicitHeight: button.implicitHeight function setting(name, fallback) { @@ -322,15 +322,22 @@ Item { open: root.popupOpen centerOnBar: true triggerMode: "click" - contentWidth: 480 - contentHeight: card.implicitHeight + 28 - margin: 24 + contentWidth: popup.fittedContentWidth(Style.space(480)) + contentHeight: popup.fittedContentHeight(weatherColumn.implicitHeight) borderColor: Color.notifications.border - Column { - id: card + Flickable { + id: weatherScroll anchors.fill: parent - spacing: 14 + contentWidth: width + contentHeight: weatherColumn.implicitHeight + clip: true + boundsBehavior: Flickable.StopAtBounds + + Column { + id: weatherColumn + width: weatherScroll.width + spacing: Style.space(14) // ---- Hero row: big icon + temp on the left; location and stats stacked on the right. Item { @@ -340,9 +347,9 @@ Item { Row { id: heroLeft anchors.left: parent.left - anchors.leftMargin: 16 + anchors.leftMargin: Style.space(16) anchors.verticalCenter: parent.verticalCenter - spacing: 16 + spacing: Style.space(16) Text { id: heroIcon @@ -358,7 +365,7 @@ Item { Row { anchors.verticalCenter: parent.verticalCenter - spacing: 2 + spacing: Style.space(2) Text { id: tempBig @@ -376,7 +383,7 @@ Item { font.family: root.bar.fontFamily font.pixelSize: Style.font.display anchors.top: tempBig.top - anchors.topMargin: 10 + anchors.topMargin: Style.space(10) } } } @@ -384,13 +391,13 @@ Item { Column { id: heroRight anchors.right: parent.right - anchors.rightMargin: 20 + anchors.rightMargin: Style.space(20) anchors.verticalCenter: parent.verticalCenter - spacing: 12 + spacing: Style.space(12) Row { visible: root.reportLocation !== "" - spacing: 6 + spacing: Style.space(6) Text { text: "" // nf-fa-map_marker @@ -411,10 +418,10 @@ Item { Row { visible: !!root.current - spacing: 36 + spacing: Style.space(36) Column { - spacing: 5 + spacing: Style.space(5) Text { text: "FEELS" color: Qt.darker(root.bar.foreground, 1.5) @@ -431,7 +438,7 @@ Item { } Column { - spacing: 5 + spacing: Style.space(5) Text { text: "WIND" color: Qt.darker(root.bar.foreground, 1.5) @@ -448,7 +455,7 @@ Item { } Column { - spacing: 5 + spacing: Style.space(5) Text { text: "HUMID" color: Qt.darker(root.bar.foreground, 1.5) @@ -480,7 +487,7 @@ Item { Rectangle { visible: root.forecastDays.length > 0 width: parent.width - height: 1 + height: Style.spacing.hairline color: root.bar.foreground opacity: 0.12 } @@ -495,7 +502,7 @@ Item { Row { id: forecastRow anchors.horizontalCenter: parent.horizontalCenter - spacing: 44 + spacing: Style.space(44) Repeater { model: root.forecastDays @@ -503,7 +510,7 @@ Item { Row { required property var modelData required property int index - spacing: 10 + spacing: Style.space(10) Text { anchors.verticalCenter: parent.verticalCenter @@ -515,7 +522,7 @@ Item { Column { anchors.verticalCenter: parent.verticalCenter - spacing: 2 + spacing: Style.space(2) Text { text: root.dayName(modelData.date).toUpperCase() @@ -526,7 +533,7 @@ Item { } Row { - spacing: 6 + spacing: Style.space(6) Text { text: root.bareTempForDay(modelData, "max") @@ -548,6 +555,7 @@ Item { } } } + } // Poll the weather pill text/class every minute. Local to this widget. Process { diff --git a/shell/plugins/clipboard-picker/ClipboardPicker.qml b/shell/plugins/clipboard-picker/ClipboardPicker.qml index 368179d3..e2e20fb4 100644 --- a/shell/plugins/clipboard-picker/ClipboardPicker.qml +++ b/shell/plugins/clipboard-picker/ClipboardPicker.qml @@ -23,12 +23,12 @@ Item { property color border: foreground readonly property int cornerRadius: Style.cornerRadius property string fontFamily: Quickshell.env("OMARCHY_MENU_FONT") || "monospace" - property int contentMargin: 18 - property int headerHeight: 34 - property int contentSpacing: 6 - property int cardWidth: 800 - property int cardHeight: 600 - property int rowHeight: 50 + property int contentMargin: Style.spacing.panelPadding + property int headerHeight: Math.max(Style.space(34), Style.font.title + Style.spacing.controlPaddingY * 2) + property int contentSpacing: Style.spacing.md + property int cardWidth: Math.min(Style.space(800), panel.width - Style.gapsOut * 2) + property int cardHeight: Math.min(Style.space(600), panel.height - Style.gapsOut * 2) + property int rowHeight: Math.max(Style.space(50), Style.font.body + Style.font.caption + Style.spacing.rowPaddingX * 2) function open(payloadJson) { root.opened = true @@ -181,7 +181,7 @@ Item { anchors.centerIn: parent color: root.background border.color: root.border - border.width: 2 + border.width: Math.max(1, Style.space(2)) MouseArea { anchors.fill: parent; onClicked: {} } @@ -260,7 +260,7 @@ Item { height: parent.height model: displayModel clip: true - spacing: 4 + spacing: Style.space(4) boundsBehavior: Flickable.StopAtBounds delegate: Rectangle { @@ -273,31 +273,33 @@ Item { width: ListView.view.width height: root.rowHeight radius: root.cornerRadius - color: index === root.selectedIndex ? root.withAlpha(root.foreground, 0.08) : root.withAlpha(root.foreground, mouseArea.containsMouse ? 0.045 : 0) + color: index === root.selectedIndex ? Style.hoverFillFor(root.foreground, root.accent) : "transparent" + border.color: index === root.selectedIndex ? Style.hoverBorderFor(root.foreground, root.accent) : "transparent" + border.width: index === root.selectedIndex ? Style.hoverBorderWidth : 0 Rectangle { visible: false - width: 4 - height: parent.height - 18 - radius: Math.min(root.cornerRadius, 4) + width: Style.space(4) + height: parent.height - Style.space(18) + radius: Math.min(root.cornerRadius, Style.space(4)) color: root.accent anchors.left: parent.left - anchors.leftMargin: 8 + anchors.leftMargin: Style.space(8) anchors.verticalCenter: parent.verticalCenter } Item { anchors.fill: parent - anchors.leftMargin: 12 - anchors.rightMargin: 12 - anchors.topMargin: 8 - anchors.bottomMargin: 8 + anchors.leftMargin: Style.space(12) + anchors.rightMargin: Style.space(12) + anchors.topMargin: Style.space(8) + anchors.bottomMargin: Style.space(8) Text { width: parent.width height: parent.height text: parent.parent.isPassword ? "••••••••" : (parent.parent.previewType === "text" ? parent.parent.previewText : "Image") - color: index === root.selectedIndex ? root.accent : root.foreground + color: index === root.selectedIndex ? Style.hoverStateColor(root.foreground, root.accent) : root.foreground font.family: root.fontFamily font.pixelSize: Style.font.title font.italic: parent.parent.previewType === "file" || parent.parent.isPassword @@ -313,6 +315,7 @@ Item { anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor + onContainsMouseChanged: if (containsMouse) root.selectedIndex = index onClicked: { root.selectedIndex = index root.activateIndex(index) @@ -327,7 +330,7 @@ Item { radius: root.cornerRadius color: root.withAlpha(root.background, 0.5) border.color: root.withAlpha(root.border, 0.1) - border.width: 1 + border.width: Style.normalBorderWidth clip: true property var activeRow: displayModel.count > 0 && root.selectedIndex >= 0 && root.selectedIndex < displayModel.count ? displayModel.get(root.selectedIndex) : null @@ -335,7 +338,7 @@ Item { Text { visible: parent.activeRow && parent.activeRow.previewType === "text" anchors.fill: parent - anchors.margins: 16 + anchors.margins: Style.space(16) text: parent.activeRow ? (parent.activeRow.isPassword ? "••••••••" : parent.activeRow.previewText) : "" color: root.foreground font.family: root.fontFamily @@ -348,7 +351,7 @@ Item { Image { visible: parent.activeRow && parent.activeRow.previewType === "file" anchors.fill: parent - anchors.margins: 16 + anchors.margins: Style.space(16) source: parent.activeRow ? parent.activeRow.previewImage : "" fillMode: Image.PreserveAspectFit } @@ -357,7 +360,7 @@ Item { Column { anchors.centerIn: parent - spacing: 8 + spacing: Style.space(8) visible: displayModel.count === 0 Text { diff --git a/shell/plugins/dev-gallery/GalleryPanel.qml b/shell/plugins/dev-gallery/GalleryPanel.qml index 2a7f0520..deca77b8 100644 --- a/shell/plugins/dev-gallery/GalleryPanel.qml +++ b/shell/plugins/dev-gallery/GalleryPanel.qml @@ -334,18 +334,18 @@ Item { ScrollView { id: scrollArea anchors.fill: parent - anchors.margins: 18 + anchors.margins: Style.space(18) clip: true ScrollBar.horizontal.policy: ScrollBar.AlwaysOff Column { width: scrollArea.availableWidth - spacing: 22 + spacing: Style.space(22) // ---- Header ------------------------------------------------------ Column { width: parent.width - spacing: 4 + spacing: Style.space(4) Text { text: "Omarchy shell · dev gallery" @@ -369,7 +369,7 @@ Item { // ---- Kit conventions --------------------------------------------- Column { width: parent.width - spacing: 8 + spacing: Style.space(8) Text { text: "Conventions" @@ -381,7 +381,7 @@ Item { Rectangle { width: parent.width - implicitHeight: conventionsCol.implicitHeight + 24 + implicitHeight: conventionsCol.implicitHeight + Style.spacing.rowPaddingX * 2 color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04) radius: Style.cornerRadius border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10) @@ -392,9 +392,9 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 14 - anchors.rightMargin: 14 - spacing: 8 + anchors.leftMargin: Style.space(14) + anchors.rightMargin: Style.space(14) + spacing: Style.space(8) Text { width: parent.width @@ -402,7 +402,7 @@ Item { color: Qt.darker(root.foreground, 1.4) font.family: root.fontFamily font.pixelSize: Style.font.bodySmall - text: "Theme. qs.Commons.Style exposes cornerRadius (mirrored from Hyprland's decoration:rounding), focusBorderColor / focusFillColor / focusBorderWidth (derived from Color.accent), and hotFill (the shared hover/cursor tint). qs.Commons.Color exposes foreground / background / accent / urgent plus per-surface roles. Components default-bind to these so a caller with no overrides matches the active theme." + text: "Theme. qs.Commons.Style exposes cornerRadius plus shared normal / hover-cursor / selected / focus state tokens (state colors, fill alphas, border widths, and border alphas), spacing tokens, typography, and bar dimensions. Focus defaults to hover-cursor; selected borders are off by default. Border widths are the theme-level on/off switch for state borders. qs.Commons.Color exposes foreground / background / accent / urgent plus per-surface roles. Components default-bind to these so a caller with no overrides matches the active theme." } Text { width: parent.width @@ -410,7 +410,7 @@ Item { color: Qt.darker(root.foreground, 1.4) font.family: root.fontFamily font.pixelSize: Style.font.bodySmall - text: "Single cursor. Most reusable panel primitives expose hasCursor: bool and emit hovered(bool); a few (like PanelSlider) defer to their own focus handling. The panel root owns focusSection + selectedIndex; each element binds hasCursor: root.focusSection === 'X' && root.selectedIndex === N, and onHovered updates the same state. One highlight on screen, keyboard and mouse always agree. See plugins/bar/widgets/audioPanel.qml for the canonical recipe." + text: "Single cursor. Most reusable panel primitives expose hasCursor: bool and emit hovered(bool); composed rows (including sliders) wrap their content in CursorSurface. The panel root owns focusSection + selectedIndex; each element binds hasCursor: root.focusSection === 'X' && root.selectedIndex === N, and onHovered updates the same state. One highlight on screen, keyboard and mouse always agree. See plugins/bar/widgets/audioPanel.qml for the canonical recipe." } Text { width: parent.width @@ -437,7 +437,7 @@ Item { // ---- Typography -------------------------------------------------- Column { width: parent.width - spacing: 8 + spacing: Style.space(8) Text { text: "Typography" @@ -462,7 +462,7 @@ Item { Rectangle { width: parent.width - implicitHeight: typeCol.implicitHeight + 24 + implicitHeight: typeCol.implicitHeight + Style.spacing.rowPaddingX * 2 color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04) radius: Style.cornerRadius border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10) @@ -473,9 +473,9 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 14 - anchors.rightMargin: 14 - spacing: 10 + anchors.leftMargin: Style.space(14) + anchors.rightMargin: Style.space(14) + spacing: Style.space(10) Text { text: "Scale" @@ -511,8 +511,8 @@ Item { id: metaCol anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter - width: 140 - spacing: 1 + width: Style.space(140) + spacing: Style.space(1) Text { text: "Style.font." + modelData.key color: root.foreground @@ -532,7 +532,7 @@ Item { anchors.left: metaCol.right anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 16 + anchors.leftMargin: Style.space(16) text: modelData.sample color: root.foreground font.family: root.fontFamily @@ -558,8 +558,8 @@ Item { Grid { columns: 2 - columnSpacing: 16 - rowSpacing: 4 + columnSpacing: Style.space(16) + rowSpacing: Style.space(4) width: parent.width Text { @@ -626,6 +626,32 @@ Item { font.family: root.fontFamily font.pixelSize: Style.font.bodySmall } + + Text { + text: "Style.spacing.scale" + color: Qt.darker(root.foreground, 1.5) + font.family: root.fontFamily + font.pixelSize: Style.font.bodySmall + } + Text { + text: Style.spacing.scale.toFixed(2) + color: root.foreground + font.family: root.fontFamily + font.pixelSize: Style.font.bodySmall + } + + Text { + text: "Style.spacing.panelPadding" + color: Qt.darker(root.foreground, 1.5) + font.family: root.fontFamily + font.pixelSize: Style.font.bodySmall + } + Text { + text: Style.spacing.panelPadding + " px" + color: root.foreground + font.family: root.fontFamily + font.pixelSize: Style.font.bodySmall + } } } } @@ -636,7 +662,7 @@ Item { // ---- PanelSectionHeader ------------------------------------------ Column { width: parent.width - spacing: 8 + spacing: Style.space(8) Text { text: "PanelSectionHeader" @@ -654,7 +680,7 @@ Item { Rectangle { width: parent.width - implicitHeight: shCol.implicitHeight + 24 + implicitHeight: shCol.implicitHeight + Style.spacing.rowPaddingX * 2 color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04) radius: Style.cornerRadius border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10) @@ -665,9 +691,9 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 14 - anchors.rightMargin: 14 - spacing: 6 + anchors.leftMargin: Style.space(14) + anchors.rightMargin: Style.space(14) + spacing: Style.space(6) PanelSectionHeader { text: "DNS provider" @@ -692,7 +718,7 @@ Item { // ---- PanelSeparator ---------------------------------------------- Column { width: parent.width - spacing: 8 + spacing: Style.space(8) Text { text: "PanelSeparator" @@ -710,7 +736,7 @@ Item { Rectangle { width: parent.width - implicitHeight: sepCol.implicitHeight + 24 + implicitHeight: sepCol.implicitHeight + Style.spacing.rowPaddingX * 2 color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04) radius: Style.cornerRadius border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10) @@ -721,9 +747,9 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 14 - anchors.rightMargin: 14 - spacing: 12 + anchors.leftMargin: Style.space(14) + anchors.rightMargin: Style.space(14) + spacing: Style.space(12) PanelSeparator { foreground: root.foreground } PanelSeparator { foreground: root.foreground; strength: 0.25 } @@ -735,7 +761,7 @@ Item { // ---- CursorSurface ----------------------------------------------- Column { width: parent.width - spacing: 8 + spacing: Style.space(8) Text { text: "CursorSurface" @@ -755,7 +781,7 @@ Item { Rectangle { width: parent.width - implicitHeight: csCol.implicitHeight + 24 + implicitHeight: csCol.implicitHeight + Style.spacing.rowPaddingX * 2 color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04) radius: Style.cornerRadius border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10) @@ -766,9 +792,9 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 14 - anchors.rightMargin: 14 - spacing: 6 + anchors.leftMargin: Style.space(14) + anchors.rightMargin: Style.space(14) + spacing: Style.space(6) Repeater { model: [ @@ -781,11 +807,11 @@ Item { required property var modelData required property int index width: parent.width - implicitHeight: csLabel.implicitHeight + 16 + implicitHeight: csLabel.implicitHeight + Style.spacing.controlGap * 2 hasCursor: root.focusSection === "cursor-surface" && root.selectedIndex === index current: index === 1 foreground: root.foreground - fill: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.18) + fill: Style.hoverFillFor(root.foreground, root.accent) onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this) Text { @@ -793,8 +819,8 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 10 - anchors.rightMargin: 10 + anchors.leftMargin: Style.space(10) + anchors.rightMargin: Style.space(10) text: modelData.label color: root.foreground font.family: root.fontFamily @@ -820,7 +846,7 @@ Item { // ---- Button ------------------------------------------------------ Column { width: parent.width - spacing: 8 + spacing: Style.space(8) Text { text: "Button" @@ -830,7 +856,7 @@ Item { font.bold: true } Text { - text: "The kit's only button. State flags compose: hasCursor (panel cursor / hover) paints a tinted fill; active adds a persistent highlight; bordered draws a 1px idle ring for primary form buttons; focusable enables Tab focus with the accent ring. Click below or press h/l to walk the demo cursor." + text: "The kit's only button. State flags compose from shared tokens: hasCursor / hover paints the hover-cursor fill; active/selected add the selected fill; selected borders are off by default; bordered opts into normal/hover-cursor borders; focusable uses the same defaults as hover-cursor. Click below or press h/l to walk the demo cursor." color: Qt.darker(root.foreground, 1.5) font.family: root.fontFamily font.pixelSize: Style.font.caption @@ -840,7 +866,7 @@ Item { Rectangle { width: parent.width - implicitHeight: buttonRow.implicitHeight + 24 + implicitHeight: buttonRow.implicitHeight + Style.spacing.rowPaddingX * 2 color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04) radius: Style.cornerRadius border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10) @@ -850,15 +876,15 @@ Item { id: buttonRow anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 14 - spacing: 16 + anchors.leftMargin: Style.space(14) + spacing: Style.space(16) // Each demo Button is paired with a caption labeling the // state(s) it exercises so the section reads as one Button // showing its flag combinations side by side. Column { - spacing: 6 + spacing: Style.space(6) Button { anchors.horizontalCenter: parent.horizontalCenter text: "DHCP" @@ -879,7 +905,7 @@ Item { } Column { - spacing: 6 + spacing: Style.space(6) Button { anchors.horizontalCenter: parent.horizontalCenter text: "Cloudflare" @@ -901,13 +927,13 @@ Item { } Column { - spacing: 6 + spacing: Style.space(6) Button { anchors.horizontalCenter: parent.horizontalCenter iconText: "󰑐" tooltipText: "Refresh" - horizontalPadding: 8 - verticalPadding: 4 + horizontalPadding: Style.spacing.controlGap + verticalPadding: Style.spacing.labelGap hasCursor: root.focusSection === "button" && root.selectedIndex === 2 onHovered: function(h) { if (h) { root.focusSection = "button"; root.selectedIndex = 2 } @@ -924,7 +950,7 @@ Item { } Column { - spacing: 6 + spacing: Style.space(6) Button { anchors.horizontalCenter: parent.horizontalCenter iconText: "󰂯" @@ -947,7 +973,7 @@ Item { } Column { - spacing: 6 + spacing: Style.space(6) Button { anchors.horizontalCenter: parent.horizontalCenter text: "Apply" @@ -975,7 +1001,7 @@ Item { Column { id: buttonGroupSection width: parent.width - spacing: 8 + spacing: Style.space(8) readonly property bool focused: root.focusSection === "button-group" onFocusedChanged: if (focused) root.ensureCursorVisible(this) @@ -987,7 +1013,7 @@ Item { font.bold: true } Text { - text: "Mutually-exclusive row of Buttons. Selected option paints the accent fill+border so the chosen value stands out from non-selected options the cursor may pass through. Click to pick or press h/l + Enter." + text: "Mutually-exclusive row of Buttons. Each chip uses Button's bordered chrome; selected and cursor states come from the same shared Style tokens as every other control. Click to pick or press h/l + Enter." color: Qt.darker(root.foreground, 1.5) font.family: root.fontFamily font.pixelSize: Style.font.caption @@ -997,7 +1023,7 @@ Item { Rectangle { width: parent.width - implicitHeight: choiceRow.implicitHeight + 24 + implicitHeight: choiceRow.implicitHeight + Style.spacing.rowPaddingX * 2 color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04) radius: Style.cornerRadius border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10) @@ -1007,7 +1033,7 @@ Item { id: choiceRow anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 14 + anchors.leftMargin: Style.space(14) options: ["top", "right", "bottom", "left"] value: root.choiceDemoValue cursorIndex: root.focusSection === "button-group" ? root.selectedIndex : -1 @@ -1028,7 +1054,7 @@ Item { // ---- PanelActionButton ------------------------------------------- Column { width: parent.width - spacing: 8 + spacing: Style.space(8) Text { text: "PanelActionButton" @@ -1048,7 +1074,7 @@ Item { Rectangle { width: parent.width - implicitHeight: pabCol.implicitHeight + 24 + implicitHeight: pabCol.implicitHeight + Style.spacing.rowPaddingX * 2 color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04) radius: Style.cornerRadius border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10) @@ -1058,8 +1084,8 @@ Item { id: pabCol anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 14 - spacing: 18 + anchors.leftMargin: Style.space(14) + spacing: Style.space(18) PanelActionButton { iconText: "󰄬" @@ -1109,7 +1135,7 @@ Item { panelBackground: root.background fontFamily: root.fontFamily fontSize: Style.font.subtitle - size: 26 + size: Style.space(26) focusable: true hasCursor: root.focusSection === "panel-action-button" && root.selectedIndex === 3 onHovered: function(h) { @@ -1124,7 +1150,7 @@ Item { // ---- PanelToolTip ------------------------------------------------ Column { width: parent.width - spacing: 8 + spacing: Style.space(8) Text { text: "PanelToolTip" @@ -1144,16 +1170,16 @@ Item { Rectangle { id: tipSwatch - width: 140 - height: 36 + width: Style.space(140) + height: Style.space(36) readonly property bool focused: root.focusSection === "panel-tool-tip" color: tipMouse.containsMouse || focused - ? Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.18) - : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10) + ? Style.hoverFillFor(root.foreground, root.accent) + : Style.normalFillFor(root.foreground, root.accent) border.color: focused - ? Style.focusBorderColor - : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.35) - border.width: focused ? Style.focusBorderWidth : 1 + ? Style.hoverBorderFor(root.foreground, root.accent) + : Style.normalBorderFor(root.foreground, root.accent) + border.width: focused ? Style.hoverBorderWidth : Style.normalBorderWidth radius: Style.cornerRadius onFocusedChanged: if (focused) root.ensureCursorVisible(this) @@ -1190,7 +1216,7 @@ Item { // ---- Slider ------------------------------------------------------ Column { width: parent.width - spacing: 8 + spacing: Style.space(8) Text { text: "Slider" @@ -1211,7 +1237,7 @@ Item { CursorSurface { id: sliderWrapper width: parent.width - implicitHeight: sliderRow.implicitHeight + 24 + implicitHeight: sliderRow.implicitHeight + Style.spacing.rowPaddingX * 2 outline: true foreground: root.foreground hasCursor: root.focusSection === "slider" @@ -1229,9 +1255,9 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 14 - anchors.rightMargin: 14 - spacing: 10 + anchors.leftMargin: Style.space(14) + anchors.rightMargin: Style.space(14) + spacing: Style.space(10) property real demoVolume: 0.45 Text { @@ -1269,7 +1295,7 @@ Item { // ---- TextField ----------------------------------------------------- Column { width: parent.width - spacing: 8 + spacing: Style.space(8) Text { text: "TextField" @@ -1289,7 +1315,7 @@ Item { Rectangle { width: parent.width - implicitHeight: tfCol.implicitHeight + 24 + implicitHeight: tfCol.implicitHeight + Style.spacing.rowPaddingX * 2 color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04) radius: Style.cornerRadius border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10) @@ -1300,9 +1326,9 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 14 - anchors.rightMargin: 14 - spacing: 10 + anchors.leftMargin: Style.space(14) + anchors.rightMargin: Style.space(14) + spacing: Style.space(10) TextField { id: demoTextField @@ -1353,7 +1379,7 @@ Item { // ---- NumberField --------------------------------------------------- Column { width: parent.width - spacing: 8 + spacing: Style.space(8) Text { text: "NumberField" @@ -1373,7 +1399,7 @@ Item { Rectangle { width: parent.width - implicitHeight: numberDemo.implicitHeight + 24 + implicitHeight: numberDemo.implicitHeight + Style.spacing.rowPaddingX * 2 color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04) border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.12) border.width: 1 @@ -1383,7 +1409,7 @@ Item { id: numberDemo anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 12 + anchors.leftMargin: Style.space(12) label: "Auto-refresh interval (minutes)" from: 1 to: 1440 @@ -1404,7 +1430,7 @@ Item { // ---- Toggle -------------------------------------------------------- Column { width: parent.width - spacing: 8 + spacing: Style.space(8) Text { text: "Toggle" @@ -1414,7 +1440,7 @@ Item { font.bold: true } Text { - text: "Title + description + switch. Click anywhere on the row to flip; caller updates `checked` in response. Same focus tokens as Button." + text: "Title + description + switch. Click anywhere on the row to flip; caller updates `checked` in response. Uses the same normal / hover-cursor / focus tokens as Button and the checked switch track uses selected tokens." color: Qt.darker(root.foreground, 1.5) font.family: root.fontFamily font.pixelSize: Style.font.caption @@ -1465,7 +1491,7 @@ Item { // ---- Dropdown ----------------------------------------------------- Column { width: parent.width - spacing: 8 + spacing: Style.space(8) Text { text: "Dropdown" @@ -1485,7 +1511,7 @@ Item { Rectangle { width: parent.width - implicitHeight: ddCol.implicitHeight + 24 + implicitHeight: ddCol.implicitHeight + Style.spacing.rowPaddingX * 2 color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04) radius: Style.cornerRadius border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10) @@ -1496,13 +1522,13 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 14 - anchors.rightMargin: 14 - spacing: 6 + anchors.leftMargin: Style.space(14) + anchors.rightMargin: Style.space(14) + spacing: Style.space(6) Dropdown { id: demoDropdown - width: 260 + width: Style.spacing.dropdownWidth label: "Center anchor" fontFamily: root.fontFamily options: ["calendar", "weather", "clock", "battery"] @@ -1521,7 +1547,7 @@ Item { // ---- SearchableDropdown ------------------------------------------- Column { width: parent.width - spacing: 8 + spacing: Style.space(8) Text { text: "SearchableDropdown" @@ -1541,7 +1567,7 @@ Item { Rectangle { width: parent.width - implicitHeight: sddCol.implicitHeight + 24 + implicitHeight: sddCol.implicitHeight + Style.spacing.rowPaddingX * 2 color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04) radius: Style.cornerRadius border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10) @@ -1552,13 +1578,13 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 14 - anchors.rightMargin: 14 - spacing: 6 + anchors.leftMargin: Style.space(14) + anchors.rightMargin: Style.space(14) + spacing: Style.space(6) SearchableDropdown { id: demoSearchableDropdown - width: 280 + width: Style.spacing.searchableDropdownWidth label: "Add widget" fontFamily: root.fontFamily placeholderText: "Search widgets..." @@ -1594,7 +1620,7 @@ Item { // ---- Composed example ------------------------------------------- Column { width: parent.width - spacing: 8 + spacing: Style.space(8) Text { text: "Composed example" @@ -1614,7 +1640,7 @@ Item { Rectangle { width: parent.width - implicitHeight: composedCol.implicitHeight + 24 + implicitHeight: composedCol.implicitHeight + Style.spacing.rowPaddingX * 2 color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04) radius: Style.cornerRadius border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10) @@ -1625,9 +1651,9 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 14 - anchors.rightMargin: 14 - spacing: 6 + anchors.leftMargin: Style.space(14) + anchors.rightMargin: Style.space(14) + spacing: Style.space(6) PanelSectionHeader { text: "Wi-Fi networks" @@ -1637,10 +1663,10 @@ Item { CursorSurface { width: parent.width - implicitHeight: composedRow.implicitHeight + 12 + implicitHeight: composedRow.implicitHeight + Style.spacing.md * 2 current: true foreground: root.foreground - fill: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.18) + fill: Style.hoverFillFor(root.foreground, root.accent) hasCursor: root.focusSection === "composed" && root.selectedIndex === 0 onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this) @@ -1655,8 +1681,8 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 10 - anchors.rightMargin: 10 + anchors.leftMargin: Style.space(10) + anchors.rightMargin: Style.space(10) implicitHeight: 36 Text { @@ -1682,11 +1708,11 @@ Item { } Column { - spacing: 1 + spacing: Style.space(1) anchors.left: composedIcon.right - anchors.leftMargin: 10 + anchors.leftMargin: Style.space(10) anchors.right: composedForget.left - anchors.rightMargin: 8 + anchors.rightMargin: Style.space(8) anchors.verticalCenter: parent.verticalCenter Text { @@ -1713,9 +1739,9 @@ Item { CursorSurface { width: parent.width - implicitHeight: idleRow.implicitHeight + 12 + implicitHeight: idleRow.implicitHeight + Style.spacing.md * 2 foreground: root.foreground - fill: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.18) + fill: Style.hoverFillFor(root.foreground, root.accent) hasCursor: root.focusSection === "composed" && root.selectedIndex === 1 onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(this) @@ -1730,8 +1756,8 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 10 - anchors.rightMargin: 10 + anchors.leftMargin: Style.space(10) + anchors.rightMargin: Style.space(10) implicitHeight: 36 Text { @@ -1745,7 +1771,7 @@ Item { Text { anchors.left: parent.left - anchors.leftMargin: 24 + anchors.leftMargin: Style.space(24) anchors.verticalCenter: parent.verticalCenter text: "HughesATT" color: root.foreground @@ -1758,7 +1784,7 @@ Item { } } - Item { width: 1; height: 12 } + Item { width: 1; height: Style.spacing.rowPaddingX } } } } diff --git a/shell/plugins/emoji-picker/EmojiPicker.qml b/shell/plugins/emoji-picker/EmojiPicker.qml index 6a959679..4cd7b697 100644 --- a/shell/plugins/emoji-picker/EmojiPicker.qml +++ b/shell/plugins/emoji-picker/EmojiPicker.qml @@ -24,14 +24,14 @@ Item { property color border: foreground readonly property int cornerRadius: Style.cornerRadius property string fontFamily: Quickshell.env("OMARCHY_MENU_FONT") || "monospace" - property int contentMargin: 18 - property int headerHeight: 34 - property int contentSpacing: 6 - property int cardWidth: 400 - property int cardHeight: 500 + property int contentMargin: Style.spacing.panelPadding + property int headerHeight: Math.max(Style.space(34), Style.font.title + Style.spacing.controlPaddingY * 2) + property int contentSpacing: Style.spacing.md + property int cardWidth: Math.min(Style.space(400), panel.width - Style.gapsOut * 2) + property int cardHeight: Math.min(Style.space(500), panel.height - Style.gapsOut * 2) - property int cellWidth: 44 - property int cellHeight: 44 + property int cellWidth: Math.max(Style.space(44), Style.font.display + Style.spacing.md) + property int cellHeight: Math.max(Style.space(44), Style.font.display + Style.spacing.md) property int columns: Math.floor((cardWidth - contentMargin * 2) / cellWidth) function open(payloadJson) { @@ -183,7 +183,7 @@ Item { anchors.centerIn: parent color: root.background border.color: root.border - border.width: 2 + border.width: Math.max(1, Style.space(2)) MouseArea { anchors.fill: parent; onClicked: {} } @@ -274,7 +274,9 @@ Item { width: root.cellWidth height: root.cellHeight radius: root.cornerRadius - color: index === root.selectedIndex ? root.withAlpha(root.foreground, 0.08) : root.withAlpha(root.foreground, mouseArea.containsMouse ? 0.045 : 0) + color: index === root.selectedIndex ? Style.hoverFillFor(root.foreground, root.accent) : "transparent" + border.color: index === root.selectedIndex ? Style.hoverBorderFor(root.foreground, root.accent) : "transparent" + border.width: index === root.selectedIndex ? Style.hoverBorderWidth : 0 Text { text: parent.emoji @@ -290,6 +292,7 @@ Item { anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor + onContainsMouseChanged: if (containsMouse) root.selectedIndex = index onClicked: { root.selectedIndex = index root.activateIndex(index) @@ -300,7 +303,7 @@ Item { Column { anchors.centerIn: parent - spacing: 8 + spacing: Style.space(8) visible: displayModel.count === 0 Text { diff --git a/shell/plugins/image-picker/ImagePicker.qml b/shell/plugins/image-picker/ImagePicker.qml index 19c03ab5..50386423 100644 --- a/shell/plugins/image-picker/ImagePicker.qml +++ b/shell/plugins/image-picker/ImagePicker.qml @@ -473,7 +473,7 @@ Item { id: card visible: root.opened && root.imagesLoaded && root.layoutSettled && root.imageArray.length > 0 width: Math.min(parent.width - 80, root.expandedWidth + 13 * (root.sliceWidth + root.sliceSpacing) + 40) - height: root.expandedHeight + 30 + root.bottomChromeHeight + height: root.expandedHeight + Style.space(30) + root.bottomChromeHeight anchors.centerIn: parent MouseArea { anchors.fill: parent; onClicked: {} } @@ -481,7 +481,7 @@ Item { Item { id: carousel anchors.top: parent.top - anchors.topMargin: 30 + anchors.topMargin: Style.space(30) anchors.bottom: parent.bottom anchors.bottomMargin: root.bottomChromeHeight anchors.horizontalCenter: parent.horizontalCenter @@ -635,7 +635,7 @@ Item { id: selectedLabel visible: root.showLabels anchors.top: carousel.bottom - anchors.topMargin: 16 + anchors.topMargin: Style.space(16) anchors.horizontalCenter: carousel.horizontalCenter width: root.expandedWidth text: root.currentLabel() @@ -651,7 +651,7 @@ Item { Text { visible: root.filterable && root.filterText anchors.top: selectedLabel.bottom - anchors.topMargin: 8 + anchors.topMargin: Style.space(8) anchors.horizontalCenter: carousel.horizontalCenter width: root.expandedWidth text: root.filterText diff --git a/shell/plugins/menu/Menu.qml b/shell/plugins/menu/Menu.qml index ffcb9cef..d89445fc 100644 --- a/shell/plugins/menu/Menu.qml +++ b/shell/plugins/menu/Menu.qml @@ -57,18 +57,18 @@ Item { property color foreground: Color.menu.text property color border: foreground readonly property int cornerRadius: Style.cornerRadius - property int contentMargin: 18 - property int headerHeight: 34 - property int contentSpacing: 6 - property int baseRowHeight: 50 - property int detailRowHeight: 58 - property int rowSpacing: 3 - property int dividerHeight: 17 + property int contentMargin: Style.spacing.panelPadding + property int headerHeight: Math.max(Style.space(34), Style.font.title + Style.spacing.controlPaddingY * 2) + property int contentSpacing: Style.spacing.md + property int baseRowHeight: Math.max(Style.space(50), Style.font.body + Style.spacing.rowPaddingX * 2) + property int detailRowHeight: Math.max(Style.space(58), Style.font.body + Style.font.caption + Style.spacing.rowPaddingX * 2) + property int rowSpacing: Style.spacing.xs + property int dividerHeight: Style.space(17) property bool searchDivider: false property int layoutSerial: 0 - property int cardWidth: Math.min((root.activeMenu === "trigger.capture.screenrecord" || root.activeMenu === "style.font") ? 520 : 300, panel.width - 48) + property int cardWidth: Math.min((root.activeMenu === "trigger.capture.screenrecord" || root.activeMenu === "style.font") ? Style.space(520) : Style.space(300), panel.width - Style.gapsOut * 2) property int visibleRowsHeight: rowListHeight(layoutSerial, displayModel.count, filterText, searchDivider) - property int cardHeight: Math.min(Math.max(220, contentMargin * 2 + headerHeight + contentSpacing + visibleRowsHeight), panel.height - 48) + property int cardHeight: Math.min(Math.max(Style.space(220), contentMargin * 2 + headerHeight + contentSpacing + visibleRowsHeight), panel.height - Style.gapsOut * 2) function shellQuote(value) { return "'" + String(value).replace(/'/g, "'\\''") + "'" @@ -807,7 +807,7 @@ Item { anchors.centerIn: parent color: root.background border.color: root.border - border.width: 2 + border.width: Math.max(1, Style.space(2)) MouseArea { anchors.fill: parent; onClicked: {} } @@ -900,11 +900,11 @@ Item { Rectangle { anchors.left: parent.left - anchors.leftMargin: 4 + anchors.leftMargin: Style.space(4) anchors.right: parent.right - anchors.rightMargin: 4 + anchors.rightMargin: Style.space(4) anchors.verticalCenter: parent.verticalCenter - height: 1 + height: Style.spacing.hairline color: root.withAlpha(root.foreground, 0.2) } } @@ -925,50 +925,50 @@ Item { width: ListView.view.width height: root.rowHeightForDetail(row.detail) radius: root.cornerRadius - color: index === root.selectedIndex ? root.withAlpha(root.foreground, 0.08) : root.withAlpha(root.foreground, mouseArea.containsMouse ? 0.045 : 0) - border.color: "transparent" - border.width: 0 + color: index === root.selectedIndex ? Style.hoverFillFor(root.foreground, root.accent) : "transparent" + border.color: index === root.selectedIndex ? Style.hoverBorderFor(root.foreground, root.accent) : "transparent" + border.width: index === root.selectedIndex ? Style.hoverBorderWidth : 0 Rectangle { visible: false - width: 4 - height: parent.height - 18 - radius: Math.min(root.cornerRadius, 4) + width: Style.space(4) + height: parent.height - Style.space(18) + radius: Math.min(root.cornerRadius, Style.space(4)) color: root.accent anchors.left: parent.left - anchors.leftMargin: 8 + anchors.leftMargin: Style.space(8) anchors.verticalCenter: parent.verticalCenter } Text { id: iconText text: row.icon - color: index === root.selectedIndex ? root.accent : root.foreground + color: index === root.selectedIndex ? Style.hoverStateColor(root.foreground, root.accent) : root.foreground opacity: row.kind === "back" ? 0.7 : 1 font.family: root.fontFamily font.pixelSize: Style.font.iconLarge - width: 36 + width: Style.space(36) horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter anchors.left: parent.left - anchors.leftMargin: 8 + anchors.leftMargin: Style.space(8) y: contentColumn.y + labelText.y + (labelText.height - height) / 2 } Column { id: contentColumn anchors.left: iconText.right - anchors.leftMargin: 6 + anchors.leftMargin: Style.space(6) anchors.right: trail.left - anchors.rightMargin: 6 + anchors.rightMargin: Style.space(6) anchors.verticalCenter: parent.verticalCenter - spacing: 3 + spacing: Style.space(3) Text { id: labelText width: parent.width text: row.label - color: index === root.selectedIndex ? root.accent : root.foreground + color: index === root.selectedIndex ? Style.hoverStateColor(root.foreground, root.accent) : root.foreground font.family: root.fontFamily font.pixelSize: Style.font.heading font.weight: Font.Medium @@ -989,9 +989,9 @@ Item { Row { id: trail - width: 14 + width: Style.space(14) anchors.right: parent.right - anchors.rightMargin: 8 + anchors.rightMargin: Style.space(8) y: contentColumn.y + labelText.y + (labelText.height - height) / 2 spacing: 0 @@ -1007,7 +1007,7 @@ Item { Text { text: row.kind === "menu" || row.kind === "link" ? "›" : "" - color: index === root.selectedIndex ? root.accent : root.foreground + color: index === root.selectedIndex ? Style.hoverStateColor(root.foreground, root.accent) : root.foreground opacity: row.kind === "menu" || row.kind === "link" ? 0.36 : 0 font.family: root.fontFamily font.pixelSize: Style.font.heading @@ -1021,6 +1021,7 @@ Item { anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor + onContainsMouseChanged: if (containsMouse) root.selectedIndex = row.index onClicked: root.activateIndex(row.index) } } @@ -1028,7 +1029,7 @@ Item { Column { anchors.centerIn: parent - spacing: 8 + spacing: Style.space(8) visible: displayModel.count === 0 Text { @@ -1038,7 +1039,7 @@ Item { font.family: root.fontFamily font.pixelSize: Style.font.displayLarge horizontalAlignment: Text.AlignHCenter - width: 320 + width: Style.space(320) } Text { @@ -1048,7 +1049,7 @@ Item { font.family: root.fontFamily font.pixelSize: Style.font.title horizontalAlignment: Text.AlignHCenter - width: 320 + width: Style.space(320) } } } diff --git a/shell/plugins/notifications/Service.qml b/shell/plugins/notifications/Service.qml index f38ff9d8..cc2d2baa 100644 --- a/shell/plugins/notifications/Service.qml +++ b/shell/plugins/notifications/Service.qml @@ -42,9 +42,9 @@ Item { // shell.bar isn't reachable so the popup never lands on top of the bar. readonly property string barPosition: shell && shell.barConfig ? String(shell.barConfig.position || "top") : "top" readonly property bool barVertical: barPosition === "left" || barPosition === "right" - readonly property int defaultBarSize: barVertical ? 28 : 26 + readonly property int defaultBarSize: barVertical ? Style.bar.sizeVertical : Style.bar.sizeHorizontal readonly property int liveBarSize: shell && shell.bar && !shell.bar.barHidden ? Math.max(0, shell.bar.barSize) : defaultBarSize - readonly property int barClearance: liveBarSize + 12 + readonly property int barClearance: liveBarSize + Style.gapsOut // Fired by IPC (`omarchy-shell notifications showHistory`) so the // bar widget can drop its PopupCard from the same anchor a click would. @@ -806,10 +806,10 @@ Item { right: service.barPosition !== "left" } margins { - top: service.barPosition === "top" ? service.barClearance + 12 : 20 - bottom: service.barPosition === "bottom" ? service.barClearance + 12 : 20 - left: service.barPosition === "left" ? service.barClearance + 12 : 20 - right: service.barPosition === "right" ? service.barClearance + 12 : 20 + top: service.barPosition === "top" ? service.barClearance : Style.gapsOut + bottom: service.barPosition === "bottom" ? service.barClearance : Style.gapsOut + left: service.barPosition === "left" ? service.barClearance : Style.gapsOut + right: service.barPosition === "right" ? service.barClearance : Style.gapsOut } implicitWidth: popupColumn.implicitWidth @@ -819,7 +819,7 @@ Item { id: popupColumn anchors.right: parent.right anchors.top: parent.top - spacing: 8 + spacing: Style.space(8) Repeater { model: popupModel diff --git a/shell/plugins/notifications/components/NotificationCard.qml b/shell/plugins/notifications/components/NotificationCard.qml index b1d9ac82..381c2c3d 100644 --- a/shell/plugins/notifications/components/NotificationCard.qml +++ b/shell/plugins/notifications/components/NotificationCard.qml @@ -90,7 +90,7 @@ Rectangle { .replace(/^\s*(?:https?:\/\/|www\.)?(?:[a-z0-9-]+\.)+[a-z]{2,}(?::\d+)?(?:\/\S*)?\s+/i, "") } - implicitWidth: 380 + implicitWidth: Style.space(380) // Add 2 * border.width so mainColumn (inset by border.width on top/left/right) // doesn't push content under the bottom edge. The bottom edge is also inset // for symmetry except when the progress bar replaces it. @@ -98,7 +98,7 @@ Rectangle { radius: cornerRadius color: Color.notifications.background border.color: urgency === 2 ? Color.urgent : Color.notifications.border - border.width: 2 + border.width: Math.max(1, Style.space(2)) clip: true HoverHandler { id: hoverTracker } @@ -126,7 +126,7 @@ Rectangle { // the preview looks like a clean banner without dark letterboxing. Item { Layout.fillWidth: true - Layout.preferredHeight: 140 + Layout.preferredHeight: Style.space(140) visible: root.mediaMode clip: true @@ -164,16 +164,16 @@ Rectangle { // the summary/body ("Screenshot saved" etc) under the hero image. RowLayout { Layout.fillWidth: true - Layout.leftMargin: 12 - Layout.rightMargin: 12 - Layout.topMargin: 10 - Layout.bottomMargin: 10 - spacing: 12 + Layout.leftMargin: Style.space(12) + Layout.rightMargin: Style.space(12) + Layout.topMargin: Style.space(10) + Layout.bottomMargin: Style.space(10) + spacing: Style.space(12) Item { id: smallIconSlot - Layout.preferredWidth: 40 - Layout.preferredHeight: 40 + Layout.preferredWidth: Style.space(40) + Layout.preferredHeight: Style.space(40) Layout.alignment: Qt.AlignVCenter // Hide the slot when the icon failed to resolve (themed-icon name // not in the user's icon theme) AND we don't have a glyph fallback @@ -184,8 +184,8 @@ Rectangle { id: smallIconImage anchors.fill: parent source: root.smallIconSource - sourceSize.width: 40 * Screen.devicePixelRatio - sourceSize.height: 40 * Screen.devicePixelRatio + sourceSize.width: smallIconSlot.width * Screen.devicePixelRatio + sourceSize.height: smallIconSlot.height * Screen.devicePixelRatio fillMode: Image.PreserveAspectFit asynchronous: true smooth: true @@ -207,7 +207,7 @@ Rectangle { ColumnLayout { Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter - spacing: 2 + spacing: Style.space(2) Text { Layout.fillWidth: true @@ -224,7 +224,7 @@ Rectangle { Text { Layout.fillWidth: true - Layout.topMargin: 2 + Layout.topMargin: Style.space(2) visible: root.sanitizedBody.length > 0 text: root.sanitizedBody textFormat: Text.StyledText @@ -246,7 +246,7 @@ Rectangle { anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom - height: 3 + height: Math.max(1, Style.space(3)) color: Color.notifications.border visible: false diff --git a/shell/plugins/osd/Osd.qml b/shell/plugins/osd/Osd.qml index 639df146..7371fab8 100644 --- a/shell/plugins/osd/Osd.qml +++ b/shell/plugins/osd/Osd.qml @@ -88,24 +88,24 @@ Item { Rectangle { id: card - width: 269 - height: 68 + width: Style.space(269) + height: Math.max(Style.space(68), Style.font.displayLarge + Style.spacing.panelGap) anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom - anchors.bottomMargin: 67 + anchors.bottomMargin: Style.space(67) color: Color.alpha(Color.background, 0.97) border.color: Color.foreground - border.width: 2 + border.width: Math.max(1, Style.space(2)) radius: Style.cornerRadius opacity: root.opened ? 1 : 0 Row { anchors.fill: parent - anchors.leftMargin: 16 - anchors.rightMargin: 16 - spacing: 16 + anchors.leftMargin: Style.space(16) + anchors.rightMargin: Style.space(16) + spacing: Style.space(16) Text { - width: 28 + width: Style.space(28) anchors.verticalCenter: parent.verticalCenter horizontalAlignment: Text.AlignHCenter text: root.icon @@ -115,8 +115,8 @@ Item { } Rectangle { visible: root.hasProgress - width: visible ? 142 : 0 - height: 6 + width: visible ? Style.space(142) : 0 + height: Math.max(Style.space(6), Style.spacing.sm) anchors.verticalCenter: parent.verticalCenter color: Color.alpha(Color.foreground, 0.45) Rectangle { @@ -126,7 +126,7 @@ Item { } } Text { - width: root.hasProgress ? 41 : 190 + width: root.hasProgress ? Style.space(41) : Style.space(190) anchors.verticalCenter: parent.verticalCenter text: root.message font.family: Style.font.family diff --git a/shell/plugins/polkit/PolkitAgent.qml b/shell/plugins/polkit/PolkitAgent.qml index fc89891b..e6b3b916 100644 --- a/shell/plugins/polkit/PolkitAgent.qml +++ b/shell/plugins/polkit/PolkitAgent.qml @@ -18,9 +18,9 @@ Item { property color foreground: Color.menu.text property color border: foreground readonly property int cornerRadius: Style.cornerRadius - property int contentMargin: 18 - property int contentSpacing: 12 - property int fieldHeight: 42 + property int contentMargin: Style.spacing.panelPadding + property int contentSpacing: Style.spacing.rowPaddingX + property int fieldHeight: Math.max(Style.space(42), Style.spacing.controlHeight) property bool closing: false property bool submitted: false @@ -36,8 +36,8 @@ Item { readonly property bool dialogVisible: polkitAgent.isActive || closing readonly property bool fingerprintWaiting: dialogVisible && !responseRequired && !submitted && (fingerprintFirst || promptLooksFingerprint(currentPrompt + " " + currentSupplementary)) - readonly property int cardWidth: Math.min(312, Math.max(260, panel.width - 48)) - readonly property int cardHeight: panel.height > 0 ? Math.min(fieldHeight + contentMargin * 2, panel.height - 48) : fieldHeight + contentMargin * 2 + readonly property int cardWidth: Math.min(Style.space(312), Math.max(Style.space(260), panel.width - Style.gapsOut * 2)) + readonly property int cardHeight: panel.height > 0 ? Math.min(fieldHeight + contentMargin * 2, panel.height - Style.gapsOut * 2) : fieldHeight + contentMargin * 2 function withAlpha(color, alpha) { return Qt.rgba(color.r, color.g, color.b, alpha) @@ -238,7 +238,7 @@ Item { anchors.horizontalCenterOffset: root.shakeOffset color: root.background border.color: root.accent - border.width: 2 + border.width: Math.max(1, Style.space(2)) MouseArea { anchors.fill: parent; onClicked: root.refocus() } @@ -263,21 +263,21 @@ Item { id: cardRow anchors.fill: parent anchors.margins: root.contentMargin - spacing: 14 + spacing: Style.space(14) Text { text: "\uf023" color: root.errorFlash ? Color.urgent : root.accent font.family: root.fontFamily font.pixelSize: Style.font.iconLarge - width: 26 + width: Style.space(26) height: root.fieldHeight horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } Item { - width: parent.width - 40 + width: parent.width - Style.space(40) height: root.fieldHeight TextInput { @@ -320,8 +320,8 @@ Item { } Rectangle { - width: 2 - height: 24 + width: Math.max(1, Style.space(2)) + height: Style.space(24) anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter color: root.errorFlash ? Color.urgent : root.foreground diff --git a/shell/plugins/settings/SettingsPanel.qml b/shell/plugins/settings/SettingsPanel.qml index c4dc388b..68b0e765 100644 --- a/shell/plugins/settings/SettingsPanel.qml +++ b/shell/plugins/settings/SettingsPanel.qml @@ -122,7 +122,7 @@ Item { function ensureBodyItemVisible(item) { if (!item || typeof bodyScroll === "undefined" || !bodyScroll || !bodyScroll.contentItem) return var pos = item.mapToItem(bodyScroll.contentItem, 0, 0) - var pad = 24 + var pad = Style.space(24) var top = pos.y - pad var bottom = pos.y + item.height + pad if (top < bodyScroll.contentY) { @@ -161,6 +161,27 @@ Item { property int draftRevision: 0 property bool suppressReload: false + // When a widget action moves an entry, the Repeater rebuilds / reindexes + // cards. Remember the action group position so focus follows the moved + // widget instead of falling back to the first action on the old/new row. + property string pendingActionFocusSection: "" + property int pendingActionFocusIndex: -1 + property int pendingActionFocusAction: 0 + property int pendingActionFocusRevision: 0 + + function scheduleActionFocus(section, index, action) { + pendingActionFocusSection = section + pendingActionFocusIndex = index + pendingActionFocusAction = action + pendingActionFocusRevision++ + } + + function clearPendingActionFocus() { + pendingActionFocusSection = "" + pendingActionFocusIndex = -1 + pendingActionFocusAction = 0 + } + // ---------------- draft helpers ------------------------------------------ function cloneJson(value) { return JSON.parse(JSON.stringify(value || null)) } function isPlainObject(value) { return value !== null && typeof value === "object" && !Array.isArray(value) } @@ -283,7 +304,7 @@ Item { markDirty() } - function moveEntry(section, fromIndex, toIndex) { + function moveEntry(section, fromIndex, toIndex, focusActionIndex) { var arr = sectionArray(section) if (toIndex < 0 || toIndex >= arr.length) return mutateSection(section, function(a) { @@ -291,6 +312,7 @@ Item { a.splice(fromIndex, 1) a.splice(toIndex, 0, item) }) + if (focusActionIndex !== undefined) scheduleActionFocus(section, toIndex, focusActionIndex) } function removeEntry(section, index) { @@ -498,9 +520,9 @@ Item { id: window title: "Omarchy Bar Settings" color: root.background - implicitWidth: 760 - implicitHeight: 620 - minimumSize: Qt.size(620, 480) + implicitWidth: Style.space(760) + implicitHeight: Style.space(620) + minimumSize: Qt.size(Style.space(620), Style.space(480)) onVisibleChanged: { if (!visible && !root.closingFromHost && root.shell && typeof root.shell.hide === "function") @@ -541,71 +563,71 @@ Item { } } - Rectangle { - anchors.fill: parent - color: root.background - // No explicit border — the Hyprland window decoration already draws one. - - ColumnLayout { + Rectangle { anchors.fill: parent - spacing: 0 + color: root.background + // No explicit border — the Hyprland window decoration already draws one. - // Header - Item { - Layout.fillWidth: true - Layout.preferredHeight: 48 + ColumnLayout { + anchors.fill: parent + spacing: 0 - Text { - text: "Omarchy Bar Settings" - color: root.foreground - font.family: root.fontFamily - font.pixelSize: Style.font.heading - font.bold: true - anchors.left: parent.left - anchors.leftMargin: 18 - anchors.verticalCenter: parent.verticalCenter + // Header + Item { + Layout.fillWidth: true + Layout.preferredHeight: Math.max(Style.space(48), Style.font.heading + Style.spacing.controlPaddingY * 2) + + Text { + text: "Omarchy Bar Settings" + color: root.foreground + font.family: root.fontFamily + font.pixelSize: Style.font.heading + font.bold: true + anchors.left: parent.left + anchors.leftMargin: Style.spacing.panelPadding + anchors.verticalCenter: parent.verticalCenter + } + + Text { + text: "~/.config/omarchy/shell.json" + color: Qt.darker(root.foreground, 1.8) + font.family: root.fontFamily + font.pixelSize: Style.font.caption + anchors.right: parent.right + anchors.rightMargin: Style.spacing.panelPadding + anchors.verticalCenter: parent.verticalCenter + } } - Text { - text: "~/.config/omarchy/shell.json" - color: Qt.darker(root.foreground, 1.8) - font.family: root.fontFamily - font.pixelSize: Style.font.caption - anchors.right: parent.right - anchors.rightMargin: 18 - anchors.verticalCenter: parent.verticalCenter + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: Style.spacing.hairline + color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.18) } - } - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 1 - color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.18) - } + // Content + Flickable { + id: bodyScroll + Layout.fillWidth: true + Layout.fillHeight: true + Layout.margins: Style.spacing.panelPadding + clip: true + contentWidth: width + contentHeight: contentColumn.implicitHeight + boundsBehavior: Flickable.StopAtBounds + flickableDirection: Flickable.VerticalFlick - // Content - Flickable { - id: bodyScroll - Layout.fillWidth: true - Layout.fillHeight: true - Layout.margins: 18 - clip: true - contentWidth: width - contentHeight: contentColumn.implicitHeight - boundsBehavior: Flickable.StopAtBounds - flickableDirection: Flickable.VerticalFlick + ColumnLayout { + id: contentColumn + width: bodyScroll.width + spacing: Style.spacing.panelGap - ColumnLayout { - id: contentColumn - width: bodyScroll.width - spacing: 14 - - BarCategory { Layout.fillWidth: true } + BarCategory { Layout.fillWidth: true } + } } } } } - } // ---------- per-widget settings overlay ----------------------------------- Rectangle { @@ -633,19 +655,19 @@ Item { Rectangle { anchors.centerIn: parent - width: 420 - height: Math.min(parent.height - 60, 380) + width: Math.min(Style.space(420), parent.width - Style.gapsOut * 2) + height: Math.min(parent.height - Style.space(60), Style.space(380)) color: root.background radius: Style.cornerRadius - border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.2) - border.width: 1 + border.color: Style.normalBorderFor(root.foreground, root.accent) + border.width: Style.normalBorderWidth MouseArea { anchors.fill: parent } ColumnLayout { anchors.fill: parent - anchors.margins: 18 - spacing: 12 + anchors.margins: Style.spacing.panelPadding + spacing: Style.spacing.rowPaddingX Text { text: root.widgetName(root.widgetDialogEntry.id || "") @@ -679,7 +701,7 @@ Item { Row { Layout.alignment: Qt.AlignRight - spacing: 8 + spacing: Style.spacing.rowGap Button { text: "Cancel" foreground: root.foreground @@ -703,7 +725,7 @@ Item { // ===================== bar category ====================================== component BarCategory: ColumnLayout { - spacing: 14 + spacing: Style.spacing.panelGap Text { text: "Bar" @@ -724,10 +746,10 @@ Item { Row { Layout.fillWidth: true - spacing: 14 + spacing: Style.spacing.panelGap Column { - spacing: 4 + spacing: Style.spacing.labelGap Text { text: "Position" @@ -799,7 +821,7 @@ Item { Rectangle { Layout.fillWidth: true - Layout.preferredHeight: 1 + Layout.preferredHeight: Style.spacing.hairline color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.12) } @@ -824,8 +846,8 @@ Item { property string sectionLabel: "" property var entries: root.sectionArray(section.sectionKey) Layout.fillWidth: true - Layout.topMargin: 8 - spacing: 8 + Layout.topMargin: Style.spacing.rowGap + spacing: Style.spacing.rowGap Connections { target: root @@ -834,7 +856,7 @@ Item { RowLayout { width: section.width - spacing: 8 + spacing: Style.spacing.rowGap Text { text: section.sectionLabel @@ -862,7 +884,7 @@ Item { value: "" placeholderText: "Search widgets..." emptyText: "No widgets to add" - Layout.preferredWidth: 220 + Layout.preferredWidth: Style.spacing.searchableDropdownWidth Layout.alignment: Qt.AlignVCenter options: { var list = root.availableToAdd(section.sectionKey) @@ -890,7 +912,7 @@ Item { Column { Layout.fillWidth: true width: section.width - spacing: 4 + spacing: Style.spacing.labelGap Repeater { model: section.entries @@ -907,11 +929,11 @@ Item { Rectangle { visible: section.entries.length === 0 width: parent.width - height: 32 + height: Math.max(Style.space(32), Style.font.bodySmall + Style.spacing.controlPaddingY * 2) radius: root.cornerRadius - color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04) - border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.12) - border.width: 1 + color: Style.normalFillFor(root.foreground, root.accent) + border.color: Style.normalBorderFor(root.foreground, root.accent) + border.width: Style.normalBorderWidth Text { anchors.centerIn: parent @@ -934,56 +956,154 @@ Item { readonly property string description: root.widgetDescription(entryId) readonly property bool hasSettings: root.widgetHasSettings(entryId) - implicitHeight: 50 + implicitHeight: Style.space(50) radius: root.cornerRadius - color: cardArea.containsMouse ? Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.08) : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.03) - border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.12) - border.width: 1 + color: cardArea.containsMouse || actionRow.activeFocus + ? Style.hoverFillFor(root.foreground, root.accent) + : Style.normalFillFor(root.foreground, root.accent) + border.color: cardArea.containsMouse || actionRow.activeFocus + ? Style.hoverBorderFor(root.foreground, root.accent) + : Style.normalBorderFor(root.foreground, root.accent) + border.width: cardArea.containsMouse || actionRow.activeFocus ? Style.hoverBorderWidth : Style.normalBorderWidth Behavior on color { ColorAnimation { duration: 100 } } + function maybeRestoreActionFocus() { + if (root.pendingActionFocusSection !== card.sectionKey) return + if (root.pendingActionFocusIndex !== card.entryIndex) return + + actionRow.actionIndex = root.pendingActionFocusAction + actionRow.clampActionIndex() + root.clearPendingActionFocus() + + Qt.callLater(function() { + actionRow.forceActiveFocus() + root.ensureBodyItemVisible(card) + }) + } + + onEntryIndexChanged: maybeRestoreActionFocus() + Component.onCompleted: maybeRestoreActionFocus() + + Connections { + target: root + function onPendingActionFocusRevisionChanged() { card.maybeRestoreActionFocus() } + } + Row { id: actionRow anchors.right: parent.right - anchors.rightMargin: 8 + anchors.rightMargin: Style.spacing.controlGap anchors.verticalCenter: parent.verticalCenter - spacing: 4 + spacing: Style.spacing.labelGap + activeFocusOnTab: true + + property int actionIndex: 0 + + onActiveFocusChanged: if (activeFocus) { + clampActionIndex() + root.ensureBodyItemVisible(card) + } + + function actionVisible(index) { + switch (index) { + case 0: return moveUpButton.visible && moveUpButton.enabled + case 1: return moveDownButton.visible && moveDownButton.enabled + case 2: return settingsButton.visible && settingsButton.enabled + case 3: return removeButton.visible && removeButton.enabled + } + return false + } + + function firstActionIndex() { + for (var i = 0; i < 4; i++) if (actionVisible(i)) return i + return 0 + } + + function clampActionIndex() { + if (actionVisible(actionIndex)) return + actionIndex = firstActionIndex() + } + + function moveAction(delta) { + clampActionIndex() + var next = actionIndex + while (true) { + next += delta + if (next < 0 || next > 3) return + if (actionVisible(next)) { actionIndex = next; return } + } + } + + function activateAction() { + clampActionIndex() + switch (actionIndex) { + case 0: root.moveEntry(card.sectionKey, card.entryIndex, card.entryIndex - 1, actionIndex); return + case 1: root.moveEntry(card.sectionKey, card.entryIndex, card.entryIndex + 1, actionIndex); return + case 2: root.openWidgetSettings(card.sectionKey, card.entryIndex, card.entry); return + case 3: root.removeEntry(card.sectionKey, card.entryIndex); return + } + } + + Keys.priority: Keys.BeforeItem + Keys.onPressed: function(event) { + if (event.key === Qt.Key_Left || event.text === "h") { + moveAction(-1); event.accepted = true; return + } + if (event.key === Qt.Key_Right || event.text === "l") { + moveAction(1); event.accepted = true; return + } + if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter || event.key === Qt.Key_Space) { + activateAction(); event.accepted = true; return + } + } PanelActionButton { + id: moveUpButton iconText: "󰁝" tooltipText: "Move up" foreground: root.foreground panelBackground: root.background fontFamily: root.fontFamily fontSize: Style.font.subtitle - size: 26 - focusable: true - onClicked: root.moveEntry(card.sectionKey, card.entryIndex, card.entryIndex - 1) + size: Style.space(26) + hasCursor: actionRow.activeFocus && actionRow.actionIndex === 0 + bordered: hasCursor + onHovered: function(h) { if (h) actionRow.actionIndex = 0 } + onClicked: root.moveEntry(card.sectionKey, card.entryIndex, card.entryIndex - 1, 0) } PanelActionButton { + id: moveDownButton iconText: "󰁅" tooltipText: "Move down" foreground: root.foreground panelBackground: root.background fontFamily: root.fontFamily fontSize: Style.font.subtitle - size: 26 - focusable: true - onClicked: root.moveEntry(card.sectionKey, card.entryIndex, card.entryIndex + 1) + size: Style.space(26) + hasCursor: actionRow.activeFocus && actionRow.actionIndex === 1 + bordered: hasCursor + onHovered: function(h) { if (h) actionRow.actionIndex = 1 } + onClicked: root.moveEntry(card.sectionKey, card.entryIndex, card.entryIndex + 1, 1) } PanelActionButton { + id: settingsButton iconText: "󰒓" tooltipText: "Settings" foreground: root.foreground panelBackground: root.background fontFamily: root.fontFamily fontSize: Style.font.subtitle - size: 26 - focusable: true + size: Style.space(26) visible: card.hasSettings + hasCursor: actionRow.activeFocus && actionRow.actionIndex === 2 + bordered: hasCursor + onVisibleChanged: if (!visible && actionRow.actionIndex === 2) actionRow.clampActionIndex() + onHovered: function(h) { if (h) actionRow.actionIndex = 2 } onClicked: root.openWidgetSettings(card.sectionKey, card.entryIndex, card.entry) } PanelActionButton { + id: removeButton iconText: "󰅖" tooltipText: "Remove" foreground: root.urgent @@ -991,8 +1111,10 @@ Item { panelBackground: root.background fontFamily: root.fontFamily fontSize: Style.font.subtitle - size: 26 - focusable: true + size: Style.space(26) + hasCursor: actionRow.activeFocus && actionRow.actionIndex === 3 + bordered: hasCursor + onHovered: function(h) { if (h) actionRow.actionIndex = 3 } onClicked: root.removeEntry(card.sectionKey, card.entryIndex) } } @@ -1000,10 +1122,10 @@ Item { Column { anchors.left: parent.left anchors.right: actionRow.left - anchors.leftMargin: 12 - anchors.rightMargin: 12 + anchors.leftMargin: Style.spacing.rowPaddingX + anchors.rightMargin: Style.spacing.rowPaddingX anchors.verticalCenter: parent.verticalCenter - spacing: 2 + spacing: Style.spacing.xxs Text { text: card.displayName @@ -1064,7 +1186,7 @@ Item { signal fieldChanged(string key, var value) property var entry: ({}) - spacing: 8 + spacing: Style.spacing.rowGap width: parent ? parent.width : 0 NumberField { @@ -1088,7 +1210,7 @@ Item { signal fieldChanged(string key, var value) property var entry: ({}) - spacing: 8 + spacing: Style.spacing.rowGap width: parent ? parent.width : 0 component CalendarField: TextField { @@ -1144,7 +1266,7 @@ Item { signal fieldChanged(string key, var value) property var entry: ({}) - spacing: 8 + spacing: Style.spacing.rowGap width: parent ? parent.width : 0 NumberField { diff --git a/shell/plugins/settings/components/NDropdown.qml b/shell/plugins/settings/components/NDropdown.qml index 6d8c3f0a..aebb837d 100644 --- a/shell/plugins/settings/components/NDropdown.qml +++ b/shell/plugins/settings/components/NDropdown.qml @@ -16,18 +16,18 @@ Item { property color accent: Color.accent property string fontFamily: Style.font.family property int cornerRadius: 0 - property int rowHeight: 28 - property int popupRowHeight: 28 + property int rowHeight: Style.spacing.controlHeight + property int popupRowHeight: Style.spacing.popupRowHeight property bool showLabel: true signal changed(string value) - implicitWidth: 240 - implicitHeight: showLabel ? rowHeight + 18 : rowHeight + implicitWidth: Style.spacing.dropdownWidth + implicitHeight: showLabel ? rowHeight + Style.spacing.huge : rowHeight Column { anchors.fill: parent - spacing: 4 + spacing: Style.spacing.labelGap Text { visible: root.showLabel && root.label !== "" @@ -54,17 +54,25 @@ Item { } background: Rectangle { - color: root.background + color: combo.activeFocus + ? Style.focusFillFor(root.foreground, root.accent) + : (combo.hovered + ? Style.hoverFillFor(root.foreground, root.accent) + : Style.normalFillFor(root.foreground, root.accent)) border.color: combo.activeFocus - ? root.accent - : Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.4) - border.width: 1 + ? Style.focusBorderFor(root.foreground, root.accent) + : (combo.hovered + ? Style.hoverBorderFor(root.foreground, root.accent) + : Style.normalBorderFor(root.foreground, root.accent)) + border.width: combo.activeFocus + ? Style.focusBorderWidth + : (combo.hovered ? Style.hoverBorderWidth : Style.normalBorderWidth) radius: root.cornerRadius } contentItem: Text { - leftPadding: 8 - rightPadding: 24 + leftPadding: Style.spacing.controlGap + rightPadding: Style.space(24) text: combo.displayText color: root.foreground font: combo.font @@ -72,7 +80,7 @@ Item { } indicator: Text { - x: combo.width - width - 8 + x: combo.width - width - Style.spacing.controlGap y: combo.topPadding + (combo.availableHeight - height) / 2 text: "▾" color: Qt.darker(root.foreground, 1.2) @@ -86,12 +94,12 @@ Item { y: combo.height width: combo.width implicitHeight: Math.min(contentItem.implicitHeight, root.popupRowHeight * 8) - padding: 1 + padding: Style.spacing.hairline background: Rectangle { color: root.background - border.color: root.foreground - border.width: 1 + border.color: Style.normalBorderFor(root.foreground, root.accent) + border.width: Style.normalBorderWidth radius: root.cornerRadius } @@ -114,18 +122,18 @@ Item { contentItem: Text { text: String(modelData) - color: index === combo.highlightedIndex ? root.accent : root.foreground + color: index === combo.highlightedIndex ? Style.hoverStateColor(root.foreground, root.accent) : root.foreground font.family: root.fontFamily font.pixelSize: Style.font.body - leftPadding: 10 - rightPadding: 10 + leftPadding: Style.spacing.controlPaddingX + rightPadding: Style.spacing.controlPaddingX verticalAlignment: Text.AlignVCenter elide: Text.ElideRight } background: Rectangle { color: index === combo.highlightedIndex - ? Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.12) + ? Style.hoverFillFor(root.foreground, root.accent) : "transparent" radius: 0 } diff --git a/shell/shell.qml b/shell/shell.qml index 81295957..5afe1aa6 100644 --- a/shell/shell.qml +++ b/shell/shell.qml @@ -3,6 +3,8 @@ import QtQml.Models import Quickshell import Quickshell.Io +import qs.Commons as NoctaliaCommons + import "plugins/bar" import "services" @@ -598,18 +600,23 @@ ShellRoot { try { colorsRaw = Qt.atob(String(colorsB64 || "")) } catch (e) { colorsRaw = "" } try { shellRaw = Qt.atob(String(shellB64 || "")) } catch (e2) { shellRaw = "" } NoctaliaCommons.Color.resumeThemeReloads() + NoctaliaCommons.Style.resumeThemeReloads() NoctaliaCommons.Color.loadColors(colorsRaw) NoctaliaCommons.Color.loadShell(shellRaw) + NoctaliaCommons.Style.loadShell(shellRaw) + NoctaliaCommons.Style.scheduleRefresh() return "ok" } function suspendThemeReloads(): string { NoctaliaCommons.Color.suspendThemeReloads() + NoctaliaCommons.Style.suspendThemeReloads() return "ok" } function reloadTheme(): string { NoctaliaCommons.Color.reloadTheme() + NoctaliaCommons.Style.reloadTheme() return "ok" } diff --git a/shell/ui/settings/DynamicSettingsForm.qml b/shell/ui/settings/DynamicSettingsForm.qml index f77cbcb6..0ce65f29 100644 --- a/shell/ui/settings/DynamicSettingsForm.qml +++ b/shell/ui/settings/DynamicSettingsForm.qml @@ -19,7 +19,7 @@ Column { property var entry: ({}) property color foregroundColor: Color.foreground property string fontFamilyName: Style.font.family - spacing: 10 + spacing: Style.spacing.xl width: parent ? parent.width : 0 function currentValue(field) { @@ -40,7 +40,7 @@ Column { Column { required property var modelData width: root.width - spacing: 4 + spacing: Style.spacing.labelGap Text { text: modelData && modelData.label ? modelData.label : (modelData && modelData.key ? modelData.key : "") @@ -153,7 +153,7 @@ Column { property string fieldKey: "" property var field: ({}) width: parent.width - spacing: 8 + spacing: Style.spacing.rowGap property real currentNumber: { var v = root.currentValue(field) var n = typeof v === "number" ? v : parseFloat(String(v || 0)) @@ -161,7 +161,7 @@ Column { } Slider { id: slider - width: parent.width - readout.width - 8 + width: parent.width - readout.width - Style.spacing.rowGap from: field && field.min !== undefined ? field.min : 0 to: field && field.max !== undefined ? field.max : 1 stepSize: field && field.step !== undefined ? field.step : 0.01