Bring on gradient borders

This commit is contained in:
Ryan Hughes
2026-06-02 11:17:13 -04:00
parent 30bafe7bb0
commit 98ba4a9697
48 changed files with 1323 additions and 328 deletions
+23 -1
View File
@@ -173,6 +173,25 @@ gradient_start_value() {
fi fi
} }
shell_gradient_value() {
local spec index
spec=$(resolve_theme_ref "$1" "${2:-}")
parse_gradient "$spec"
if (( ${#GRADIENT_COLORS[@]} == 0 )); then
printf "%s" "$spec"
return
fi
for index in "${!GRADIENT_COLORS[@]}"; do
(( index > 0 )) && printf " "
printf "%s" "${GRADIENT_COLORS[$index]}"
done
[[ -n $GRADIENT_ANGLE ]] && printf " %sdeg" "$GRADIENT_ANGLE"
}
add_template_value() { add_template_value() {
local key="$1" local key="$1"
local value="$2" local value="$2"
@@ -247,6 +266,9 @@ add_gradient_function_value() {
gradient_start) gradient_start)
value=$(gradient_start_value "$key" "$fallback") value=$(gradient_start_value "$key" "$fallback")
;; ;;
shell_gradient)
value=$(shell_gradient_value "$key" "$fallback")
;;
*) *)
return return
;; ;;
@@ -264,7 +286,7 @@ add_gradient_function_values() {
[[ -n ${seen[$token]:-} ]] && continue [[ -n ${seen[$token]:-} ]] && continue
seen[$token]=1 seen[$token]=1
add_gradient_function_value "$token" add_gradient_function_value "$token"
done < <(grep -hEo '\{\{[[:space:]]*(hypr_gradient|gradient_start)[[:space:]]+[^}]+[[:space:]]*\}\}' "$tpl" 2>/dev/null || true) done < <(grep -hEo '\{\{[[:space:]]*(hypr_gradient|gradient_start|shell_gradient)[[:space:]]+[^}]+[[:space:]]*\}\}' "$tpl" 2>/dev/null || true)
done done
} }
+26 -15
View File
@@ -1,6 +1,6 @@
# Omarchy shell surfaces. Colors derive from colors.toml; sizes and the # Omarchy shell surfaces. Colors derive from colors.toml; sizes and the
# typographic scale come from the keys below. Themes can ship # typographic scale come from the keys below. Themes can ship
# themes/<name>/shell.toml to override any individual key. # themes/<name>/shell.toml to replace this generated file.
[bar] [bar]
# Alpha companions (where present) range from 0 (invisible) to 1 (opaque). # Alpha companions (where present) range from 0 (invisible) to 1 (opaque).
@@ -20,15 +20,20 @@ size-vertical = 28
# Shared state tokens for interactive control chrome (buttons, dropdowns, # Shared state tokens for interactive control chrome (buttons, dropdowns,
# tab strips, etc). # tab strips, etc).
# Normal: idle control chrome. # Normal: idle control chrome. Border widths accept one CSS-style scalar/list:
# N, "Y X", "T X B", or "T R B L". Per-side keys like
# normal-border-width-left override the list. Each *-border accepts either a
# solid color or a Hyprland-style gradient, e.g. "rgba(...) rgba(...) 45deg".
normal-color = "{{ foreground }}" normal-color = "{{ foreground }}"
normal-fill-alpha = 0.04 normal-fill-alpha = 0.04
normal-border = "{{ foreground }}"
normal-border-width = 1 normal-border-width = 1
normal-border-alpha = 0.4 normal-border-alpha = 0.4
# Hover-cursor: mouse hover and the panel keyboard cursor. # Hover-cursor: mouse hover and the panel keyboard cursor.
hover-cursor-color = "{{ foreground }}" hover-cursor-color = "{{ foreground }}"
hover-cursor-fill-alpha = 0.08 hover-cursor-fill-alpha = 0.08
hover-cursor-border = "{{ foreground }}"
hover-cursor-border-width = 1 hover-cursor-border-width = 1
hover-cursor-border-alpha = 0.25 hover-cursor-border-alpha = 0.25
@@ -37,12 +42,14 @@ hover-cursor-border-alpha = 0.25
# — themes that want focus to stand out override these four lines. # — themes that want focus to stand out override these four lines.
focus-color = "{{ foreground }}" focus-color = "{{ foreground }}"
focus-fill-alpha = 0.08 focus-fill-alpha = 0.08
focus-border = "{{ foreground }}"
focus-border-width = 1 focus-border-width = 1
focus-border-alpha = 0.25 focus-border-alpha = 0.25
# Selected: persistent chosen/current state. # Selected: persistent chosen/current state.
selected-color = "{{ foreground }}" selected-color = "{{ foreground }}"
selected-fill-alpha = 0.18 selected-fill-alpha = 0.18
selected-border = "{{ foreground }}"
selected-border-width = 0 selected-border-width = 0
selected-border-alpha = 1.0 selected-border-alpha = 1.0
@@ -107,11 +114,15 @@ base-size = 12
[popups] [popups]
# Shared by every bar flyout (dropdowns, OSD, popup cards). # Shared by every bar flyout (dropdowns, OSD, popup cards).
# Border accepts either a solid color or a Hyprland-style gradient. Border
# widths accept one CSS-style scalar/list: N, "Y X", "T X B", or "T R B L";
# individual border-width-top/right/bottom/left keys override the list.
background = "{{ background }}" background = "{{ background }}"
background-alpha = 1.0 background-alpha = 1.0
text = "{{ foreground }}" text = "{{ foreground }}"
border = "{{ accent }}" border = "{{ shell_gradient hyprland_active_border accent }}"
border-alpha = 1.0 border-alpha = 1.0
# border-width = 2
[tooltip] [tooltip]
# Hover tooltips across the bar, panels, and buttons. background-alpha of # Hover tooltips across the bar, panels, and buttons. background-alpha of
@@ -119,18 +130,18 @@ border-alpha = 1.0
background = "{{ background }}" background = "{{ background }}"
background-alpha = 0.97 background-alpha = 0.97
text = "{{ foreground }}" text = "{{ foreground }}"
border = "{{ foreground }}" border = "{{ shell_gradient hyprland_active_border foreground }}"
border-alpha = 1.0 border-alpha = 1.0
[notifications] [notifications]
background = "{{ background }}" background = "{{ background }}"
background-alpha = 1.0 background-alpha = 1.0
text = "{{ foreground }}" text = "{{ foreground }}"
# Conventionally matches the Hyprland active-window border. If a theme # Conventionally matches the Hyprland active-window border. Border accepts
# defines a gradient active border, the shell uses its first stop because # either a solid color or the full active-border gradient.
# QML Rectangle borders are flat colors. border = "{{ shell_gradient hyprland_active_border accent }}"
border = "{{ gradient_start hyprland_active_border accent }}"
border-alpha = 1.0 border-alpha = 1.0
# border-width = 2
countdown = "{{ accent }}" countdown = "{{ accent }}"
[launcher] [launcher]
@@ -141,14 +152,14 @@ countdown = "{{ accent }}"
background = "{{ background }}" background = "{{ background }}"
background-alpha = 0.95 background-alpha = 0.95
text = "{{ foreground }}" text = "{{ foreground }}"
border = "{{ foreground }}" border = "{{ shell_gradient hyprland_active_border foreground }}"
border-alpha = 1.0 border-alpha = 1.0
scrim = "{{ background }}" scrim = "{{ background }}"
scrim-alpha = 0.5 scrim-alpha = 0.5
selected-background = "{{ foreground }}" selected-background = "{{ foreground }}"
selected-background-alpha = 0.08 selected-background-alpha = 0.08
selected-text = "{{ accent }}" selected-text = "{{ accent }}"
selected-border = "{{ foreground }}" selected-border = "{{ shell_gradient hyprland_active_border foreground }}"
selected-border-alpha = 0.25 selected-border-alpha = 0.25
[menu] [menu]
@@ -158,14 +169,14 @@ selected-border-alpha = 0.25
background = "{{ background }}" background = "{{ background }}"
background-alpha = 1.0 background-alpha = 1.0
text = "{{ foreground }}" text = "{{ foreground }}"
border = "{{ foreground }}" border = "{{ shell_gradient hyprland_active_border foreground }}"
border-alpha = 1.0 border-alpha = 1.0
scrim = "{{ background }}" scrim = "{{ background }}"
scrim-alpha = 0.5 scrim-alpha = 0.5
selected-background = "{{ foreground }}" selected-background = "{{ foreground }}"
selected-background-alpha = 0.08 selected-background-alpha = 0.08
selected-text = "{{ accent }}" selected-text = "{{ accent }}"
selected-border = "{{ foreground }}" selected-border = "{{ shell_gradient hyprland_active_border foreground }}"
selected-border-alpha = 0.25 selected-border-alpha = 0.25
[polkit] [polkit]
@@ -178,7 +189,7 @@ background = "{{ background }}"
background-alpha = 1.0 background-alpha = 1.0
text = "{{ foreground }}" text = "{{ foreground }}"
text-error = "{{ color1 }}" text-error = "{{ color1 }}"
border = "{{ accent }}" border = "{{ shell_gradient hyprland_active_border accent }}"
border-error = "{{ color1 }}" border-error = "{{ color1 }}"
border-alpha = 1.0 border-alpha = 1.0
scrim = "{{ background }}" scrim = "{{ background }}"
@@ -196,8 +207,8 @@ background = "{{ background }}"
background-alpha = 0.8 background-alpha = 0.8
text = "{{ foreground }}" text = "{{ foreground }}"
text-error = "{{ color1 }}" text-error = "{{ color1 }}"
border = "{{ foreground }}" border = "{{ shell_gradient hyprland_active_border foreground }}"
border-active = "{{ accent }}" border-active = "{{ shell_gradient hyprland_active_border accent }}"
border-error = "{{ color1 }}" border-error = "{{ color1 }}"
border-alpha = 1.0 border-alpha = 1.0
# selection is the text-selection tint inside the input field. # selection is the text-selection tint inside the input field.
+20 -11
View File
@@ -126,10 +126,13 @@ customizes, `shell.json` is canonical — there is no deep-merge.
## Theme tokens ## Theme tokens
See [`theming.md`](theming.md) for the full theme/template workflow,
including generated `*.tpl` files, gradient helpers, and shell border syntax.
Themes ship colors in `themes/<name>/colors.toml` and surface roles + Themes ship colors in `themes/<name>/colors.toml` and surface roles +
sizing in `themes/<name>/shell.toml`. Defaults are generated from sizing in `themes/<name>/shell.toml`. Defaults are generated from
`default/themed/shell.toml.tpl`; a theme may also drop a hand-written `default/themed/shell.toml.tpl`; a theme may also drop a hand-written
`shell.toml` next to its `colors.toml` to override individual keys. `shell.toml` next to its `colors.toml` to replace the generated file.
The shell exposes these tokens to QML via two singletons in The shell exposes these tokens to QML via two singletons in
`qs.Commons`: `qs.Commons`:
@@ -143,29 +146,34 @@ The shell exposes these tokens to QML via two singletons in
state tokens/helpers, spacing (`Style.spacing.*` / `Style.space(px)`), state tokens/helpers, spacing (`Style.spacing.*` / `Style.space(px)`),
the type scale (`Style.font.*`), and bar dimensions the type scale (`Style.font.*`), and bar dimensions
(`Style.bar.sizeHorizontal` / `Style.bar.sizeVertical`). (`Style.bar.sizeHorizontal` / `Style.bar.sizeVertical`).
- `Border` — border-spec helpers for QML surfaces. Use with
`BorderSurface` from `qs.Ui` when a border should honor shell theme
gradients or per-side widths. `Color.<section>.border` is only the
flat-color fallback for code that cannot render a real border.
### Interactive states ### Interactive states
`[controls]` standardizes reusable control chrome (buttons, dropdowns, `[controls]` standardizes reusable control chrome (buttons, dropdowns,
tab strips, etc.) around four states: `normal`, `hover-cursor`, `focus`, tab strips, etc.) around four states: `normal`, `hover-cursor`, `focus`,
and `selected`. State colors accept palette roles (`foreground`, and `selected`. State colors and border tokens accept palette roles
`accent`, `urgent`, `background`) or hex strings; the default template (`foreground`, `accent`, `urgent`, `background`) or hex strings; border
ships hex values. Fill/border alphas are applied to that state's color. values may also be gradients. Fill alpha applies to the state color;
border alpha applies to the state's border token.
Surfaces like `[menu]`, `[launcher]`, and `[image-picker]` define Surfaces like `[menu]`, `[launcher]`, and `[image-picker]` define
their own `selected-*` tokens and do **not** inherit from `[controls]`. their own `selected-*` tokens and do **not** inherit from `[controls]`.
`[controls]` only governs the shared button/dropdown chrome. `[controls]` only governs the shared button/dropdown chrome.
| State | Color token | Fill alpha | Border width | Border alpha | | State | Color token | Fill alpha | Border token | Border width | Border alpha |
|-------|-------------|------------|--------------|--------------| |-------|-------------|------------|--------------|--------------|--------------|
| Normal idle chrome | `normal-color` | `normal-fill-alpha` | `normal-border-width` | `normal-border-alpha` | | Normal idle chrome | `normal-color` | `normal-fill-alpha` | `normal-border` | `normal-border-width` | `normal-border-alpha` |
| Hover / keyboard cursor | `hover-cursor-color` | `hover-cursor-fill-alpha` | `hover-cursor-border-width` | `hover-cursor-border-alpha` | | Hover / keyboard cursor | `hover-cursor-color` | `hover-cursor-fill-alpha` | `hover-cursor-border` | `hover-cursor-border-width` | `hover-cursor-border-alpha` |
| Qt activeFocus | `focus-color` | `focus-fill-alpha` | `focus-border-width` | `focus-border-alpha` | | Qt activeFocus | `focus-color` | `focus-fill-alpha` | `focus-border` | `focus-border-width` | `focus-border-alpha` |
| Persistent selected/current | `selected-color` | `selected-fill-alpha` | `selected-border-width` | `selected-border-alpha` | | Persistent selected/current | `selected-color` | `selected-fill-alpha` | `selected-border` | `selected-border-width` | `selected-border-alpha` |
The template ships `focus-*` adjacent to `hover-cursor-*` with the same The template ships `focus-*` adjacent to `hover-cursor-*` with the same
values so mouse hover, keyboard cursor, and tab focus read identically. values so mouse hover, keyboard cursor, and tab focus read identically.
Themes that want focus to stand out override the four `focus-*` keys. Themes that want focus to stand out override the `focus-*` keys.
Border widths are the theme-level on/off switches for state borders; set 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 a width to `0` to keep the fill while removing that state border. The
@@ -178,6 +186,7 @@ default keeps selected borders off globally (`selected-border-width =
hover-cursor-color = "accent" hover-cursor-color = "accent"
focus-color = "accent" focus-color = "accent"
selected-color = "foreground" selected-color = "foreground"
focus-border = "rgba(33ccffee) rgba(00ff99ee) 45deg"
# Keep selected fills but remove selected-state borders. # Keep selected fills but remove selected-state borders.
selected-border-width = 0 selected-border-width = 0
+296
View File
@@ -0,0 +1,296 @@
# Omarchy theming
Omarchy themes live under `themes/<name>/` in the source tree, with optional
user themes under `~/.config/omarchy/themes/<name>/`. A theme normally starts
with a `colors.toml`; Omarchy generates the rest of the theme files from
`default/themed/*.tpl` when `omarchy-theme-set <name>` runs.
## Theme activation flow
`omarchy-theme-set <name>` builds a clean staging directory at
`~/.config/omarchy/current/next-theme`:
1. Copy the first-party theme from `themes/<name>/`.
2. Overlay any user theme files from `~/.config/omarchy/themes/<name>/`.
3. If needed, generate `colors.toml` from `alacritty.toml`.
4. Run `omarchy-theme-set-templates` to render templates into the staging
theme.
5. Move the staging theme into `~/.config/omarchy/current/theme` and notify the
running shell.
Template rendering only happens when the staged theme has `colors.toml`.
Existing files are never overwritten by a template, so a hand-written
`themes/<name>/shell.toml` or `hyprland.lua` wins over
`default/themed/shell.toml.tpl` or `hyprland.lua.tpl`.
User templates in `~/.config/omarchy/themed/*.tpl` are processed before the
built-in templates. If a user template has the same output filename as a
built-in template, the built-in output is skipped.
## `colors.toml`
`colors.toml` provides the palette keys used by templates. Common keys are:
```toml
foreground = "#a9b1d6"
background = "#1a1b26"
accent = "#7aa2f7"
color1 = "#f7768e"
color4 = "#7aa2f7"
```
Any key can be referenced from a template with `{{ key }}`. The shell also
uses a few semantic palette keys directly:
- `foreground`
- `background`
- `accent` — preferred when present; otherwise some places fall back to
`color4`
- `urgent` / `color1`
## Template placeholders
Templates are plain files ending in `.tpl`. `omarchy-theme-set-templates`
replaces placeholders with values from `colors.toml`.
### Color placeholders
For a color key such as `accent = "#7aa2f7"`:
| Placeholder | Output |
|-------------|--------|
| `{{ accent }}` | `#7aa2f7` |
| `{{ accent_strip }}` | `7aa2f7` |
| `{{ accent_rgb }}` | `122,162,247` |
### Color mixing
`mix`, `mix_strip`, and `mix_rgb` blend two hex colors by a fraction or
percentage:
```text
{{ mix background foreground 15% }}
{{ mix_strip background accent 0.35 }}
{{ mix_rgb color0 color7 50 }}
```
### Gradient helpers
Some theme keys can be either a solid color or a Hyprland-style gradient:
```toml
hyprland_active_border = "rgba(33ccffee) rgba(00ff99ee) 45deg"
```
Gradient helper placeholders understand those values:
| Helper | Use | Example output |
|--------|-----|----------------|
| `{{ hypr_gradient hyprland_active_border accent }}` | Hyprland Lua config | `{ colors = { "rgba(33ccffee)", "rgba(00ff99ee)" }, angle = 45 }` |
| `{{ shell_gradient hyprland_active_border accent }}` | shell border tokens | `rgba(33ccffee) rgba(00ff99ee) 45deg` |
| `{{ gradient_start hyprland_active_border accent }}` | flat-color-only consumers | `#33ccff` |
The second argument is a fallback. For example,
`{{ shell_gradient hyprland_active_border accent }}` means: use
`hyprland_active_border` if the theme defines it; otherwise use `accent`.
The helper does not choose the first color unless you use `gradient_start`.
## `shell.toml`
`shell.toml` contains shell surface roles, control states, spacing, typography,
and bar sizing. The default generated file comes from
`default/themed/shell.toml.tpl`.
The running shell reads `shell.toml` into two QML singletons:
- `Color` for palette and surface roles like `Color.menu.border`.
- `Style` for controls, spacing, font scale, corner radius, and bar sizing.
### Borders
Shell border tokens accept either a solid color or a gradient in the same key:
```toml
[notifications]
border = "#7aa2f7"
```
or:
```toml
[notifications]
border = "rgba(33ccffee) rgba(00ff99ee) 45deg"
```
Do not add a separate `border-gradient` key for new themes. The parser still
accepts `border-gradient` and `*-border-gradient` for compatibility with older
configs, but the canonical form is the border key itself.
Border alphas apply to solid borders and to every gradient stop:
```toml
[notifications]
border = "rgba(33ccffee) rgba(00ff99ee) 45deg"
border-alpha = 0.8
```
If a color stop already includes alpha, the stop alpha and `border-alpha` are
combined.
### Border widths
Border widths accept CSS-style lists:
```toml
border-width = 2 # all sides
border-width = "2 4" # top/bottom, right/left
border-width = "2 4 6" # top, right/left, bottom
border-width = "2 4 6 8" # top, right, bottom, left
```
Per-side keys override the list:
```toml
[notifications]
border-width = 2
border-width-left = 6
```
That gives notifications a 2px border on the top, right, and bottom, and a 6px
left edge.
State-specific borders follow the same pattern. A selected menu row can use a
different width from the card border:
```toml
[menu]
selected-border = "accent"
selected-border-width = "1 1 1 4"
```
For state-specific surfaces such as lock and polkit, the token name prefixes
the width key:
```toml
[lock]
border-active = "rgba(33ccffee) rgba(00ff99ee) 45deg"
border-active-width-left = 6
```
### Control borders
`[controls]` governs shared controls such as buttons, dropdowns, text fields,
toggles, and cursor rows. Each state has a fill color, optional border value,
border width, and border alpha:
```toml
[controls]
normal-color = "#a9b1d6"
normal-border = "#a9b1d6"
normal-border-width = 1
normal-border-alpha = 0.4
hover-cursor-color = "#a9b1d6"
hover-cursor-border = "#a9b1d6"
hover-cursor-border-width = 1
hover-cursor-border-alpha = 0.25
```
The `*-border` keys can also be gradients:
```toml
[controls]
focus-border = "rgba(33ccffee) rgba(00ff99ee) 45deg"
focus-border-width = "2 2 2 4"
```
Set a border width to `0` to keep the fill but remove that state border.
### Surface sections
Common shell sections include:
- `[bar]`
- `[controls]`
- `[popups]`
- `[tooltip]`
- `[notifications]`
- `[launcher]`
- `[menu]`
- `[polkit]`
- `[lock]`
- `[image-picker]`
- `[spacing]`
- `[font]`
Clipboard and emojis inherit menu tokens. Popups are used by bar flyouts,
dropdowns, OSD, and popup cards.
## QML border API
Plugin and shell QML should use `BorderSurface` for theme-aware borders:
```qml
import qs.Commons
import qs.Ui
BorderSurface {
color: Color.popups.background
borderSpec: Border.surfaceSpec("popups", "border", Color.popups.border, 2)
padding: Style.spacing.popupPadding
Item {
anchors.fill: parent
anchors.topMargin: parent.contentTopInset
anchors.rightMargin: parent.contentRightInset
anchors.bottomMargin: parent.contentBottomInset
anchors.leftMargin: parent.contentLeftInset
}
}
```
Use `Border.surfaceSpec(section, token, fallbackColor, fallbackWidth)` for
shell theme tokens, `Border.controlSpec(state, foreground, accent)` for shared
controls, and `Border.flat(color, width)` for a deliberate local border that
should not be overridden by the active theme. `Color.<section>.border` is the
flat first-stop color for consumers that cannot render full border specs.
## Hyprland templates
Hyprland theme output is generated from `default/themed/hyprland.lua.tpl`.
Use `hypr_gradient` for border values because Hyprland's Lua config wants a
Lua string for solid colors and a Lua table for gradients:
```lua
local active_border_color = {{ hypr_gradient hyprland_active_border accent }}
```
For a solid fallback this renders:
```lua
local active_border_color = "#7aa2f7"
```
For a gradient it renders:
```lua
local active_border_color = { colors = { "rgba(33ccffee)", "rgba(00ff99ee)" }, angle = 45 }
```
## Adding or overriding theme files
- Add palette values to `themes/<name>/colors.toml`.
- Prefer generated files when the theme can be expressed with templates.
- Add a hand-written file in `themes/<name>/` only when that theme needs to
override the generated output entirely.
- Add a new built-in template under `default/themed/<file>.tpl` when every
theme should generate that file.
- Add a user-wide template under `~/.config/omarchy/themed/<file>.tpl` when a
local customization should apply across themes.
When changing templates or theme helpers, run focused tests such as:
```bash
./test/cli
./test/shell
```
+210
View File
@@ -0,0 +1,210 @@
pragma Singleton
import QtQuick
import "BorderGeometry.js" as Geometry
// Central border-spec factory for shell surfaces and controls. A spec carries
// color, optional gradient, and top/right/bottom/left widths so renderers can
// choose the cheap Rectangle path or the Shape-ring path without duplicating
// theme parsing logic.
QtObject {
id: root
function none() {
return flat("transparent", 0)
}
function flat(color, width) {
return {
color: color || "transparent",
widths: Geometry.parseWidthSpec(width, 0),
gradient: { colors: [], angle: 0, enabled: false },
}
}
function value(section, key) {
var v = Color.shellValues[section + "." + key]
return (v === undefined || v === null) ? "" : v
}
function valueOr(section, keys) {
for (var i = 0; i < keys.length; i++) {
var v = value(section, keys[i])
if (String(v).length > 0) return v
}
return ""
}
function alpha(section, key, fallback) {
var raw = value(section, key)
if (String(raw).length === 0) return fallback
var n = Number(raw)
return isFinite(n) ? Geometry.clampAlpha(n) : fallback
}
function cssColor(color, opacity) {
var a = opacity === undefined || opacity === null ? 1 : Geometry.clampAlpha(opacity)
if (color && typeof color === "object" && color.r !== undefined) {
if (typeof Qt !== "undefined" && Qt.rgba) {
return Qt.rgba(color.r, color.g, color.b, (color.a === undefined ? 1 : color.a) * a)
}
return "#"
+ Geometry.padHex(color.r * 255)
+ Geometry.padHex(color.g * 255)
+ Geometry.padHex(color.b * 255)
+ Geometry.padHex((color.a === undefined ? 1 : color.a) * a * 255)
}
var s = String(color || "").replace(/^\s+|\s+$/g, "")
var role = s.toLowerCase()
if (role === "foreground" || role === "text") return cssColor(Color.foreground, a)
if (role === "accent") return cssColor(Color.accent, a)
if (role === "urgent") return cssColor(Color.urgent, a)
if (role === "background") return cssColor(Color.background, a)
if (role === "transparent") return "transparent"
return Geometry.canonicalColor(s, a)
}
function resolvedGradient(raw, fallbackColor, opacity) {
var s = String(raw || "").replace(/^\s+|\s+$/g, "")
if (s.length === 0) return { colors: [], angle: 0, enabled: false }
var parts = s.split(/\s+/)
var colors = []
var angle = 0
for (var i = 0; i < parts.length; i++) {
if (parts[i].match(/^-?\d+(?:\.\d+)?deg$/)) angle = Number(parts[i].replace(/deg$/, ""))
else colors.push(cssColor(parts[i], opacity))
}
if (colors.length === 0) colors.push(cssColor(fallbackColor, opacity))
return { colors: colors, angle: angle, enabled: colors.length > 1 }
}
function sameColor(a, b) {
if (typeof Qt === "undefined" || !Qt.color) return String(a) === String(b)
var ca = typeof a === "string" ? Qt.color(a) : a
var cb = typeof b === "string" ? Qt.color(b) : b
if (!ca || !cb || ca.r === undefined || cb.r === undefined) return String(a) === String(b)
return Math.round(ca.r * 255) === Math.round(cb.r * 255)
&& Math.round(ca.g * 255) === Math.round(cb.g * 255)
&& Math.round(ca.b * 255) === Math.round(cb.b * 255)
&& Math.round((ca.a === undefined ? 1 : ca.a) * 255) === Math.round((cb.a === undefined ? 1 : cb.a) * 255)
}
function localOrSurfaceSpec(section, token, localColor, defaultColor, fallbackWidth, alphaKey) {
if (!sameColor(localColor, defaultColor)) return flat(localColor, fallbackWidth)
return surfaceSpec(section, token, localColor, fallbackWidth, alphaKey)
}
function surfaceWidths(section, token, fallbackWidth) {
var base = valueOr(section, token === "border" ? ["border-width"] : [token + "-width", "border-width"])
var widths = Geometry.parseWidthSpec(base, fallbackWidth)
return Geometry.withSideOverrides(
widths,
valueOr(section, token === "border" ? ["border-width-top"] : [token + "-width-top", "border-width-top"]),
valueOr(section, token === "border" ? ["border-width-right"] : [token + "-width-right", "border-width-right"]),
valueOr(section, token === "border" ? ["border-width-bottom"] : [token + "-width-bottom", "border-width-bottom"]),
valueOr(section, token === "border" ? ["border-width-left"] : [token + "-width-left", "border-width-left"])
)
}
function borderValue(raw, fallbackColor, opacity, legacyGradientRaw) {
var fallback = cssColor(fallbackColor, opacity)
var primaryRaw = String(raw || "").replace(/^\s+|\s+$/g, "")
var primary = resolvedGradient(primaryRaw.length > 0 ? primaryRaw : fallbackColor, fallbackColor, opacity)
var color = primary.colors.length > 0 ? primary.colors[0] : fallback
var gradient = primary.enabled ? primary : { colors: [], angle: 0, enabled: false }
// Backward compatibility for existing configs written during the separate
// border-gradient experiment. New configs put solid colors and gradients in
// the same border token.
if (!gradient.enabled && String(legacyGradientRaw || "").replace(/^\s+|\s+$/g, "").length > 0) {
var legacy = resolvedGradient(legacyGradientRaw, color, opacity)
if (legacy.enabled) gradient = legacy
}
return { color: color, gradient: gradient }
}
function surfaceSpec(section, token, fallbackColor, fallbackWidth, alphaKey) {
var opacity = alpha(section, alphaKey || token + "-alpha", 1.0)
var legacyGradientRaw = valueOr(section, token === "border" ? ["border-gradient"] : [token + "-gradient", "border-gradient"])
var resolved = borderValue(value(section, token), fallbackColor, opacity, legacyGradientRaw)
return {
color: resolved.color,
widths: surfaceWidths(section, token, fallbackWidth),
gradient: resolved.gradient,
}
}
function controlPrefix(state) {
if (state === "hover" || state === "hot") return "hover-cursor"
return state || "normal"
}
function controlColor(prefix, foreground, accent, urgent) {
if (prefix === "focus") return Style.focusStateColor(foreground, accent, urgent)
if (prefix === "hover-cursor") return Style.hoverStateColor(foreground, accent, urgent)
if (prefix === "selected") return Style.selectedStateColor(foreground, accent, urgent)
return Style.normalStateColor(foreground, accent, urgent)
}
function controlAlpha(prefix) {
if (prefix === "focus") return Style.focusBorderAlpha
if (prefix === "hover-cursor") return Style.hoverBorderAlpha
if (prefix === "selected") return Style.selectedBorderAlpha
return Style.normalBorderAlpha
}
function controlFallbackWidth(prefix) {
if (prefix === "focus") return Style.focusBorderWidth
if (prefix === "hover-cursor") return Style.hoverBorderWidth
if (prefix === "selected") return Style.selectedBorderWidth
return Style.normalBorderWidth
}
function controlWidths(state) {
var prefix = controlPrefix(state)
var fallbackWidth = controlFallbackWidth(prefix)
var base = Style.styleOverrides[prefix + "-border-width"]
var widths = Geometry.parseWidthSpec(base, fallbackWidth)
return Geometry.withSideOverrides(
widths,
Style.styleOverrides[prefix + "-border-width-top"],
Style.styleOverrides[prefix + "-border-width-right"],
Style.styleOverrides[prefix + "-border-width-bottom"],
Style.styleOverrides[prefix + "-border-width-left"]
)
}
function controlHasWidth(state) {
return Geometry.maxWidth(controlWidths(state)) > 0
}
function controlSpec(state, foreground, accent, urgent) {
var prefix = controlPrefix(state)
var resolved = borderValue(
Style.styleOverrides[prefix + "-border"],
controlColor(prefix, foreground, accent, urgent),
controlAlpha(prefix),
Style.styleOverrides[prefix + "-border-gradient"]
)
return { color: resolved.color, widths: controlWidths(prefix), gradient: resolved.gradient }
}
function withWidth(spec, width) {
if (!spec) return flat("transparent", 0)
return { color: spec.color, gradient: spec.gradient, widths: Geometry.parseWidthSpec(width, 0) }
}
function isNone(spec) { return !spec || Geometry.maxWidth(spec.widths) <= 0 }
function needsOverlay(spec) { return Geometry.needsOverlay(spec) }
function canUseNative(spec) { return Geometry.canUseNative(spec) }
function top(spec) { return spec && spec.widths ? spec.widths.top : 0 }
function right(spec) { return spec && spec.widths ? spec.widths.right : 0 }
function bottom(spec) { return spec && spec.widths ? spec.widths.bottom : 0 }
function left(spec) { return spec && spec.widths ? spec.widths.left : 0 }
function uniformWidth(spec) { return spec && spec.widths ? spec.widths.top : 0 }
function color(spec) { return spec ? spec.color : "transparent" }
}
+257
View File
@@ -0,0 +1,257 @@
.pragma library
function clamp(value, min, max) {
var n = Number(value)
if (!isFinite(n)) return min
return Math.max(min, Math.min(max, n))
}
function clampAlpha(value) {
return clamp(value, 0, 1)
}
function padHex(value) {
var n = clamp(Math.round(Number(value)), 0, 255)
var h = n.toString(16)
return h.length < 2 ? "0" + h : h
}
function qmlHexColor(rgb, alphaByte) {
var rgbPart = String(rgb || "").replace(/^#/, "")
var a = clamp(Math.round(Number(alphaByte)), 0, 255)
if (typeof Qt !== "undefined" && Qt.rgba && rgbPart.length >= 6) {
return Qt.rgba(
parseInt(rgbPart.substring(0, 2), 16) / 255,
parseInt(rgbPart.substring(2, 4), 16) / 255,
parseInt(rgbPart.substring(4, 6), 16) / 255,
a / 255
)
}
var aHex = padHex(a)
return aHex.toLowerCase() === "ff" ? "#" + rgbPart : "#" + rgbPart + aHex
}
function canonicalColor(value, alpha) {
var a = alpha === undefined || alpha === null ? 1 : clampAlpha(alpha)
var s = String(value || "").replace(/^\s+|\s+$/g, "")
var m
m = s.match(/^#([0-9A-Fa-f]{3})$/)
if (m) {
var sh = m[1]
return qmlHexColor(
sh.charAt(0) + sh.charAt(0)
+ sh.charAt(1) + sh.charAt(1)
+ sh.charAt(2) + sh.charAt(2),
a * 255
)
}
m = s.match(/^#([0-9A-Fa-f]{6})([0-9A-Fa-f]{2})?$/)
if (m) {
var colorAlpha = m[2] ? parseInt(m[2], 16) / 255 : 1
return qmlHexColor(m[1], colorAlpha * a * 255)
}
m = s.match(/^[Rr][Gg][Bb]\(([0-9A-Fa-f]{6})\)$/)
if (m) return qmlHexColor(m[1], a * 255)
m = s.match(/^[Rr][Gg][Bb][Aa]\(([0-9A-Fa-f]{6})([0-9A-Fa-f]{2})\)$/)
if (m) return qmlHexColor(m[1], (parseInt(m[2], 16) / 255) * a * 255)
m = s.match(/^[Rr][Gg][Bb]\(([0-9]+),([0-9]+),([0-9]+)\)$/)
if (m) return qmlHexColor(padHex(m[1]) + padHex(m[2]) + padHex(m[3]), a * 255)
m = s.match(/^[Rr][Gg][Bb][Aa]\(([0-9]+),([0-9]+),([0-9]+),([0-9.]+)\)$/)
if (m) return qmlHexColor(padHex(m[1]) + padHex(m[2]) + padHex(m[3]), clampAlpha(m[4]) * a * 255)
m = s.match(/^0x([0-9A-Fa-f]{2})([0-9A-Fa-f]{6})$/)
if (m) return qmlHexColor(m[2], (parseInt(m[1], 16) / 255) * a * 255)
return s
}
function parseWidthSpec(value, fallback) {
var fb = Number(fallback)
if (!isFinite(fb) || fb < 0) fb = 0
if (value === undefined || value === null || value === "") {
return { top: fb, right: fb, bottom: fb, left: fb }
}
var parts = String(value).match(/-?\d+(?:\.\d+)?/g) || []
var nums = []
for (var i = 0; i < parts.length && i < 4; i++) {
var n = Number(parts[i])
nums.push(isFinite(n) && n > 0 ? n : 0)
}
if (nums.length === 0) nums = [fb]
if (nums.length === 1) return { top: nums[0], right: nums[0], bottom: nums[0], left: nums[0] }
if (nums.length === 2) return { top: nums[0], right: nums[1], bottom: nums[0], left: nums[1] }
if (nums.length === 3) return { top: nums[0], right: nums[1], bottom: nums[2], left: nums[1] }
return { top: nums[0], right: nums[1], bottom: nums[2], left: nums[3] }
}
function withSideOverrides(widths, top, right, bottom, left) {
var out = {
top: Number(widths && widths.top) || 0,
right: Number(widths && widths.right) || 0,
bottom: Number(widths && widths.bottom) || 0,
left: Number(widths && widths.left) || 0,
}
if (top !== undefined && top !== null && top !== "") out.top = Math.max(0, Number(top) || 0)
if (right !== undefined && right !== null && right !== "") out.right = Math.max(0, Number(right) || 0)
if (bottom !== undefined && bottom !== null && bottom !== "") out.bottom = Math.max(0, Number(bottom) || 0)
if (left !== undefined && left !== null && left !== "") out.left = Math.max(0, Number(left) || 0)
return out
}
function parseGradientSpec(value, fallbackColor, alpha) {
var s = String(value || "").replace(/^\s+|\s+$/g, "")
var colors = []
var angle = 0
var parts = s.length > 0 ? s.split(/\s+/) : []
for (var i = 0; i < parts.length; i++) {
var part = parts[i]
var angleMatch = part.match(/^(-?\d+(?:\.\d+)?)deg$/)
if (angleMatch) angle = Number(angleMatch[1])
else colors.push(canonicalColor(part, alpha))
}
if (colors.length === 0 && fallbackColor !== undefined && fallbackColor !== null)
colors.push(canonicalColor(fallbackColor, alpha))
return {
colors: colors,
angle: isFinite(angle) ? angle : 0,
enabled: colors.length > 1,
}
}
function isUniform(widths) {
if (!widths) return true
return widths.top === widths.right && widths.top === widths.bottom && widths.top === widths.left
}
function maxWidth(widths) {
if (!widths) return 0
return Math.max(widths.top || 0, widths.right || 0, widths.bottom || 0, widths.left || 0)
}
function needsOverlay(spec) {
if (!spec) return false
if (maxWidth(spec.widths) <= 0) return false
return !!(spec.gradient && spec.gradient.enabled) || !isUniform(spec.widths)
}
function canUseNative(spec) {
return !!spec && maxWidth(spec.widths) > 0 && !needsOverlay(spec)
}
function normalizeRadii(w, h, r) {
var tl = { rx: Math.max(0, Number(r.tlrx) || 0), ry: Math.max(0, Number(r.tlry) || 0) }
var tr = { rx: Math.max(0, Number(r.trrx) || 0), ry: Math.max(0, Number(r.trry) || 0) }
var br = { rx: Math.max(0, Number(r.brrx) || 0), ry: Math.max(0, Number(r.brry) || 0) }
var bl = { rx: Math.max(0, Number(r.blrx) || 0), ry: Math.max(0, Number(r.blry) || 0) }
var scale = 1
if (tl.rx + tr.rx > w && tl.rx + tr.rx > 0) scale = Math.min(scale, w / (tl.rx + tr.rx))
if (bl.rx + br.rx > w && bl.rx + br.rx > 0) scale = Math.min(scale, w / (bl.rx + br.rx))
if (tl.ry + bl.ry > h && tl.ry + bl.ry > 0) scale = Math.min(scale, h / (tl.ry + bl.ry))
if (tr.ry + br.ry > h && tr.ry + br.ry > 0) scale = Math.min(scale, h / (tr.ry + br.ry))
if (scale < 1) {
tl.rx *= scale; tr.rx *= scale; br.rx *= scale; bl.rx *= scale
tl.ry *= scale; tr.ry *= scale; br.ry *= scale; bl.ry *= scale
}
return { tl: tl, tr: tr, br: br, bl: bl }
}
function roundedRectPath(x, y, w, h, radii) {
if (w <= 0 || h <= 0) return ""
var r = normalizeRadii(w, h, radii)
var right = x + w
var bottom = y + h
var p = []
p.push("M", x + r.tl.rx, y)
p.push("H", right - r.tr.rx)
if (r.tr.rx > 0 || r.tr.ry > 0) p.push("A", r.tr.rx, r.tr.ry, 0, 0, 1, right, y + r.tr.ry)
p.push("V", bottom - r.br.ry)
if (r.br.rx > 0 || r.br.ry > 0) p.push("A", r.br.rx, r.br.ry, 0, 0, 1, right - r.br.rx, bottom)
p.push("H", x + r.bl.rx)
if (r.bl.rx > 0 || r.bl.ry > 0) p.push("A", r.bl.rx, r.bl.ry, 0, 0, 1, x, bottom - r.bl.ry)
p.push("V", y + r.tl.ry)
if (r.tl.rx > 0 || r.tl.ry > 0) p.push("A", r.tl.rx, r.tl.ry, 0, 0, 1, x + r.tl.rx, y)
p.push("Z")
return p.join(" ")
}
function ringPath(w, h, radius, widths) {
w = Math.max(0, Number(w) || 0)
h = Math.max(0, Number(h) || 0)
radius = Math.max(0, Number(radius) || 0)
widths = widths || { top: 0, right: 0, bottom: 0, left: 0 }
var outer = roundedRectPath(0, 0, w, h, {
tlrx: radius, tlry: radius,
trrx: radius, trry: radius,
brrx: radius, brry: radius,
blrx: radius, blry: radius,
})
var ix = widths.left || 0
var iy = widths.top || 0
var iw = w - ix - (widths.right || 0)
var ih = h - iy - (widths.bottom || 0)
if (iw <= 0 || ih <= 0) return outer
var inner = roundedRectPath(ix, iy, iw, ih, {
tlrx: Math.max(0, radius - (widths.left || 0)),
tlry: Math.max(0, radius - (widths.top || 0)),
trrx: Math.max(0, radius - (widths.right || 0)),
trry: Math.max(0, radius - (widths.top || 0)),
brrx: Math.max(0, radius - (widths.right || 0)),
brry: Math.max(0, radius - (widths.bottom || 0)),
blrx: Math.max(0, radius - (widths.left || 0)),
blry: Math.max(0, radius - (widths.bottom || 0)),
})
return outer + " " + inner
}
function gradientEndpoints(w, h, angle) {
w = Math.max(1, Number(w) || 1)
h = Math.max(1, Number(h) || 1)
var rad = (Number(angle) || 0) * Math.PI / 180
var dx = Math.cos(rad)
var dy = Math.sin(rad)
var len = (Math.abs(w * dx) + Math.abs(h * dy)) / 2
var cx = w / 2
var cy = h / 2
return {
x1: cx - dx * len,
y1: cy - dy * len,
x2: cx + dx * len,
y2: cy + dy * len,
}
}
function stopColor(colors, index) {
if (!colors || colors.length === 0) return "transparent"
if (index < colors.length) return colors[index]
return colors[colors.length - 1]
}
function stopPosition(colors, index) {
var count = colors ? colors.length : 0
if (count <= 1) return index === 0 ? 0 : 1
if (index >= count) return 1
return index / (count - 1)
}
+32 -6
View File
@@ -2,11 +2,12 @@ pragma Singleton
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import "BorderGeometry.js" as Geometry
// Color surfaces for the shell. Foundational palette (foreground, background, // Color surfaces for the shell. Foundational palette (foreground, background,
// accent, urgent) comes from theme/colors.toml. Per-surface roles come from // accent, urgent) comes from theme/colors.toml. Per-surface roles come from
// theme/shell.toml — generated per theme from default/themed/shell.toml.tpl, // theme/shell.toml — generated per theme from default/themed/shell.toml.tpl,
// or shipped directly by a theme to override individual keys. Surfaces that // or shipped directly by a theme to replace the generated file. Surfaces that
// don't appear in shell.toml fall back to the foundational palette. // don't appear in shell.toml fall back to the foundational palette.
QtObject { QtObject {
id: root id: root
@@ -34,9 +35,32 @@ QtObject {
return Util.clampAlpha(n) return Util.clampAlpha(n)
} }
// Compose a color from a base-color key and its `-alpha` companion. function firstColorToken(value) {
var parts = String(value || "").replace(/^\s+|\s+$/g, "").split(/\s+/)
for (var i = 0; i < parts.length; i++) {
if (!parts[i].match(/^-?\d+(?:\.\d+)?deg$/)) return parts[i]
}
return value
}
function flatColor(value, fallback) {
var token = firstColorToken(value)
var role = String(token || "").replace(/^\s+|\s+$/g, "").toLowerCase()
if (role === "foreground" || role === "text") return root.foreground
if (role === "accent") return root.accent
if (role === "urgent") return root.urgent
if (role === "background") return root.background
if (role === "transparent") return Qt.rgba(0, 0, 0, 0)
var color = Geometry.canonicalColor(token, 1)
if (typeof color === "string" && color === token && token.charAt(0) !== "#") return fallback
return color
}
// Compose a color from a base-color key and its `-alpha` companion. If the
// base token is a gradient, color-only consumers use the first stop.
function composed(colorKey, alphaKey, colorFallback, alphaFallback) { function composed(colorKey, alphaKey, colorFallback, alphaFallback) {
return Util.alpha(pick(colorKey, colorFallback), pickAlpha(alphaKey, alphaFallback)) return Util.alpha(flatColor(pick(colorKey, colorFallback), colorFallback), pickAlpha(alphaKey, alphaFallback))
} }
readonly property QtObject bar: QtObject { readonly property QtObject bar: QtObject {
@@ -130,8 +154,9 @@ QtObject {
// Single TOML walker for shell.toml. Both Color (surface roles) and Style // Single TOML walker for shell.toml. Both Color (surface roles) and Style
// (typography, spacing, bar, control states) consume the resulting dict. // (typography, spacing, bar, control states) consume the resulting dict.
// Accepts quoted strings and bare numeric values; tolerates inline comments. // Accepts quoted strings, bare numeric values, bare width lists, and bare
// Numbers are kept as strings here — readers coerce when they pull a value. // role names; tolerates inline comments. Numbers are kept as strings here —
// readers coerce when they pull a value.
function loadShell(raw) { function loadShell(raw) {
var parsed = {} var parsed = {}
var text = String(raw || "") var text = String(raw || "")
@@ -145,8 +170,9 @@ QtObject {
if (sectionMatch) { section = sectionMatch[1]; continue } if (sectionMatch) { section = sectionMatch[1]; continue }
var stringKv = line.match(/^([A-Za-z0-9_-]+)\s*=\s*["']([^"']+)["']\s*(#.*)?$/) var stringKv = line.match(/^([A-Za-z0-9_-]+)\s*=\s*["']([^"']+)["']\s*(#.*)?$/)
var numKv = line.match(/^([A-Za-z0-9_-]+)\s*=\s*(-?\d+(?:\.\d+)?)\s*(#.*)?$/) var numKv = line.match(/^([A-Za-z0-9_-]+)\s*=\s*(-?\d+(?:\.\d+)?)\s*(#.*)?$/)
var widthKv = line.match(/^([A-Za-z0-9_-]+)\s*=\s*(-?\d+(?:\.\d+)?(?:\s+-?\d+(?:\.\d+)?){1,3})\s*(#.*)?$/)
var bareKv = line.match(/^([A-Za-z0-9_-]+)\s*=\s*([A-Za-z][A-Za-z0-9_-]*)\s*(#.*)?$/) var bareKv = line.match(/^([A-Za-z0-9_-]+)\s*=\s*([A-Za-z][A-Za-z0-9_-]*)\s*(#.*)?$/)
var kv = stringKv || numKv || bareKv var kv = stringKv || numKv || widthKv || bareKv
if (!kv || !section) continue if (!kv || !section) continue
parsed[section + "." + kv[1]] = kv[2] parsed[section + "." + kv[1]] = kv[2]
} }
+2 -1
View File
@@ -414,8 +414,9 @@ QtObject {
if (key === "scale") nextSpacingScale = fval if (key === "scale") nextSpacingScale = fval
else spacingOut[key] = fval else spacingOut[key] = fval
} }
} else if (section === "controls") { } else if (section === "controls" || section === "style") {
// Strings are passed through; styleRawNum/styleString coerce on read. // Strings are passed through; styleRawNum/styleString coerce on read.
// [style] is the legacy name for [controls].
styleOut[key] = raw styleOut[key] = raw
} }
} }
+1
View File
@@ -1,4 +1,5 @@
module qs.Commons module qs.Commons
singleton Border 1.0 Border.qml
singleton Color 1.0 Color.qml singleton Color 1.0 Color.qml
singleton Style 1.0 Style.qml singleton Style 1.0 Style.qml
singleton Util 1.0 Util.qml singleton Util 1.0 Util.qml
+55
View File
@@ -0,0 +1,55 @@
import QtQuick
import QtQuick.Shapes
import qs.Commons
import "../Commons/BorderGeometry.js" as Geometry
// Visual-only border renderer. It draws a filled rounded ring so borders can
// have gradients and independent top/right/bottom/left widths. Flat uniform
// borders should stay on Rectangle.border; this component is the fallback for
// cases Rectangle cannot represent.
Item {
id: root
property var borderSpec: Border.none()
property real radius: 0
readonly property var _widths: borderSpec && borderSpec.widths ? borderSpec.widths : Geometry.parseWidthSpec(0, 0)
readonly property bool hasBorder: Geometry.maxWidth(_widths) > 0
readonly property var _gradient: borderSpec && borderSpec.gradient ? borderSpec.gradient : ({ colors: [], angle: 0, enabled: false })
readonly property var _colors: _gradient.enabled ? _gradient.colors : [Border.color(borderSpec), Border.color(borderSpec)]
readonly property var _endpoints: Geometry.gradientEndpoints(width, height, _gradient.angle || 0)
readonly property string _path: Geometry.ringPath(width, height, radius, _widths)
visible: hasBorder && width > 0 && height > 0
anchors.fill: parent
z: 100000
Shape {
anchors.fill: parent
preferredRendererType: Shape.CurveRenderer
ShapePath {
fillRule: ShapePath.OddEvenFill
strokeWidth: 0
fillGradient: LinearGradient {
x1: root._endpoints.x1
y1: root._endpoints.y1
x2: root._endpoints.x2
y2: root._endpoints.y2
GradientStop { position: Geometry.stopPosition(root._colors, 0); color: Geometry.stopColor(root._colors, 0) }
GradientStop { position: Geometry.stopPosition(root._colors, 1); color: Geometry.stopColor(root._colors, 1) }
GradientStop { position: Geometry.stopPosition(root._colors, 2); color: Geometry.stopColor(root._colors, 2) }
GradientStop { position: Geometry.stopPosition(root._colors, 3); color: Geometry.stopColor(root._colors, 3) }
GradientStop { position: Geometry.stopPosition(root._colors, 4); color: Geometry.stopColor(root._colors, 4) }
GradientStop { position: Geometry.stopPosition(root._colors, 5); color: Geometry.stopColor(root._colors, 5) }
GradientStop { position: Geometry.stopPosition(root._colors, 6); color: Geometry.stopColor(root._colors, 6) }
GradientStop { position: Geometry.stopPosition(root._colors, 7); color: Geometry.stopColor(root._colors, 7) }
GradientStop { position: Geometry.stopPosition(root._colors, 8); color: Geometry.stopColor(root._colors, 8) }
GradientStop { position: Geometry.stopPosition(root._colors, 9); color: Geometry.stopColor(root._colors, 9) }
}
PathSvg { path: root._path }
}
}
}
+40
View File
@@ -0,0 +1,40 @@
import QtQuick
import qs.Commons
// Rectangle-compatible surface with Omarchy border specs. Uses native
// Rectangle.border for cheap flat/uniform borders and BorderOverlay for
// gradients or per-side widths.
Rectangle {
id: root
property var borderSpec: Border.none()
property real padding: 0
property real topPadding: padding
property real rightPadding: padding
property real bottomPadding: padding
property real leftPadding: padding
readonly property real borderTop: Border.top(borderSpec)
readonly property real borderRight: Border.right(borderSpec)
readonly property real borderBottom: Border.bottom(borderSpec)
readonly property real borderLeft: Border.left(borderSpec)
readonly property real contentTopInset: borderTop + topPadding
readonly property real contentRightInset: borderRight + rightPadding
readonly property real contentBottomInset: borderBottom + bottomPadding
readonly property real contentLeftInset: borderLeft + leftPadding
readonly property bool usesOverlayBorder: Border.needsOverlay(borderSpec)
border.color: Border.canUseNative(borderSpec) ? Border.color(borderSpec) : "transparent"
border.width: Border.canUseNative(borderSpec) ? Border.uniformWidth(borderSpec) : 0
Loader {
anchors.fill: parent
active: root.usesOverlayBorder
sourceComponent: BorderOverlay {
anchors.fill: parent
radius: root.radius
borderSpec: root.borderSpec
}
}
}
+24 -22
View File
@@ -17,7 +17,7 @@ import qs.Commons
// //
// Emits `hovered(bool)` so panels with their own keyboard cursor model // Emits `hovered(bool)` so panels with their own keyboard cursor model
// can update state on mouse enter/leave. // can update state on mouse enter/leave.
Rectangle { BorderSurface {
id: root id: root
property string text: "" property string text: ""
@@ -46,6 +46,11 @@ Rectangle {
property real verticalPadding: Style.spacing.controlPaddingY property real verticalPadding: Style.spacing.controlPaddingY
property bool leftAlign: false property bool leftAlign: false
leftPadding: horizontalPadding
rightPadding: horizontalPadding
topPadding: verticalPadding
bottomPadding: verticalPadding
// Tooltip palette. Auto-rendered if tooltipText is set. Defaults pull // Tooltip palette. Auto-rendered if tooltipText is set. Defaults pull
// from [tooltip] in shell.toml; override per-instance only when a button // from [tooltip] in shell.toml; override per-instance only when a button
// intentionally wants a tooltip that diverges from the theme. // intentionally wants a tooltip that diverges from the theme.
@@ -62,13 +67,21 @@ Rectangle {
Keys.onEnterPressed: if (focusable) root.clicked() Keys.onEnterPressed: if (focusable) root.clicked()
Keys.onSpacePressed: if (focusable) root.clicked() Keys.onSpacePressed: if (focusable) root.clicked()
implicitWidth: row.implicitWidth + horizontalPadding * 2 implicitWidth: row.implicitWidth + horizontalPadding * 2 + borderLeft + borderRight
implicitHeight: row.implicitHeight + verticalPadding * 2 implicitHeight: row.implicitHeight + verticalPadding * 2 + borderTop + borderBottom
radius: Style.cornerRadius radius: Style.cornerRadius
readonly property bool hot: mouseArea.containsMouse || hasCursor readonly property bool hot: mouseArea.containsMouse || hasCursor
readonly property bool _showFocusRing: focusable && activeFocus readonly property bool _showFocusRing: focusable && activeFocus
readonly property color _selectedColor: Style.selectedStateColor(root.foreground, root.accent) readonly property color _selectedColor: Style.selectedStateColor(root.foreground, root.accent)
readonly property var _tooltipBorderSpec: Border.localOrSurfaceSpec("tooltip", "border", root.tooltipBorder, Color.tooltip.border, Math.max(1, Style.normalBorderWidth))
readonly property var _selectedBorderSpec: Border.controlSpec("selected", root.foreground, root.accent)
readonly property var _normalBorderSpec: Border.controlSpec("normal", root.foreground, root.accent)
readonly property var _borderSpec: _showFocusRing ? Border.controlSpec("focus", root.foreground, root.accent)
: hot ? Border.controlSpec("hover-cursor", root.foreground, root.accent)
: selected ? (Border.controlHasWidth("selected") ? _selectedBorderSpec : (bordered ? _normalBorderSpec : Border.none()))
: bordered ? _normalBorderSpec
: Border.none()
color: mouseArea.pressed ? Style.pressedFillFor(root.foreground, root.accent) color: mouseArea.pressed ? Style.pressedFillFor(root.foreground, root.accent)
: _showFocusRing ? Style.focusFillFor(root.foreground, root.accent) : _showFocusRing ? Style.focusFillFor(root.foreground, root.accent)
@@ -84,17 +97,7 @@ Rectangle {
// default for plain buttons; explicitly bordered buttons keep their // default for plain buttons; explicitly bordered buttons keep their
// normal border when selected unless selected-border-width opts in to a // normal border when selected unless selected-border-width opts in to a
// dedicated selected border. // dedicated selected border.
border.color: _showFocusRing ? Style.focusBorderFor(root.foreground, root.accent) borderSpec: _borderSpec
: 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"
border.width: _showFocusRing ? Style.focusBorderWidth
: hot ? Style.hoverBorderWidth
: selected ? (Style.selectedBorderWidth > 0 ? Style.selectedBorderWidth : (bordered ? Style.normalBorderWidth : 0))
: bordered ? Style.normalBorderWidth
: 0
Behavior on color { ColorAnimation { duration: 120 } } Behavior on color { ColorAnimation { duration: 120 } }
@@ -103,10 +106,9 @@ Rectangle {
text: root.tooltipText text: root.tooltipText
delay: 400 delay: 400
padding: 0 padding: 0
background: Rectangle { background: BorderSurface {
color: root.tooltipBackground color: root.tooltipBackground
border.color: root.tooltipBorder borderSpec: root._tooltipBorderSpec
border.width: Math.max(1, Style.normalBorderWidth)
radius: 0 radius: 0
} }
contentItem: Text { contentItem: Text {
@@ -114,10 +116,10 @@ Rectangle {
color: root.tooltipForeground color: root.tooltipForeground
font.family: root.fontFamily font.family: root.fontFamily
font.pixelSize: Style.font.bodySmall font.pixelSize: Style.font.bodySmall
leftPadding: Style.spacing.controlPaddingX leftPadding: Border.left(root._tooltipBorderSpec) + Style.spacing.controlPaddingX
rightPadding: Style.spacing.controlPaddingX rightPadding: Border.right(root._tooltipBorderSpec) + Style.spacing.controlPaddingX
topPadding: Style.spacing.controlPaddingY topPadding: Border.top(root._tooltipBorderSpec) + Style.spacing.controlPaddingY
bottomPadding: Style.spacing.controlPaddingY bottomPadding: Border.bottom(root._tooltipBorderSpec) + Style.spacing.controlPaddingY
} }
} }
@@ -125,7 +127,7 @@ Rectangle {
id: row id: row
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.left: root.leftAlign ? parent.left : undefined anchors.left: root.leftAlign ? parent.left : undefined
anchors.leftMargin: root.leftAlign ? root.horizontalPadding : 0 anchors.leftMargin: root.leftAlign ? root.contentLeftInset : 0
anchors.horizontalCenter: root.leftAlign ? undefined : parent.horizontalCenter anchors.horizontalCenter: root.leftAlign ? undefined : parent.horizontalCenter
spacing: Style.spacing.controlGap spacing: Style.spacing.controlGap
+11 -8
View File
@@ -46,20 +46,24 @@ Item {
MouseArea { anchors.fill: parent; onClicked: root.canceled() } MouseArea { anchors.fill: parent; onClicked: root.canceled() }
Rectangle { BorderSurface {
id: card
width: Math.min(parent.width - Style.space(96), Style.space(370)) width: Math.min(parent.width - Style.space(96), Style.space(370))
height: Style.space(132) height: Style.space(132)
anchors.centerIn: parent anchors.centerIn: parent
color: root.background color: root.background
border.color: root.selectedText borderSpec: Border.flat(root.selectedText, Style.normalBorderWidth)
border.width: Style.normalBorderWidth padding: Style.space(18)
radius: root.cornerRadius radius: root.cornerRadius
MouseArea { anchors.fill: parent; onClicked: {} } MouseArea { anchors.fill: parent; onClicked: {} }
Item { Item {
anchors.fill: parent anchors.fill: parent
anchors.margins: Style.space(18) anchors.topMargin: card.contentTopInset
anchors.rightMargin: card.contentRightInset
anchors.bottomMargin: card.contentBottomInset
anchors.leftMargin: card.contentLeftInset
Text { Text {
anchors.left: parent.left anchors.left: parent.left
@@ -80,7 +84,7 @@ Item {
Repeater { Repeater {
model: [root.cancelText, root.confirmText] model: [root.cancelText, root.confirmText]
Rectangle { BorderSurface {
required property int index required property int index
required property string modelData required property string modelData
@@ -92,10 +96,9 @@ Item {
color: selected color: selected
? (destructive ? Util.alpha(Color.urgent, 0.22) : root.selectedBackground) ? (destructive ? Util.alpha(Color.urgent, 0.22) : root.selectedBackground)
: "transparent" : "transparent"
border.color: destructive borderSpec: Border.flat(destructive
? (selected ? Color.urgent : Util.alpha(Color.urgent, 0.56)) ? (selected ? Color.urgent : Util.alpha(Color.urgent, 0.56))
: (selected ? root.selectedText : Util.alpha(root.foreground, 0.38)) : (selected ? root.selectedText : Util.alpha(root.foreground, 0.38)), Style.normalBorderWidth)
border.width: Style.normalBorderWidth
radius: 0 radius: 0
Text { Text {
+6 -12
View File
@@ -11,7 +11,7 @@ import qs.Commons
// hover-cursor border. `outline` remains as a compatibility flag for // hover-cursor border. `outline` remains as a compatibility flag for
// callers that used to request border-only rows, but slider rows still // callers that used to request border-only rows, but slider rows still
// receive the same hover-cursor background as every other row. // receive the same hover-cursor background as every other row.
Rectangle { BorderSurface {
id: root id: root
property bool hasCursor: false property bool hasCursor: false
@@ -27,19 +27,13 @@ Rectangle {
radius: Style.cornerRadius radius: Style.cornerRadius
color: hasCursor ? fill : (current ? currentFill : "transparent") color: hasCursor ? fill : (current ? currentFill : "transparent")
borderSpec: root.hasCursor
border.color: root.hasCursor ? Border.controlSpec("hover-cursor", root.foreground, root.accent)
? Style.hoverBorderFor(root.foreground, root.accent)
: (root.current : (root.current
? Style.selectedBorderFor(root.foreground, root.accent) ? Border.controlSpec("selected", root.foreground, root.accent)
: (root.bordered : (root.bordered
? Style.normalBorderFor(root.foreground, root.accent) ? Border.controlSpec("normal", root.foreground, root.accent)
: "transparent")) : Border.none()))
border.width: root.hasCursor
? Style.hoverBorderWidth
: (root.current
? Style.selectedBorderWidth
: (root.bordered ? Style.normalBorderWidth : 0))
Behavior on color { Behavior on color {
ColorAnimation { duration: 60 } ColorAnimation { duration: 60 }
+13 -9
View File
@@ -26,6 +26,7 @@ Item {
property color background: Color.popups.background property color background: Color.popups.background
property color popupBorder: Color.popups.border property color popupBorder: Color.popups.border
property color accent: Color.accent property color accent: Color.accent
readonly property var popupBorderSpec: Border.localOrSurfaceSpec("popups", "border", popupBorder, Color.popups.border, Style.normalBorderWidth)
property string fontFamily: Style.font.family property string fontFamily: Style.font.family
property int rowHeight: Style.spacing.controlHeight property int rowHeight: Style.spacing.controlHeight
property int popupRowHeight: Style.spacing.popupRowHeight property int popupRowHeight: Style.spacing.popupRowHeight
@@ -78,7 +79,7 @@ Item {
font.bold: true font.bold: true
} }
Rectangle { BorderSurface {
id: trigger id: trigger
width: parent.width width: parent.width
height: root.rowHeight height: root.rowHeight
@@ -86,10 +87,10 @@ Item {
readonly property bool _focused: trigger.activeFocus readonly property bool _focused: trigger.activeFocus
readonly property bool _hot: triggerHover.hovered || root.hasCursor readonly property bool _hot: triggerHover.hovered || root.hasCursor
readonly property var _borderSpec: Border.controlSpec(trigger._focused ? "focus" : (trigger._hot ? "hover-cursor" : "normal"), root.foreground, root.accent)
color: Style.controlFill(trigger._focused, trigger._hot, root.foreground, root.accent) color: Style.controlFill(trigger._focused, trigger._hot, root.foreground, root.accent)
border.color: Style.controlBorder(trigger._focused, trigger._hot, root.foreground, root.accent) borderSpec: _borderSpec
border.width: Style.controlBorderWidth(trigger._focused, trigger._hot)
activeFocusOnTab: true activeFocusOnTab: true
@@ -112,8 +113,8 @@ Item {
anchors.left: parent.left anchors.left: parent.left
anchors.right: chevron.left anchors.right: chevron.left
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Style.spacing.controlPaddingX anchors.leftMargin: trigger.borderLeft + Style.spacing.controlPaddingX
anchors.rightMargin: Style.spacing.md anchors.rightMargin: trigger.borderRight + Style.spacing.md
text: root.currentLabel() text: root.currentLabel()
color: root.foreground color: root.foreground
font.family: root.fontFamily font.family: root.fontFamily
@@ -125,7 +126,7 @@ Item {
id: chevron id: chevron
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: Style.spacing.controlGap anchors.rightMargin: trigger.borderRight + Style.spacing.controlGap
text: "󰅀" text: "󰅀"
color: Qt.darker(root.foreground, 1.2) color: Qt.darker(root.foreground, 1.2)
font.family: root.fontFamily font.family: root.fontFamily
@@ -149,12 +150,15 @@ Item {
implicitHeight: Math.min(root.options.length * root.popupRowHeight + Math.max(0, root.options.length - 1) * Style.spacing.labelGap + Style.spacing.xxs, 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) root.popupRowHeight * 8 + 7 * Style.spacing.labelGap + Style.spacing.xxs)
padding: Style.spacing.hairline padding: Style.spacing.hairline
leftPadding: Border.left(root.popupBorderSpec) + Style.spacing.hairline
rightPadding: Border.right(root.popupBorderSpec) + Style.spacing.hairline
topPadding: Border.top(root.popupBorderSpec) + Style.spacing.hairline
bottomPadding: Border.bottom(root.popupBorderSpec) + Style.spacing.hairline
focus: true focus: true
background: Rectangle { background: BorderSurface {
color: root.background color: root.background
border.color: root.popupBorder borderSpec: root.popupBorderSpec
border.width: Style.normalBorderWidth
radius: Style.cornerRadius radius: Style.cornerRadius
} }
+7 -4
View File
@@ -285,15 +285,15 @@ PanelWindow {
// --- card ---------------------------------------------------------------- // --- card ----------------------------------------------------------------
Rectangle { BorderSurface {
id: card id: card
x: root.cardOrigin.x x: root.cardOrigin.x
y: root.cardOrigin.y y: root.cardOrigin.y
width: root.contentWidth width: root.contentWidth
height: root.contentHeight height: root.contentHeight
color: Color.popups.background color: Color.popups.background
border.color: Color.popups.border borderSpec: Border.surfaceSpec("popups", "border", Color.popups.border, Math.max(1, Style.space(2)))
border.width: Math.max(1, Style.space(2)) padding: root.padding
radius: Style.cornerRadius radius: Style.cornerRadius
opacity: root.open || root.popoutSwitching ? 1.0 : 0 opacity: root.open || root.popoutSwitching ? 1.0 : 0
@@ -309,7 +309,10 @@ PanelWindow {
Item { Item {
id: contentHolder id: contentHolder
anchors.fill: parent anchors.fill: parent
anchors.margins: root.padding anchors.topMargin: card.contentTopInset
anchors.rightMargin: card.contentRightInset
anchors.bottomMargin: card.contentBottomInset
anchors.leftMargin: card.contentLeftInset
opacity: root.popoutSwitching ? (root.open ? 1.0 : 0) : 1.0 opacity: root.popoutSwitching ? (root.open ? 1.0 : 0) : 1.0
Behavior on opacity { Behavior on opacity {
+21 -19
View File
@@ -37,6 +37,7 @@ Item {
property color background: Color.popups.background property color background: Color.popups.background
property color popupBorder: Color.popups.border property color popupBorder: Color.popups.border
property color accent: Color.accent property color accent: Color.accent
readonly property var popupBorderSpec: Border.localOrSurfaceSpec("popups", "border", popupBorder, Color.popups.border, Style.normalBorderWidth)
property string fontFamily: Style.font.family property string fontFamily: Style.font.family
property int rowHeight: Style.spacing.controlHeight property int rowHeight: Style.spacing.controlHeight
property int popupRowHeight: Style.spacing.popupRowHeight property int popupRowHeight: Style.spacing.popupRowHeight
@@ -266,7 +267,7 @@ Item {
font.bold: true font.bold: true
} }
Rectangle { BorderSurface {
id: trigger id: trigger
width: parent.width width: parent.width
height: root.rowHeight height: root.rowHeight
@@ -274,10 +275,10 @@ Item {
readonly property bool _focused: trigger.activeFocus readonly property bool _focused: trigger.activeFocus
readonly property bool _hot: triggerHover.hovered || root.hasCursor readonly property bool _hot: triggerHover.hovered || root.hasCursor
readonly property var _borderSpec: Border.controlSpec(trigger._focused ? "focus" : (trigger._hot ? "hover-cursor" : "normal"), root.foreground, root.accent)
color: Style.controlFill(trigger._focused, trigger._hot, root.foreground, root.accent) color: Style.controlFill(trigger._focused, trigger._hot, root.foreground, root.accent)
border.color: Style.controlBorder(trigger._focused, trigger._hot, root.foreground, root.accent) borderSpec: _borderSpec
border.width: Style.controlBorderWidth(trigger._focused, trigger._hot)
activeFocusOnTab: true activeFocusOnTab: true
@@ -300,8 +301,8 @@ Item {
anchors.left: parent.left anchors.left: parent.left
anchors.right: chevron.left anchors.right: chevron.left
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Style.spacing.controlPaddingX anchors.leftMargin: trigger.borderLeft + Style.spacing.controlPaddingX
anchors.rightMargin: Style.spacing.md anchors.rightMargin: trigger.borderRight + Style.spacing.md
text: root.selectionLabel() || root.triggerLabel || root.noSelectionText text: root.selectionLabel() || root.triggerLabel || root.noSelectionText
color: root.selectionLabel() ? root.foreground : Qt.darker(root.foreground, 1.5) color: root.selectionLabel() ? root.foreground : Qt.darker(root.foreground, 1.5)
font.family: root.fontFamily font.family: root.fontFamily
@@ -313,7 +314,7 @@ Item {
id: chevron id: chevron
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: Style.spacing.controlGap anchors.rightMargin: trigger.borderRight + Style.spacing.controlGap
text: "󰅀" text: "󰅀"
color: Qt.darker(root.foreground, 1.2) color: Qt.darker(root.foreground, 1.2)
font.family: root.fontFamily font.family: root.fontFamily
@@ -359,6 +360,10 @@ Item {
// when there isn't room, otherwise the popup overflows the window. // when there isn't room, otherwise the popup overflows the window.
implicitHeight: Math.min(_availableBelow, _idealContent, _maxRowsHeight) implicitHeight: Math.min(_availableBelow, _idealContent, _maxRowsHeight)
padding: Style.spacing.hairline padding: Style.spacing.hairline
leftPadding: Border.left(root.popupBorderSpec) + Style.spacing.hairline
rightPadding: Border.right(root.popupBorderSpec) + Style.spacing.hairline
topPadding: Border.top(root.popupBorderSpec) + Style.spacing.hairline
bottomPadding: Border.bottom(root.popupBorderSpec) + Style.spacing.hairline
focus: true focus: true
Connections { Connections {
@@ -369,10 +374,9 @@ Item {
function onHeightChanged() { popup.reposition() } function onHeightChanged() { popup.reposition() }
} }
background: Rectangle { background: BorderSurface {
color: root.background color: root.background
border.color: root.popupBorder borderSpec: root.popupBorderSpec
border.width: Style.normalBorderWidth
radius: Style.cornerRadius radius: Style.cornerRadius
} }
@@ -432,7 +436,7 @@ Item {
} }
} }
Rectangle { BorderSurface {
id: refreshButton id: refreshButton
visible: root.arrayFrom(root.optionsCommand).length > 0 visible: root.arrayFrom(root.optionsCommand).length > 0
enabled: !root.loadingOptions enabled: !root.loadingOptions
@@ -442,10 +446,9 @@ Item {
color: refreshHover.hovered color: refreshHover.hovered
? Style.hoverFillFor(root.foreground, root.accent) ? Style.hoverFillFor(root.foreground, root.accent)
: Style.normalFillFor(root.foreground, root.accent) : Style.normalFillFor(root.foreground, root.accent)
border.color: refreshHover.hovered borderSpec: refreshHover.hovered
? Style.hoverBorderFor(root.foreground, root.accent) ? Border.controlSpec("hover-cursor", root.foreground, root.accent)
: Style.normalBorderFor(root.foreground, root.accent) : Border.controlSpec("normal", root.foreground, root.accent)
border.width: refreshHover.hovered ? Style.hoverBorderWidth : Style.normalBorderWidth
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
@@ -550,17 +553,16 @@ Item {
anchors.rightMargin: Style.spacing.controlPaddingX anchors.rightMargin: Style.spacing.controlPaddingX
spacing: Style.spacing.rowGap spacing: Style.spacing.rowGap
Rectangle { BorderSurface {
id: checkbox id: checkbox
width: Style.space(16) width: Style.space(16)
height: Style.space(16) height: Style.space(16)
radius: Math.max(2, Style.cornerRadius / 2) radius: Math.max(2, Style.cornerRadius / 2)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: selected ? Style.selectedFillFor(root.foreground, root.accent) : "transparent" color: selected ? Style.selectedFillFor(root.foreground, root.accent) : "transparent"
border.color: selected borderSpec: selected
? Style.selectedBorderFor(root.foreground, root.accent) ? Border.controlSpec("selected", root.foreground, root.accent)
: Style.normalBorderFor(root.foreground, root.accent) : Border.controlSpec("normal", root.foreground, root.accent)
border.width: selected ? Style.selectedBorderWidth : Style.normalBorderWidth
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
+12 -7
View File
@@ -44,15 +44,20 @@ Column {
font.family: root.fontFamily font.family: root.fontFamily
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
readonly property bool _focused: spin.activeFocus
readonly property bool _hot: root._hovered || root.hasCursor
readonly property var _borderSpec: Border.controlSpec(_focused ? "focus" : (_hot ? "hover-cursor" : "normal"), root.foreground, root.accent)
leftPadding: Border.left(_borderSpec) + Style.spacing.controlPaddingX
rightPadding: Border.right(_borderSpec) + Style.spacing.controlPaddingX
topPadding: Border.top(_borderSpec)
bottomPadding: Border.bottom(_borderSpec)
onValueModified: root.modified(value) onValueModified: root.modified(value)
background: Rectangle { background: BorderSurface {
readonly property bool _focused: spin.activeFocus color: Style.controlFill(spin._focused, spin._hot, root.foreground, root.accent)
readonly property bool _hot: root._hovered || root.hasCursor borderSpec: spin._borderSpec
color: Style.controlFill(_focused, _hot, root.foreground, root.accent)
border.color: Style.controlBorder(_focused, _hot, root.foreground, root.accent)
border.width: Style.controlBorderWidth(_focused, _hot)
radius: Style.cornerRadius radius: Style.cornerRadius
HoverHandler { HoverHandler {
+7 -11
View File
@@ -24,7 +24,7 @@ import qs.Commons
// Use this when a PanelActionButton is itself the cursor target (rather // Use this when a PanelActionButton is itself the cursor target (rather
// than living inside a CursorSurface row). Emits `hovered(bool)` on // than living inside a CursorSurface row). Emits `hovered(bool)` on
// pointer enter/leave so the panel can update its cursor state to match. // pointer enter/leave so the panel can update its cursor state to match.
Rectangle { BorderSurface {
id: root id: root
property string iconText: "" property string iconText: ""
@@ -53,22 +53,18 @@ Rectangle {
readonly property bool _showFocusRing: focusable && activeFocus readonly property bool _showFocusRing: focusable && activeFocus
readonly property bool _hot: (mouse.containsMouse || root.hasCursor) && root.enabled readonly property bool _hot: (mouse.containsMouse || root.hasCursor) && root.enabled
readonly property var _borderSpec: _showFocusRing
? Border.controlSpec("focus", hoverColor, hoverColor)
: (_hot && bordered
? Border.controlSpec("hover-cursor", hoverColor, hoverColor)
: (bordered ? Border.controlSpec("normal", foreground, Color.accent) : Border.none()))
color: _showFocusRing color: _showFocusRing
? Style.focusFillFor(hoverColor, hoverColor) ? Style.focusFillFor(hoverColor, hoverColor)
: (_hot : (_hot
? Style.hoverFillFor(hoverColor, hoverColor) ? Style.hoverFillFor(hoverColor, hoverColor)
: "transparent") : "transparent")
border.width: _showFocusRing borderSpec: _borderSpec
? 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 } } Behavior on color { ColorAnimation { duration: 60 } }
+2 -3
View File
@@ -56,15 +56,14 @@ Item {
height: 1 height: 1
} }
Rectangle { BorderSurface {
id: detailPill id: detailPill
visible: root.detail !== "" visible: root.detail !== ""
implicitWidth: detailText.implicitWidth + Style.space(10) implicitWidth: detailText.implicitWidth + Style.space(10)
implicitHeight: detailText.implicitHeight + Style.space(4) implicitHeight: detailText.implicitHeight + Style.space(4)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: "transparent" color: "transparent"
border.color: Style.normalBorderFor(root.foreground) borderSpec: Border.controlSpec("normal", root.foreground, Color.accent)
border.width: Style.normalBorderWidth
radius: Style.cornerRadius radius: Style.cornerRadius
Text { Text {
+2 -3
View File
@@ -55,14 +55,13 @@ Item {
} }
} }
Rectangle { BorderSurface {
id: knob id: knob
width: root.knobSize width: root.knobSize
height: root.knobSize height: root.knobSize
radius: root.knobSize / 2 radius: root.knobSize / 2
color: root.knobColor color: root.knobColor
border.color: root.bar ? root.bar.background : "#101315" borderSpec: Border.flat(root.bar ? root.bar.background : "#101315", Math.max(1, Style.space(2)))
border.width: Math.max(1, Style.space(2))
anchors.verticalCenter: track.verticalCenter anchors.verticalCenter: track.verticalCenter
x: Math.max(0, Math.min(track.width - width, track.width * root.progress - width / 2)) x: Math.max(0, Math.min(track.width - width, track.width * root.progress - width / 2))
scale: root._hot ? 1.15 : 1.0 scale: root._hot ? 1.15 : 1.0
+8 -7
View File
@@ -24,13 +24,14 @@ ToolTip {
property string fontFamily: Style.font.family property string fontFamily: Style.font.family
property real fontSize: Style.font.bodySmall property real fontSize: Style.font.bodySmall
readonly property var panelBorderSpec: Border.localOrSurfaceSpec("tooltip", "border", panelBorder, Color.tooltip.border, Style.normalBorderWidth)
delay: 400 delay: 400
padding: 0 padding: 0
background: Rectangle { background: BorderSurface {
color: root.panelBackground color: root.panelBackground
border.color: root.panelBorder borderSpec: root.panelBorderSpec
border.width: Style.normalBorderWidth
radius: Style.cornerRadius radius: Style.cornerRadius
} }
@@ -39,9 +40,9 @@ ToolTip {
color: root.panelForeground color: root.panelForeground
font.family: root.fontFamily font.family: root.fontFamily
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
leftPadding: Style.spacing.controlPaddingX leftPadding: Border.left(root.panelBorderSpec) + Style.spacing.controlPaddingX
rightPadding: Style.spacing.controlPaddingX rightPadding: Border.right(root.panelBorderSpec) + Style.spacing.controlPaddingX
topPadding: Style.spacing.controlPaddingY topPadding: Border.top(root.panelBorderSpec) + Style.spacing.controlPaddingY
bottomPadding: Style.spacing.controlPaddingY bottomPadding: Border.bottom(root.panelBorderSpec) + Style.spacing.controlPaddingY
} }
} }
+8 -4
View File
@@ -14,6 +14,7 @@ PopupWindow {
property int contentWidth: Style.space(280) property int contentWidth: Style.space(280)
property int contentHeight: Style.space(200) property int contentHeight: Style.space(200)
property color borderColor: Color.popups.border property color borderColor: Color.popups.border
property var borderSpec: Border.localOrSurfaceSpec("popups", "border", borderColor, Color.popups.border, Math.max(1, Style.space(2)))
property bool open: false property bool open: false
property bool centerOnBar: false property bool centerOnBar: false
// "click" — uses HyprlandFocusGrab so clicking outside dismisses the popup. // "click" — uses HyprlandFocusGrab so clicking outside dismisses the popup.
@@ -145,12 +146,12 @@ PopupWindow {
} }
} }
Rectangle { BorderSurface {
id: card id: card
anchors.fill: parent anchors.fill: parent
color: Color.popups.background color: Color.popups.background
border.color: root.borderColor borderSpec: root.borderSpec
border.width: Math.max(1, Style.space(2)) padding: root.padding
radius: Style.cornerRadius radius: Style.cornerRadius
opacity: root.open ? 1.0 : 0 opacity: root.open ? 1.0 : 0
@@ -161,7 +162,10 @@ PopupWindow {
Item { Item {
id: contentHolder id: contentHolder
anchors.fill: parent anchors.fill: parent
anchors.margins: root.padding anchors.topMargin: card.contentTopInset
anchors.rightMargin: card.contentRightInset
anchors.bottomMargin: card.contentBottomInset
anchors.leftMargin: card.contentLeftInset
} }
HoverHandler { HoverHandler {
+13 -9
View File
@@ -29,6 +29,7 @@ Item {
property color background: Color.popups.background property color background: Color.popups.background
property color popupBorder: Color.popups.border property color popupBorder: Color.popups.border
property color accent: Color.accent property color accent: Color.accent
readonly property var popupBorderSpec: Border.localOrSurfaceSpec("popups", "border", popupBorder, Color.popups.border, Style.normalBorderWidth)
property string fontFamily: Style.font.family property string fontFamily: Style.font.family
property int rowHeight: Style.spacing.controlHeight property int rowHeight: Style.spacing.controlHeight
property int popupRowHeight: Style.spacing.popupRowHeight property int popupRowHeight: Style.spacing.popupRowHeight
@@ -100,7 +101,7 @@ Item {
font.bold: true font.bold: true
} }
Rectangle { BorderSurface {
id: trigger id: trigger
width: parent.width width: parent.width
height: root.rowHeight height: root.rowHeight
@@ -108,10 +109,10 @@ Item {
readonly property bool _focused: trigger.activeFocus readonly property bool _focused: trigger.activeFocus
readonly property bool _hot: triggerHover.hovered || root.hasCursor readonly property bool _hot: triggerHover.hovered || root.hasCursor
readonly property var _borderSpec: Border.controlSpec(trigger._focused ? "focus" : (trigger._hot ? "hover-cursor" : "normal"), root.foreground, root.accent)
color: Style.controlFill(trigger._focused, trigger._hot, root.foreground, root.accent) color: Style.controlFill(trigger._focused, trigger._hot, root.foreground, root.accent)
border.color: Style.controlBorder(trigger._focused, trigger._hot, root.foreground, root.accent) borderSpec: _borderSpec
border.width: Style.controlBorderWidth(trigger._focused, trigger._hot)
activeFocusOnTab: true activeFocusOnTab: true
@@ -134,8 +135,8 @@ Item {
anchors.left: parent.left anchors.left: parent.left
anchors.right: chevron.left anchors.right: chevron.left
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Style.spacing.controlPaddingX anchors.leftMargin: trigger.borderLeft + Style.spacing.controlPaddingX
anchors.rightMargin: Style.spacing.md anchors.rightMargin: trigger.borderRight + Style.spacing.md
text: root.currentLabel() || root.triggerLabel || root.placeholderText text: root.currentLabel() || root.triggerLabel || root.placeholderText
color: (root.currentLabel() || root.triggerLabel) ? root.foreground : Qt.darker(root.foreground, 1.5) color: (root.currentLabel() || root.triggerLabel) ? root.foreground : Qt.darker(root.foreground, 1.5)
font.family: root.fontFamily font.family: root.fontFamily
@@ -147,7 +148,7 @@ Item {
id: chevron id: chevron
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: Style.spacing.controlGap anchors.rightMargin: trigger.borderRight + Style.spacing.controlGap
text: "󰅀" text: "󰅀"
color: Qt.darker(root.foreground, 1.2) color: Qt.darker(root.foreground, 1.2)
font.family: root.fontFamily font.family: root.fontFamily
@@ -172,12 +173,15 @@ Item {
Math.min(resultList.contentHeight + Style.space(50), Math.min(resultList.contentHeight + Style.space(50),
root.popupRowHeight * 6 + 5 * Style.spacing.labelGap + Style.space(50))) root.popupRowHeight * 6 + 5 * Style.spacing.labelGap + Style.space(50)))
padding: Style.spacing.hairline padding: Style.spacing.hairline
leftPadding: Border.left(root.popupBorderSpec) + Style.spacing.hairline
rightPadding: Border.right(root.popupBorderSpec) + Style.spacing.hairline
topPadding: Border.top(root.popupBorderSpec) + Style.spacing.hairline
bottomPadding: Border.bottom(root.popupBorderSpec) + Style.spacing.hairline
focus: true focus: true
background: Rectangle { background: BorderSurface {
color: root.background color: root.background
border.color: root.popupBorder borderSpec: root.popupBorderSpec
border.width: Style.normalBorderWidth
radius: Style.cornerRadius radius: Style.cornerRadius
} }
+7 -7
View File
@@ -34,6 +34,7 @@ TextField {
readonly property bool _focused: activeFocus readonly property bool _focused: activeFocus
readonly property bool _hot: hovered || hasCursor readonly property bool _hot: hovered || hasCursor
readonly property var _borderSpec: Border.controlSpec(_focused ? "focus" : (_hot ? "hover-cursor" : "normal"), root.foreground, root.accent)
echoMode: password ? TextInput.Password : TextInput.Normal echoMode: password ? TextInput.Password : TextInput.Normal
font.family: Style.font.family font.family: Style.font.family
@@ -43,15 +44,14 @@ TextField {
selectedTextColor: foreground selectedTextColor: foreground
placeholderTextColor: Qt.darker(foreground, 1.6) placeholderTextColor: Qt.darker(foreground, 1.6)
leftPadding: horizontalPadding leftPadding: horizontalPadding + Border.left(_borderSpec)
rightPadding: horizontalPadding rightPadding: horizontalPadding + Border.right(_borderSpec)
topPadding: verticalPadding topPadding: verticalPadding + Border.top(_borderSpec)
bottomPadding: verticalPadding bottomPadding: verticalPadding + Border.bottom(_borderSpec)
background: Rectangle { background: BorderSurface {
color: Style.controlFill(root._focused, root._hot, root.foreground, root.accent) color: Style.controlFill(root._focused, root._hot, root.foreground, root.accent)
border.color: Style.controlBorder(root._focused, root._hot, root.foreground, root.accent) borderSpec: root._borderSpec
border.width: Style.controlBorderWidth(root._focused, root._hot)
radius: Style.cornerRadius radius: Style.cornerRadius
} }
} }
+9 -10
View File
@@ -12,7 +12,7 @@ import qs.Commons
// `rounded` auto-detects from Style.cornerRadius so the switch follows // `rounded` auto-detects from Style.cornerRadius so the switch follows
// the theme: pill shape when Hyprland corners are rounded, square on sharp. // the theme: pill shape when Hyprland corners are rounded, square on sharp.
// Callers can override per-instance. // Callers can override per-instance.
Rectangle { BorderSurface {
id: root id: root
property string label: "" property string label: ""
@@ -52,10 +52,10 @@ Rectangle {
radius: Style.cornerRadius radius: Style.cornerRadius
readonly property bool _hot: hasCursor || mouse.containsMouse readonly property bool _hot: hasCursor || mouse.containsMouse
readonly property var _borderSpec: Border.controlSpec(activeFocus ? "focus" : (_hot ? "hover-cursor" : "normal"), foreground, accent)
color: Style.controlFill(activeFocus, _hot, foreground, accent) color: Style.controlFill(activeFocus, _hot, foreground, accent)
border.color: Style.controlBorder(activeFocus, _hot, foreground, accent) borderSpec: _borderSpec
border.width: Style.controlBorderWidth(activeFocus, _hot)
Behavior on color { ColorAnimation { duration: 100 } } Behavior on color { ColorAnimation { duration: 100 } }
@@ -64,8 +64,8 @@ Rectangle {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Style.spacing.rowPaddingX anchors.leftMargin: root.borderLeft + Style.spacing.rowPaddingX
anchors.rightMargin: Style.spacing.rowPaddingX anchors.rightMargin: root.borderRight + Style.spacing.rowPaddingX
spacing: Style.spacing.rowPaddingX spacing: Style.spacing.rowPaddingX
Column { Column {
@@ -94,7 +94,7 @@ Rectangle {
} }
} }
Rectangle { BorderSurface {
id: track id: track
width: root.trackWidth width: root.trackWidth
height: root.trackHeight height: root.trackHeight
@@ -102,10 +102,9 @@ Rectangle {
color: root.checked color: root.checked
? Style.selectedFillFor(root.foreground, root.accent) ? Style.selectedFillFor(root.foreground, root.accent)
: Style.normalFillFor(root.foreground, root.accent) : Style.normalFillFor(root.foreground, root.accent)
border.color: root.checked borderSpec: root.checked
? Style.selectedBorderFor(root.foreground, root.accent) ? Border.controlSpec("selected", root.foreground, root.accent)
: Style.normalBorderFor(root.foreground, root.accent) : Border.controlSpec("normal", root.foreground, root.accent)
border.width: root.checked ? Style.selectedBorderWidth : Style.normalBorderWidth
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Behavior on color { ColorAnimation { duration: 120 } } Behavior on color { ColorAnimation { duration: 120 } }
+2
View File
@@ -2,6 +2,8 @@ module qs.Ui
BarIndicator 1.0 BarIndicator.qml BarIndicator 1.0 BarIndicator.qml
BarWidget 1.0 BarWidget.qml BarWidget 1.0 BarWidget.qml
BorderOverlay 1.0 BorderOverlay.qml
BorderSurface 1.0 BorderSurface.qml
Button 1.0 Button.qml Button 1.0 Button.qml
ButtonGroup 1.0 ButtonGroup.qml ButtonGroup 1.0 ButtonGroup.qml
ConfirmDialog 1.0 ConfirmDialog.qml ConfirmDialog 1.0 ConfirmDialog.qml
+6 -9
View File
@@ -798,13 +798,12 @@ Item {
} }
} }
Rectangle { BorderSurface {
id: tooltipBubble id: tooltipBubble
implicitWidth: tooltipLabel.implicitWidth + 20 implicitWidth: tooltipLabel.implicitWidth + 20
implicitHeight: tooltipLabel.implicitHeight + 14 implicitHeight: tooltipLabel.implicitHeight + 14
color: Color.tooltip.background color: Color.tooltip.background
border.color: Color.tooltip.border borderSpec: Border.surfaceSpec("tooltip", "border", Color.tooltip.border, 1)
border.width: 1
radius: Style.cornerRadius radius: Style.cornerRadius
Text { Text {
@@ -904,11 +903,10 @@ Item {
width: ghostWindow.ghostWidth + ghostWindow.ghostPadding * 2 width: ghostWindow.ghostWidth + ghostWindow.ghostPadding * 2
height: ghostWindow.ghostHeight + ghostWindow.ghostPadding * 2 height: ghostWindow.ghostHeight + ghostWindow.ghostPadding * 2
Rectangle { BorderSurface {
anchors.fill: parent anchors.fill: parent
color: root.transparent ? "transparent" : root.background color: root.transparent ? "transparent" : root.background
border.color: root.barForeground borderSpec: Border.flat(root.barForeground, 1)
border.width: 1
radius: Math.min(Style.cornerRadius, height / 2) radius: Math.min(Style.cornerRadius, height / 2)
opacity: root.transparent ? 0.45 : 0.94 opacity: root.transparent ? 0.45 : 0.94
} }
@@ -1234,13 +1232,12 @@ Item {
HoverHandler { id: moduleHover } HoverHandler { id: moduleHover }
Rectangle { BorderSurface {
visible: slot.dragSource visible: slot.dragSource
anchors.fill: parent anchors.fill: parent
anchors.margins: Style.space(1) anchors.margins: Style.space(1)
color: root.transparent ? "transparent" : root.background color: root.transparent ? "transparent" : root.background
border.color: root.barForeground borderSpec: Border.flat(root.barForeground, 1)
border.width: 1
radius: Math.min(Style.cornerRadius, height / 2) radius: Math.min(Style.cornerRadius, height / 2)
opacity: root.transparent ? 0.22 : 0.32 opacity: root.transparent ? 0.22 : 0.32
} }
+8 -6
View File
@@ -26,6 +26,7 @@ Item {
property color background: Color.menu.background property color background: Color.menu.background
property color foreground: Color.menu.text property color foreground: Color.menu.text
property color border: Color.menu.border property color border: Color.menu.border
property var borderSpec: Border.surfaceSpec("menu", "border", border, Math.max(1, Style.space(2)))
property color scrim: Color.menu.scrim property color scrim: Color.menu.scrim
property color selectedBackground: Color.menu.selectedBackground property color selectedBackground: Color.menu.selectedBackground
property color selectedText: Color.menu.selectedText property color selectedText: Color.menu.selectedText
@@ -275,15 +276,15 @@ Item {
onClicked: root.close() onClicked: root.close()
} }
Rectangle { BorderSurface {
id: card id: card
width: root.cardWidth width: root.cardWidth
height: root.cardHeight height: root.cardHeight
radius: root.cornerRadius radius: root.cornerRadius
anchors.centerIn: parent anchors.centerIn: parent
color: root.background color: root.background
border.color: root.border borderSpec: root.borderSpec
border.width: Math.max(1, Style.space(2)) padding: root.contentMargin
MouseArea { anchors.fill: parent; onClicked: {} } MouseArea { anchors.fill: parent; onClicked: {} }
@@ -357,7 +358,10 @@ Item {
Column { Column {
anchors.fill: parent anchors.fill: parent
anchors.margins: root.contentMargin anchors.topMargin: card.contentTopInset
anchors.rightMargin: card.contentRightInset
anchors.bottomMargin: card.contentBottomInset
anchors.leftMargin: card.contentLeftInset
spacing: root.contentSpacing spacing: root.contentSpacing
Rectangle { Rectangle {
@@ -365,7 +369,6 @@ Item {
height: root.headerHeight height: root.headerHeight
radius: root.cornerRadius radius: root.cornerRadius
color: "transparent" color: "transparent"
border.width: 0
Text { Text {
anchors.left: parent.left anchors.left: parent.left
@@ -415,7 +418,6 @@ Item {
height: root.rowHeight height: root.rowHeight
radius: root.cornerRadius radius: root.cornerRadius
color: hasCursor ? root.selectedBackground : "transparent" color: hasCursor ? root.selectedBackground : "transparent"
border.width: 0
Row { Row {
anchors.fill: parent anchors.fill: parent
+30 -44
View File
@@ -376,13 +376,12 @@ Item {
font.bold: true font.bold: true
} }
Rectangle { BorderSurface {
width: parent.width width: parent.width
implicitHeight: conventionsCol.implicitHeight + Style.spacing.rowPaddingX * 2 implicitHeight: conventionsCol.implicitHeight + Style.spacing.rowPaddingX * 2
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04) color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04)
radius: Style.cornerRadius radius: Style.cornerRadius
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10) borderSpec: Border.flat(Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10), 1)
border.width: 1
Column { Column {
id: conventionsCol id: conventionsCol
@@ -458,13 +457,12 @@ Item {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
Rectangle { BorderSurface {
width: parent.width width: parent.width
implicitHeight: typeCol.implicitHeight + Style.spacing.rowPaddingX * 2 implicitHeight: typeCol.implicitHeight + Style.spacing.rowPaddingX * 2
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04) color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04)
radius: Style.cornerRadius radius: Style.cornerRadius
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10) borderSpec: Border.flat(Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10), 1)
border.width: 1
Column { Column {
id: typeCol id: typeCol
@@ -676,13 +674,12 @@ Item {
font.pixelSize: Style.font.caption font.pixelSize: Style.font.caption
} }
Rectangle { BorderSurface {
width: parent.width width: parent.width
implicitHeight: shCol.implicitHeight + Style.spacing.rowPaddingX * 2 implicitHeight: shCol.implicitHeight + Style.spacing.rowPaddingX * 2
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04) color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04)
radius: Style.cornerRadius radius: Style.cornerRadius
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10) borderSpec: Border.flat(Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10), 1)
border.width: 1
Column { Column {
id: shCol id: shCol
@@ -732,13 +729,12 @@ Item {
font.pixelSize: Style.font.caption font.pixelSize: Style.font.caption
} }
Rectangle { BorderSurface {
width: parent.width width: parent.width
implicitHeight: sepCol.implicitHeight + Style.spacing.rowPaddingX * 2 implicitHeight: sepCol.implicitHeight + Style.spacing.rowPaddingX * 2
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04) color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04)
radius: Style.cornerRadius radius: Style.cornerRadius
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10) borderSpec: Border.flat(Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10), 1)
border.width: 1
Column { Column {
id: sepCol id: sepCol
@@ -777,13 +773,12 @@ Item {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
Rectangle { BorderSurface {
width: parent.width width: parent.width
implicitHeight: csCol.implicitHeight + Style.spacing.rowPaddingX * 2 implicitHeight: csCol.implicitHeight + Style.spacing.rowPaddingX * 2
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04) color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04)
radius: Style.cornerRadius radius: Style.cornerRadius
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10) borderSpec: Border.flat(Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10), 1)
border.width: 1
Column { Column {
id: csCol id: csCol
@@ -862,13 +857,12 @@ Item {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
Rectangle { BorderSurface {
width: parent.width width: parent.width
implicitHeight: buttonRow.implicitHeight + Style.spacing.rowPaddingX * 2 implicitHeight: buttonRow.implicitHeight + Style.spacing.rowPaddingX * 2
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04) color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04)
radius: Style.cornerRadius radius: Style.cornerRadius
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10) borderSpec: Border.flat(Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10), 1)
border.width: 1
Row { Row {
id: buttonRow id: buttonRow
@@ -1019,13 +1013,12 @@ Item {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
Rectangle { BorderSurface {
width: parent.width width: parent.width
implicitHeight: choiceRow.implicitHeight + Style.spacing.rowPaddingX * 2 implicitHeight: choiceRow.implicitHeight + Style.spacing.rowPaddingX * 2
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04) color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04)
radius: Style.cornerRadius radius: Style.cornerRadius
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10) borderSpec: Border.flat(Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10), 1)
border.width: 1
ButtonGroup { ButtonGroup {
id: choiceRow id: choiceRow
@@ -1070,13 +1063,12 @@ Item {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
Rectangle { BorderSurface {
width: parent.width width: parent.width
implicitHeight: pabCol.implicitHeight + Style.spacing.rowPaddingX * 2 implicitHeight: pabCol.implicitHeight + Style.spacing.rowPaddingX * 2
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04) color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04)
radius: Style.cornerRadius radius: Style.cornerRadius
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10) borderSpec: Border.flat(Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10), 1)
border.width: 1
Row { Row {
id: pabCol id: pabCol
@@ -1162,7 +1154,7 @@ Item {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
Rectangle { BorderSurface {
id: tipSwatch id: tipSwatch
width: Style.space(140) width: Style.space(140)
height: Style.space(36) height: Style.space(36)
@@ -1170,10 +1162,9 @@ Item {
color: tipMouse.containsMouse || focused color: tipMouse.containsMouse || focused
? Style.hoverFillFor(root.foreground, root.accent) ? Style.hoverFillFor(root.foreground, root.accent)
: Style.normalFillFor(root.foreground, root.accent) : Style.normalFillFor(root.foreground, root.accent)
border.color: focused borderSpec: focused
? Style.hoverBorderFor(root.foreground, root.accent) ? Border.controlSpec("hover-cursor", root.foreground, root.accent)
: Style.normalBorderFor(root.foreground, root.accent) : Border.controlSpec("normal", root.foreground, root.accent)
border.width: focused ? Style.hoverBorderWidth : Style.normalBorderWidth
radius: Style.cornerRadius radius: Style.cornerRadius
onFocusedChanged: if (focused) root.ensureCursorVisible(this) onFocusedChanged: if (focused) root.ensureCursorVisible(this)
@@ -1306,13 +1297,12 @@ Item {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
Rectangle { BorderSurface {
width: parent.width width: parent.width
implicitHeight: tfCol.implicitHeight + Style.spacing.rowPaddingX * 2 implicitHeight: tfCol.implicitHeight + Style.spacing.rowPaddingX * 2
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04) color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04)
radius: Style.cornerRadius radius: Style.cornerRadius
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10) borderSpec: Border.flat(Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10), 1)
border.width: 1
Column { Column {
id: tfCol id: tfCol
@@ -1390,12 +1380,11 @@ Item {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
Rectangle { BorderSurface {
width: parent.width width: parent.width
implicitHeight: numberDemo.implicitHeight + Style.spacing.rowPaddingX * 2 implicitHeight: numberDemo.implicitHeight + Style.spacing.rowPaddingX * 2
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04) 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) borderSpec: Border.flat(Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.12), 1)
border.width: 1
radius: Style.cornerRadius radius: Style.cornerRadius
NumberField { NumberField {
@@ -1502,13 +1491,12 @@ Item {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
Rectangle { BorderSurface {
width: parent.width width: parent.width
implicitHeight: ddCol.implicitHeight + Style.spacing.rowPaddingX * 2 implicitHeight: ddCol.implicitHeight + Style.spacing.rowPaddingX * 2
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04) color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04)
radius: Style.cornerRadius radius: Style.cornerRadius
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10) borderSpec: Border.flat(Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10), 1)
border.width: 1
Column { Column {
id: ddCol id: ddCol
@@ -1558,13 +1546,12 @@ Item {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
Rectangle { BorderSurface {
width: parent.width width: parent.width
implicitHeight: sddCol.implicitHeight + Style.spacing.rowPaddingX * 2 implicitHeight: sddCol.implicitHeight + Style.spacing.rowPaddingX * 2
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04) color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04)
radius: Style.cornerRadius radius: Style.cornerRadius
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10) borderSpec: Border.flat(Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10), 1)
border.width: 1
Column { Column {
id: sddCol id: sddCol
@@ -1630,13 +1617,12 @@ Item {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
Rectangle { BorderSurface {
width: parent.width width: parent.width
implicitHeight: composedCol.implicitHeight + Style.spacing.rowPaddingX * 2 implicitHeight: composedCol.implicitHeight + Style.spacing.rowPaddingX * 2
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04) color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04)
radius: Style.cornerRadius radius: Style.cornerRadius
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10) borderSpec: Border.flat(Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10), 1)
border.width: 1
Column { Column {
id: composedCol id: composedCol
+9 -6
View File
@@ -3,6 +3,7 @@ import Quickshell.Io
import Quickshell.Wayland import Quickshell.Wayland
import QtQuick import QtQuick
import qs.Commons import qs.Commons
import qs.Ui
import "EmojiSearch.js" as EmojiSearch import "EmojiSearch.js" as EmojiSearch
Item { Item {
@@ -25,6 +26,7 @@ Item {
property color background: Color.menu.background property color background: Color.menu.background
property color foreground: Color.menu.text property color foreground: Color.menu.text
property color border: Color.menu.border property color border: Color.menu.border
property var borderSpec: Border.surfaceSpec("menu", "border", border, Math.max(1, Style.space(2)))
property color scrim: Color.menu.scrim property color scrim: Color.menu.scrim
property color selectedBackground: Color.menu.selectedBackground property color selectedBackground: Color.menu.selectedBackground
property color selectedText: Color.menu.selectedText property color selectedText: Color.menu.selectedText
@@ -175,15 +177,15 @@ Item {
onClicked: root.dismiss() onClicked: root.dismiss()
} }
Rectangle { BorderSurface {
id: card id: card
width: root.cardWidth width: root.cardWidth
height: root.cardHeight height: root.cardHeight
radius: root.cornerRadius radius: root.cornerRadius
anchors.centerIn: parent anchors.centerIn: parent
color: root.background color: root.background
border.color: root.border borderSpec: root.borderSpec
border.width: Math.max(1, Style.space(2)) padding: root.contentMargin
MouseArea { anchors.fill: parent; onClicked: {} } MouseArea { anchors.fill: parent; onClicked: {} }
@@ -232,7 +234,10 @@ Item {
Column { Column {
anchors.fill: parent anchors.fill: parent
anchors.margins: root.contentMargin anchors.topMargin: card.contentTopInset
anchors.rightMargin: card.contentRightInset
anchors.bottomMargin: card.contentBottomInset
anchors.leftMargin: card.contentLeftInset
spacing: root.contentSpacing spacing: root.contentSpacing
Rectangle { Rectangle {
@@ -240,7 +245,6 @@ Item {
height: root.headerHeight height: root.headerHeight
radius: root.cornerRadius radius: root.cornerRadius
color: "transparent" color: "transparent"
border.width: 0
Text { Text {
anchors.left: parent.left anchors.left: parent.left
@@ -278,7 +282,6 @@ Item {
height: root.cellHeight height: root.cellHeight
radius: root.cornerRadius radius: root.cornerRadius
color: hasCursor ? root.selectedBackground : "transparent" color: hasCursor ? root.selectedBackground : "transparent"
border.width: 0
Text { Text {
text: parent.emoji text: parent.emoji
+14 -8
View File
@@ -37,9 +37,12 @@ Item {
property color background: Color.launcher.background property color background: Color.launcher.background
property color foreground: Color.launcher.text property color foreground: Color.launcher.text
property color border: Color.launcher.border property color border: Color.launcher.border
property var borderSpec: Border.surfaceSpec("launcher", "border", border, 2)
property color scrim: Color.launcher.scrim property color scrim: Color.launcher.scrim
property color selectedBackground: Color.launcher.selectedBackground property color selectedBackground: Color.launcher.selectedBackground
property color selectedText: Color.launcher.selectedText property color selectedText: Color.launcher.selectedText
property color selectedBorder: Color.launcher.selectedBorder
property var selectedBorderSpec: Border.surfaceSpec("launcher", "selected-border", selectedBorder, 0)
property string fontFamily: Style.font.menuFamily property string fontFamily: Style.font.menuFamily
property int cardWidth: 644 property int cardWidth: 644
@@ -342,15 +345,15 @@ Item {
onClicked: root.dismiss() onClicked: root.dismiss()
} }
Rectangle { BorderSurface {
id: card id: card
width: Math.min(root.cardWidth, panel.width - Style.gapsOut * 2) width: Math.min(root.cardWidth, panel.width - Style.gapsOut * 2)
height: Math.min(root.cardHeight, panel.height - Style.gapsOut * 2) height: Math.min(root.cardHeight, panel.height - Style.gapsOut * 2)
radius: Style.cornerRadius radius: Style.cornerRadius
anchors.centerIn: parent anchors.centerIn: parent
color: root.background color: root.background
border.color: root.border borderSpec: root.borderSpec
border.width: 2 padding: root.contentMargin
clip: true clip: true
MouseArea { anchors.fill: parent; onClicked: {} } MouseArea { anchors.fill: parent; onClicked: {} }
@@ -435,7 +438,10 @@ Item {
Column { Column {
anchors.fill: parent anchors.fill: parent
anchors.margins: root.contentMargin anchors.topMargin: card.contentTopInset
anchors.rightMargin: card.contentRightInset
anchors.bottomMargin: card.contentBottomInset
anchors.leftMargin: card.contentLeftInset
spacing: root.contentSpacing spacing: root.contentSpacing
Rectangle { Rectangle {
@@ -471,7 +477,7 @@ Item {
spacing: 0 spacing: 0
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
delegate: Rectangle { delegate: BorderSurface {
id: row id: row
required property int index required property int index
required property string name required property string name
@@ -484,12 +490,12 @@ Item {
height: root.rowHeight height: root.rowHeight
radius: 0 radius: 0
color: row.hasCursor ? root.selectedBackground : "transparent" color: row.hasCursor ? root.selectedBackground : "transparent"
border.width: 0 borderSpec: row.hasCursor ? root.selectedBorderSpec : Border.none()
Item { Item {
id: iconSlot id: iconSlot
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 14 anchors.leftMargin: row.borderLeft + 14
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: root.iconSlotWidth width: root.iconSlotWidth
height: parent.height height: parent.height
@@ -519,7 +525,7 @@ Item {
anchors.left: iconSlot.right anchors.left: iconSlot.right
anchors.leftMargin: 14 anchors.leftMargin: 14
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: 14 anchors.rightMargin: row.borderRight + 14
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: row.name text: row.name
color: row.hasCursor ? root.selectedText : root.foreground color: row.hasCursor ? root.selectedText : root.foreground
+10 -5
View File
@@ -1,6 +1,7 @@
import QtQuick import QtQuick
import QtQuick.Effects import QtQuick.Effects
import qs.Commons import qs.Commons
import qs.Ui
Item { Item {
id: root id: root
@@ -26,6 +27,9 @@ Item {
readonly property int fieldFontSize: Style.font.heading readonly property int fieldFontSize: Style.font.heading
readonly property int passwordDotFontSize: Math.round(fieldFontSize * 1.3) readonly property int passwordDotFontSize: Math.round(fieldFontSize * 1.3)
readonly property bool showPasswordCursor: inputEnabled && !authenticatingPassword && failureMessage.length === 0 readonly property bool showPasswordCursor: inputEnabled && !authenticatingPassword && failureMessage.length === 0
readonly property string borderToken: failureMessage.length > 0 ? "border-error" : (authenticatingPassword ? "border-active" : "border")
readonly property color borderFallback: failureMessage.length > 0 ? Color.lock.borderError : (authenticatingPassword ? Color.lock.borderActive : Color.lock.border)
readonly property var inputBorderSpec: Border.surfaceSpec("lock", borderToken, borderFallback, root.outlineThickness, "border-alpha")
signal submitPassword(string password) signal submitPassword(string password)
signal passwordTextEdited(string password) signal passwordTextEdited(string password)
@@ -97,22 +101,23 @@ Item {
onPositionChanged: root.wakeRequested() onPositionChanged: root.wakeRequested()
} }
Rectangle { BorderSurface {
id: inputField id: inputField
width: root.fieldWidth width: root.fieldWidth
height: root.fieldHeight height: root.fieldHeight
anchors.centerIn: parent anchors.centerIn: parent
color: Color.lock.background color: Color.lock.background
border.color: root.failureMessage.length > 0 ? Color.lock.borderError : (root.authenticatingPassword ? Color.lock.borderActive : Color.lock.border) borderSpec: root.inputBorderSpec
border.width: root.outlineThickness
radius: Style.cornerRadius radius: Style.cornerRadius
clip: true clip: true
TextInput { TextInput {
id: passwordInput id: passwordInput
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: root.outlineThickness + 18 anchors.topMargin: inputField.borderTop
anchors.rightMargin: root.outlineThickness + 18 anchors.rightMargin: inputField.borderRight + 18
anchors.bottomMargin: inputField.borderBottom
anchors.leftMargin: inputField.borderLeft + 18
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
horizontalAlignment: TextInput.AlignHCenter horizontalAlignment: TextInput.AlignHCenter
activeFocusOnPress: true activeFocusOnPress: true
+17 -11
View File
@@ -3,6 +3,7 @@ import Quickshell.Io
import Quickshell.Wayland import Quickshell.Wayland
import QtQuick import QtQuick
import qs.Commons import qs.Commons
import qs.Ui
import "MenuModel.js" as MenuModel import "MenuModel.js" as MenuModel
Item { Item {
@@ -77,9 +78,12 @@ Item {
property color background: Color.menu.background property color background: Color.menu.background
property color foreground: Color.menu.text property color foreground: Color.menu.text
property color border: Color.menu.border property color border: Color.menu.border
property var borderSpec: Border.surfaceSpec("menu", "border", border, Math.max(1, Style.space(2)))
property color scrim: Color.menu.scrim property color scrim: Color.menu.scrim
property color selectedBackground: Color.menu.selectedBackground property color selectedBackground: Color.menu.selectedBackground
property color selectedText: Color.menu.selectedText property color selectedText: Color.menu.selectedText
property color selectedBorder: Color.menu.selectedBorder
property var selectedBorderSpec: Border.surfaceSpec("menu", "selected-border", selectedBorder, 0)
readonly property int cornerRadius: Style.cornerRadius readonly property int cornerRadius: Style.cornerRadius
property int contentMargin: Style.spacing.panelPadding property int contentMargin: Style.spacing.panelPadding
property int headerHeight: Math.max(Style.space(34), Style.font.title + Style.spacing.controlPaddingY * 2) property int headerHeight: Math.max(Style.space(34), Style.font.title + Style.spacing.controlPaddingY * 2)
@@ -787,15 +791,15 @@ Item {
onClicked: root.cancel() onClicked: root.cancel()
} }
Rectangle { BorderSurface {
id: card id: card
width: root.cardWidth width: root.cardWidth
height: root.cardHeight height: root.cardHeight
radius: root.cornerRadius radius: root.cornerRadius
anchors.centerIn: parent anchors.centerIn: parent
color: root.background color: root.background
border.color: root.border borderSpec: root.borderSpec
border.width: Math.max(1, Style.space(2)) padding: root.contentMargin
MouseArea { anchors.fill: parent; onClicked: {} } MouseArea { anchors.fill: parent; onClicked: {} }
@@ -842,7 +846,10 @@ Item {
Column { Column {
anchors.fill: parent anchors.fill: parent
anchors.margins: root.contentMargin anchors.topMargin: card.contentTopInset
anchors.rightMargin: card.contentRightInset
anchors.bottomMargin: card.contentBottomInset
anchors.leftMargin: card.contentLeftInset
spacing: root.contentSpacing spacing: root.contentSpacing
Rectangle { Rectangle {
@@ -850,7 +857,6 @@ Item {
height: root.headerHeight height: root.headerHeight
radius: root.cornerRadius radius: root.cornerRadius
color: "transparent" color: "transparent"
border.width: 0
Text { Text {
anchors.left: parent.left anchors.left: parent.left
@@ -898,7 +904,7 @@ Item {
} }
} }
delegate: Rectangle { delegate: BorderSurface {
id: row id: row
required property int index required property int index
required property string itemId required property string itemId
@@ -918,7 +924,7 @@ Item {
height: root.rowHeightForDetail(row.detail) height: root.rowHeightForDetail(row.detail)
radius: root.cornerRadius radius: root.cornerRadius
color: row.hasCursor ? root.selectedBackground : "transparent" color: row.hasCursor ? root.selectedBackground : "transparent"
border.width: 0 borderSpec: row.hasCursor ? root.selectedBorderSpec : Border.none()
Rectangle { Rectangle {
visible: false visible: false
@@ -927,7 +933,7 @@ Item {
radius: Math.min(root.cornerRadius, Style.space(4)) radius: Math.min(root.cornerRadius, Style.space(4))
color: root.selectedBackground color: root.selectedBackground
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Style.space(8) anchors.leftMargin: row.borderLeft + Style.space(8)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
@@ -942,14 +948,14 @@ Item {
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Style.space(8) anchors.leftMargin: row.borderLeft + Style.space(8)
y: contentColumn.y + labelText.y + (labelText.height - height) / 2 y: contentColumn.y + labelText.y + (labelText.height - height) / 2
} }
Column { Column {
id: contentColumn id: contentColumn
anchors.left: row.hasIcon ? iconText.right : parent.left anchors.left: row.hasIcon ? iconText.right : parent.left
anchors.leftMargin: row.hasIcon ? Style.space(6) : Style.space(18) anchors.leftMargin: row.hasIcon ? Style.space(6) : row.borderLeft + Style.space(18)
anchors.right: trail.left anchors.right: trail.left
anchors.rightMargin: Style.space(6) anchors.rightMargin: Style.space(6)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -982,7 +988,7 @@ Item {
id: trail id: trail
width: Style.space(14) width: Style.space(14)
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Style.space(8) anchors.rightMargin: row.borderRight + Style.space(8)
y: contentColumn.y + labelText.y + (labelText.height - height) / 2 y: contentColumn.y + labelText.y + (labelText.height - height) / 2
spacing: 0 spacing: 0
+8 -5
View File
@@ -1055,7 +1055,7 @@ BarWidget {
} }
} }
component SectionCard: Rectangle { component SectionCard: BorderSurface {
id: section id: section
property string title: "" property string title: ""
property string subtitle: "" property string subtitle: ""
@@ -1064,17 +1064,20 @@ BarWidget {
Layout.fillWidth: true Layout.fillWidth: true
color: card color: card
border.color: Qt.rgba(foreground.r, foreground.g, foreground.b, 0.05) borderSpec: Border.flat(Qt.rgba(foreground.r, foreground.g, foreground.b, 0.05), 1)
border.width: 1 padding: 12
radius: Style.cornerRadius radius: Style.cornerRadius
implicitHeight: body.implicitHeight + 22 implicitHeight: body.implicitHeight + contentTopInset + contentBottomInset
ColumnLayout { ColumnLayout {
id: body id: body
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.margins: 12 anchors.topMargin: section.contentTopInset
anchors.rightMargin: section.contentRightInset
anchors.bottomMargin: section.contentBottomInset
anchors.leftMargin: section.contentLeftInset
spacing: 8 spacing: 8
PanelSectionHeader { PanelSectionHeader {
+8 -11
View File
@@ -130,14 +130,13 @@ BarWidget {
Item { Layout.fillWidth: true } Item { Layout.fillWidth: true }
Rectangle { BorderSurface {
id: dndPill id: dndPill
Layout.preferredHeight: Math.max(Style.space(24), Style.font.bodySmall + Style.spacing.controlPaddingY * 2) Layout.preferredHeight: Math.max(Style.space(24), Style.font.bodySmall + Style.spacing.controlPaddingY * 2)
Layout.preferredWidth: dndLabel.implicitWidth + dndGlyph.implicitWidth + Style.space(18) Layout.preferredWidth: dndLabel.implicitWidth + dndGlyph.implicitWidth + Style.space(18)
radius: Math.min(Style.space(12), root.cardRadius + Style.space(6)) radius: Math.min(Style.space(12), root.cardRadius + Style.space(6))
color: dndOn ? root.colAccent : root.colSurface color: dndOn ? root.colAccent : root.colSurface
border.color: dndOn ? root.colAccent : root.colBorder borderSpec: Border.flat(dndOn ? root.colAccent : root.colBorder, Style.normalBorderWidth)
border.width: Style.normalBorderWidth
readonly property bool dndOn: !!root.notificationService && root.notificationService.doNotDisturb readonly property bool dndOn: !!root.notificationService && root.notificationService.doNotDisturb
@@ -228,13 +227,12 @@ BarWidget {
Item { Layout.fillWidth: true } Item { Layout.fillWidth: true }
Rectangle { BorderSurface {
Layout.preferredWidth: actionLabel.implicitWidth + Style.space(16) Layout.preferredWidth: actionLabel.implicitWidth + Style.space(16)
Layout.preferredHeight: Math.max(Style.space(22), Style.font.bodySmall + Style.spacing.controlPaddingY * 2) Layout.preferredHeight: Math.max(Style.space(22), Style.font.bodySmall + Style.spacing.controlPaddingY * 2)
radius: Math.min(Style.space(6), root.cardRadius) radius: Math.min(Style.space(6), root.cardRadius)
color: actionArea.containsMouse ? root.colBorder : "transparent" color: actionArea.containsMouse ? root.colBorder : "transparent"
border.color: root.colBorder borderSpec: Border.flat(root.colBorder, Style.normalBorderWidth)
border.width: Style.normalBorderWidth
Text { Text {
id: actionLabel id: actionLabel
@@ -272,7 +270,7 @@ BarWidget {
: (onPending ? root.notificationService.pendingModel : root.notificationService.pastModel) : (onPending ? root.notificationService.pendingModel : root.notificationService.pastModel)
visible: count > 0 visible: count > 0
delegate: Rectangle { delegate: BorderSurface {
id: rowCard id: rowCard
required property int index required property int index
required property string app required property string app
@@ -293,8 +291,7 @@ BarWidget {
implicitHeight: rowContent.implicitHeight + Style.spacing.panelGap implicitHeight: rowContent.implicitHeight + Style.spacing.panelGap
radius: root.cardRadius radius: root.cardRadius
color: "transparent" color: "transparent"
border.color: root.colBorder borderSpec: Border.flat(root.colBorder, Style.normalBorderWidth)
border.width: Style.normalBorderWidth
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
@@ -307,8 +304,8 @@ BarWidget {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Style.space(12) anchors.leftMargin: rowCard.borderLeft + Style.space(12)
anchors.rightMargin: Style.space(12) anchors.rightMargin: rowCard.borderRight + Style.space(12)
spacing: Style.space(10) spacing: Style.space(10)
Item { Item {
@@ -6,9 +6,10 @@ import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import qs.Commons import qs.Commons
import qs.Ui
import "../NotificationLogic.js" as NotificationLogic import "../NotificationLogic.js" as NotificationLogic
Rectangle { BorderSurface {
id: root id: root
property string app: "" property string app: ""
@@ -48,6 +49,7 @@ Rectangle {
readonly property color dimColor: Qt.darker(Color.notifications.text, 1.4) readonly property color dimColor: Qt.darker(Color.notifications.text, 1.4)
readonly property color bodyColor: Qt.darker(Color.notifications.text, 1.15) readonly property color bodyColor: Qt.darker(Color.notifications.text, 1.15)
readonly property color accentColor: urgency === 2 ? Color.urgent : (urgency === 0 ? dimColor : Color.notifications.countdown) readonly property color accentColor: urgency === 2 ? Color.urgent : (urgency === 0 ? dimColor : Color.notifications.countdown)
readonly property var cardBorderSpec: Border.surfaceSpec("notifications", "border", Color.notifications.border, Math.max(1, Style.space(2)))
function sanitizeBody(s) { function sanitizeBody(s) {
return NotificationLogic.sanitizeBody(s, app, appIcon) return NotificationLogic.sanitizeBody(s, app, appIcon)
@@ -62,13 +64,12 @@ Rectangle {
} }
implicitWidth: Style.space(380) implicitWidth: Style.space(380)
// Add 2 * border.width so mainColumn (inset by border.width on top/left/right) // Add vertical border insets so mainColumn (inset by border on top/left/right)
// doesn't push content under the bottom edge. // doesn't push content under the bottom edge.
implicitHeight: mainColumn.implicitHeight + border.width * 2 implicitHeight: mainColumn.implicitHeight + borderTop + borderBottom
radius: cornerRadius radius: cornerRadius
color: Color.notifications.background color: Color.notifications.background
border.color: Color.notifications.border borderSpec: cardBorderSpec
border.width: Math.max(1, Style.space(2))
clip: true clip: true
HoverHandler { id: hoverTracker } HoverHandler { id: hoverTracker }
@@ -86,9 +87,9 @@ Rectangle {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: root.border.width anchors.topMargin: root.borderTop
anchors.leftMargin: root.border.width anchors.leftMargin: root.borderLeft
anchors.rightMargin: root.border.width anchors.rightMargin: root.borderRight
spacing: 0 spacing: 0
// Text content. // Text content.
+7 -5
View File
@@ -3,6 +3,7 @@ import Quickshell
import Quickshell.Io import Quickshell.Io
import Quickshell.Wayland import Quickshell.Wayland
import qs.Commons import qs.Commons
import qs.Ui
import "OsdModel.js" as OsdModel import "OsdModel.js" as OsdModel
Item { Item {
@@ -79,7 +80,7 @@ Item {
// never blocks clicks to the desktop below it. // never blocks clicks to the desktop below it.
mask: Region {} mask: Region {}
Rectangle { BorderSurface {
id: card id: card
width: root.mediaOsd ? root.mediaCardWidth : root.cardWidth width: root.mediaOsd ? root.mediaCardWidth : root.cardWidth
height: Math.max(Style.space(68), Style.font.displayLarge + Style.spacing.panelGap) height: Math.max(Style.space(68), Style.font.displayLarge + Style.spacing.panelGap)
@@ -87,15 +88,16 @@ Item {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.bottomMargin: Style.space(67) anchors.bottomMargin: Style.space(67)
color: Util.alpha(Color.background, 0.97) color: Util.alpha(Color.background, 0.97)
border.color: Color.popups.border borderSpec: Border.surfaceSpec("popups", "border", Color.popups.border, Math.max(1, Style.space(2)))
border.width: Math.max(1, Style.space(2))
radius: Style.cornerRadius radius: Style.cornerRadius
opacity: root.opened ? 1 : 0 opacity: root.opened ? 1 : 0
Row { Row {
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: Style.space(16) anchors.topMargin: card.borderTop
anchors.rightMargin: Style.space(16) anchors.rightMargin: card.borderRight + Style.space(16)
anchors.bottomMargin: card.borderBottom
anchors.leftMargin: card.borderLeft + Style.space(16)
spacing: Style.space(16) spacing: Style.space(16)
Text { Text {
width: Style.space(28) width: Style.space(28)
+6 -9
View File
@@ -741,15 +741,14 @@ Panel {
Item { width: Math.max(0, parent.width - parent.children[0].width - heroDetailPill.implicitWidth); height: 1 } Item { width: Math.max(0, parent.width - parent.children[0].width - heroDetailPill.implicitWidth); height: 1 }
Rectangle { BorderSurface {
id: heroDetailPill id: heroDetailPill
visible: heroSsid.detail !== "" visible: heroSsid.detail !== ""
implicitWidth: heroDetail.implicitWidth + Style.space(10) implicitWidth: heroDetail.implicitWidth + Style.space(10)
implicitHeight: heroDetail.implicitHeight + Style.space(4) implicitHeight: heroDetail.implicitHeight + Style.space(4)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: "transparent" color: "transparent"
border.color: Style.normalBorderFor(root.bar.foreground) borderSpec: Border.controlSpec("normal", root.bar.foreground, Color.accent)
border.width: Style.normalBorderWidth
radius: Style.cornerRadius radius: Style.cornerRadius
Text { Text {
@@ -1139,12 +1138,11 @@ Panel {
font.pixelSize: Style.font.subtitle font.pixelSize: Style.font.subtitle
} }
Rectangle { BorderSurface {
anchors.fill: parent anchors.fill: parent
visible: row.forgetFocused visible: row.forgetFocused
color: Style.hoverFillFor(root.bar.urgent, root.bar.urgent) color: Style.hoverFillFor(root.bar.urgent, root.bar.urgent)
border.color: Style.hoverBorderFor(root.bar.urgent, root.bar.urgent) borderSpec: Border.controlSpec("hover-cursor", root.bar.urgent, root.bar.urgent)
border.width: Style.hoverBorderWidth
radius: Style.cornerRadius radius: Style.cornerRadius
z: -1 z: -1
} }
@@ -1256,7 +1254,7 @@ Panel {
Component.onCompleted: if (visible) Qt.callLater(forceActiveFocus) Component.onCompleted: if (visible) Qt.callLater(forceActiveFocus)
} }
Rectangle { BorderSurface {
id: statusMsgWrapper id: statusMsgWrapper
visible: row.isBusy || row.isFailed visible: row.isBusy || row.isFailed
anchors.left: parent.left anchors.left: parent.left
@@ -1264,8 +1262,7 @@ Panel {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
height: Style.spacing.controlHeight height: Style.spacing.controlHeight
color: Style.normalFillFor(root.bar.foreground) color: Style.normalFillFor(root.bar.foreground)
border.color: Style.normalBorderFor(root.bar.foreground) borderSpec: Border.controlSpec("normal", root.bar.foreground, Color.accent)
border.width: Style.normalBorderWidth
radius: Style.cornerRadius radius: Style.cornerRadius
Text { Text {
+2 -3
View File
@@ -1022,10 +1022,9 @@ Panel {
event.accepted = true event.accepted = true
} }
} }
background: Rectangle { background: BorderSurface {
color: Color.background color: Color.background
border.color: root.dim borderSpec: Border.flat(root.dim, 1)
border.width: 1
radius: Style.cornerRadius radius: Style.cornerRadius
} }
@@ -1,5 +1,6 @@
import QtQuick import QtQuick
import qs.Commons import qs.Commons
import qs.Ui
Item { Item {
id: root id: root
@@ -42,7 +43,7 @@ Item {
rotation: -45 rotation: -45
} }
Rectangle { BorderSurface {
visible: root.warning visible: root.warning
width: Math.max(7, parent.width * 0.42) width: Math.max(7, parent.width * 0.42)
height: width height: width
@@ -50,8 +51,7 @@ Item {
color: root.badgeColor color: root.badgeColor
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
border.color: Color.popups.background borderSpec: Border.flat(Color.popups.background, 1)
border.width: 1
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
+9 -4
View File
@@ -4,6 +4,7 @@ import Quickshell.Io
import Quickshell.Services.Polkit import Quickshell.Services.Polkit
import Quickshell.Wayland import Quickshell.Wayland
import qs.Commons import qs.Commons
import qs.Ui
import "PolkitModel.js" as PolkitModel import "PolkitModel.js" as PolkitModel
Item { Item {
@@ -16,6 +17,7 @@ Item {
property color foreground: Color.polkit.text property color foreground: Color.polkit.text
property color border: Color.polkit.border property color border: Color.polkit.border
property color borderError: Color.polkit.borderError property color borderError: Color.polkit.borderError
property var borderSpec: Border.surfaceSpec("polkit", errorFlash ? "border-error" : "border", errorFlash ? borderError : border, Math.max(1, Style.space(2)), "border-alpha")
property color scrim: Color.polkit.scrim property color scrim: Color.polkit.scrim
readonly property int cornerRadius: Style.cornerRadius readonly property int cornerRadius: Style.cornerRadius
property int contentMargin: Style.spacing.panelPadding property int contentMargin: Style.spacing.panelPadding
@@ -212,7 +214,7 @@ Item {
onClicked: root.refocus() onClicked: root.refocus()
} }
Rectangle { BorderSurface {
id: card id: card
width: root.cardWidth width: root.cardWidth
height: root.cardHeight height: root.cardHeight
@@ -220,8 +222,8 @@ Item {
anchors.centerIn: parent anchors.centerIn: parent
anchors.horizontalCenterOffset: root.shakeOffset anchors.horizontalCenterOffset: root.shakeOffset
color: root.background color: root.background
border.color: root.errorFlash ? root.borderError : root.border borderSpec: root.borderSpec
border.width: Math.max(1, Style.space(2)) padding: root.contentMargin
MouseArea { anchors.fill: parent; onClicked: root.refocus() } MouseArea { anchors.fill: parent; onClicked: root.refocus() }
@@ -245,7 +247,10 @@ Item {
Row { Row {
id: cardRow id: cardRow
anchors.fill: parent anchors.fill: parent
anchors.margins: root.contentMargin anchors.topMargin: card.contentTopInset
anchors.rightMargin: card.contentRightInset
anchors.bottomMargin: card.contentBottomInset
anchors.leftMargin: card.contentLeftInset
spacing: Style.space(14) spacing: Style.space(14)
Text { Text {
+9 -4
View File
@@ -2,6 +2,7 @@ import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import QtQuick import QtQuick
import qs.Commons import qs.Commons
import qs.Ui
import "ReminderFlowModel.js" as ReminderFlowModel import "ReminderFlowModel.js" as ReminderFlowModel
Item { Item {
@@ -20,6 +21,7 @@ Item {
property color background: Color.menu.background property color background: Color.menu.background
property color foreground: Color.menu.text property color foreground: Color.menu.text
property color border: Color.menu.border property color border: Color.menu.border
property var borderSpec: Border.surfaceSpec("menu", "border", border, Math.max(1, Style.space(2)))
property color scrim: Color.menu.scrim property color scrim: Color.menu.scrim
readonly property int cornerRadius: Style.cornerRadius readonly property int cornerRadius: Style.cornerRadius
property int contentMargin: Style.spacing.panelPadding property int contentMargin: Style.spacing.panelPadding
@@ -110,15 +112,15 @@ Item {
onClicked: root.dismiss() onClicked: root.dismiss()
} }
Rectangle { BorderSurface {
id: card id: card
width: root.cardWidth width: root.cardWidth
height: root.cardHeight height: root.cardHeight
radius: root.cornerRadius radius: root.cornerRadius
anchors.centerIn: parent anchors.centerIn: parent
color: root.background color: root.background
border.color: root.border borderSpec: root.borderSpec
border.width: Math.max(1, Style.space(2)) padding: root.contentMargin
MouseArea { anchors.fill: parent; onClicked: {} } MouseArea { anchors.fill: parent; onClicked: {} }
@@ -148,7 +150,10 @@ Item {
Item { Item {
anchors.fill: parent anchors.fill: parent
anchors.margins: root.contentMargin anchors.topMargin: card.contentTopInset
anchors.rightMargin: card.contentRightInset
anchors.bottomMargin: card.contentBottomInset
anchors.leftMargin: card.contentLeftInset
Text { Text {
anchors.left: parent.left anchors.left: parent.left
+6 -8
View File
@@ -117,13 +117,12 @@ BarWidget {
spacing: Style.space(10) spacing: Style.space(10)
width: parent.width width: parent.width
Rectangle { BorderSurface {
width: Style.space(64) width: Style.space(64)
height: Style.space(64) height: Style.space(64)
radius: Style.spacing.labelGap radius: Style.spacing.labelGap
color: Style.normalFillFor(root.bar.foreground, Color.accent) color: Style.normalFillFor(root.bar.foreground, Color.accent)
border.color: Style.normalBorderFor(root.bar.foreground, Color.accent) borderSpec: Border.controlSpec("normal", root.bar.foreground, Color.accent)
border.width: Style.normalBorderWidth
Image { Image {
anchors.fill: parent anchors.fill: parent
@@ -230,7 +229,7 @@ BarWidget {
Repeater { Repeater {
model: root.sourcePlayers model: root.sourcePlayers
Rectangle { BorderSurface {
id: sourceRow id: sourceRow
required property var modelData required property var modelData
@@ -244,16 +243,15 @@ BarWidget {
height: sourceInner.implicitHeight + Style.space(10) height: sourceInner.implicitHeight + Style.space(10)
radius: Style.spacing.labelGap radius: Style.spacing.labelGap
color: selected ? Style.selectedFillFor(root.bar.foreground, Color.accent) : "transparent" color: selected ? Style.selectedFillFor(root.bar.foreground, Color.accent) : "transparent"
border.color: selected ? Style.selectedBorderFor(root.bar.foreground, Color.accent) : "transparent" borderSpec: selected ? Border.controlSpec("normal", root.bar.foreground, Color.accent) : Border.none()
border.width: selected ? Style.normalBorderWidth : 0
Row { Row {
id: sourceInner id: sourceInner
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Style.space(8) anchors.leftMargin: sourceRow.borderLeft + Style.space(8)
anchors.rightMargin: Style.space(8) anchors.rightMargin: sourceRow.borderRight + Style.space(8)
spacing: Style.space(8) spacing: Style.space(8)
Text { Text {
+4
View File
@@ -249,8 +249,10 @@ EOF
cat >"$USER_THEMED/gradient-test.txt.tpl" <<'EOF' cat >"$USER_THEMED/gradient-test.txt.tpl" <<'EOF'
hypr={{ hypr_gradient hyprland_active_border accent }} hypr={{ hypr_gradient hyprland_active_border accent }}
shell={{ gradient_start hyprland_active_border accent }} shell={{ gradient_start hyprland_active_border accent }}
shell-gradient={{ shell_gradient hyprland_active_border accent }}
fallback-hypr={{ hypr_gradient missing accent }} fallback-hypr={{ hypr_gradient missing accent }}
fallback-shell={{ gradient_start missing accent }} fallback-shell={{ gradient_start missing accent }}
fallback-shell-gradient={{ shell_gradient missing accent }}
EOF EOF
OMARCHY_PATH="$ROOT" HOME="$PI_TMPDIR" "$ROOT/bin/omarchy-theme-set-templates" OMARCHY_PATH="$ROOT" HOME="$PI_TMPDIR" "$ROOT/bin/omarchy-theme-set-templates"
@@ -261,8 +263,10 @@ pass "theme template color mix helpers work"
grep -qx 'hypr={ colors = { "rgba(010203ee)", "rgba(040506ee)" }, angle = 45 }' "$NEXT_THEME/gradient-test.txt" || fail "theme template hypr_gradient helper works" grep -qx 'hypr={ colors = { "rgba(010203ee)", "rgba(040506ee)" }, angle = 45 }' "$NEXT_THEME/gradient-test.txt" || fail "theme template hypr_gradient helper works"
grep -qx 'shell=#010203' "$NEXT_THEME/gradient-test.txt" || fail "theme template gradient_start helper works" grep -qx 'shell=#010203' "$NEXT_THEME/gradient-test.txt" || fail "theme template gradient_start helper works"
grep -qx 'shell-gradient=rgba(010203ee) rgba(040506ee) 45deg' "$NEXT_THEME/gradient-test.txt" || fail "theme template shell_gradient helper works"
grep -qx 'fallback-hypr="#336699"' "$NEXT_THEME/gradient-test.txt" || fail "theme template hypr_gradient fallback works" grep -qx 'fallback-hypr="#336699"' "$NEXT_THEME/gradient-test.txt" || fail "theme template hypr_gradient fallback works"
grep -qx 'fallback-shell=#336699' "$NEXT_THEME/gradient-test.txt" || fail "theme template gradient_start fallback works" grep -qx 'fallback-shell=#336699' "$NEXT_THEME/gradient-test.txt" || fail "theme template gradient_start fallback works"
grep -qx 'fallback-shell-gradient=#336699' "$NEXT_THEME/gradient-test.txt" || fail "theme template shell_gradient fallback works"
pass "theme template gradient helpers work" pass "theme template gradient helpers work"
jq -e '.name == "omarchy-system" and .vars.panel == "#0f0f0f" and .vars.border == "#4d4d4d" and .colors.accent == "accent"' "$NEXT_THEME/pi.json" >/dev/null jq -e '.name == "omarchy-system" and .vars.panel == "#0f0f0f" and .vars.border == "#4d4d4d" and .colors.accent == "accent"' "$NEXT_THEME/pi.json" >/dev/null
+44
View File
@@ -0,0 +1,44 @@
#!/bin/bash
source "$(dirname "$0")/base-test.sh"
run_node_test <<'JS'
const fs = require('fs')
const vm = require('vm')
const source = fs.readFileSync(path.join(root, 'shell/Commons/BorderGeometry.js'), 'utf8').replace(/^\.pragma library\n/, '')
const geometry = {}
vm.createContext(geometry)
vm.runInContext(source, geometry)
assertDeepEqual(
geometry.parseWidthSpec('2 4 6 8', 1),
{ top: 2, right: 4, bottom: 6, left: 8 },
'border geometry parses four-sided widths'
)
assertDeepEqual(
geometry.parseWidthSpec('2 4', 1),
{ top: 2, right: 4, bottom: 2, left: 4 },
'border geometry parses CSS two-value widths'
)
const gradient = geometry.parseGradientSpec('rgba(010203ee) rgba(040506ee) 45deg', '#336699', 1)
assertEqual(gradient.colors[0], '#010203ee', 'border geometry parses first rgba gradient stop')
assertEqual(gradient.colors[1], '#040506ee', 'border geometry parses second rgba gradient stop')
assertEqual(gradient.angle, 45, 'border geometry parses gradient angle')
assert(gradient.enabled, 'border geometry marks multi-stop gradients enabled')
assertEqual(
geometry.canonicalColor('0xee33ccff', 1),
'#33ccffee',
'border geometry converts legacy ARGB color to QML RGBA hex'
)
const pathData = geometry.ringPath(100, 50, 10, { top: 4, right: 2, bottom: 8, left: 6 })
assert(pathData.includes('M 10 0'), 'border geometry emits outer rounded path')
assert(pathData.includes('M 10 4'), 'border geometry emits inset inner rounded path')
const endpoints = geometry.gradientEndpoints(100, 50, 0)
assertEqual(Math.round(endpoints.x1), 0, 'border geometry 0deg starts at left edge')
assertEqual(Math.round(endpoints.x2), 100, 'border geometry 0deg ends at right edge')
JS
-5
View File
@@ -1,5 +0,0 @@
# last-horizon preserves the historical behavior of matching notification
# borders to the theme's Hyprland active-window border (hyprland_active_border
# in colors.toml), rather than the accent.
[notifications]
border = "#8a8588"
-5
View File
@@ -1,5 +0,0 @@
# solitude preserves the historical behavior of matching notification
# borders to the theme's Hyprland active-window border (hyprland_active_border
# in colors.toml), rather than the accent.
[notifications]
border = "#798186"