mirror of
https://github.com/arthur-pbty/arthur-os.git
synced 2026-08-06 12:39:35 +02:00
Bring on gradient borders
This commit is contained in:
@@ -173,6 +173,25 @@ gradient_start_value() {
|
||||
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() {
|
||||
local key="$1"
|
||||
local value="$2"
|
||||
@@ -247,6 +266,9 @@ add_gradient_function_value() {
|
||||
gradient_start)
|
||||
value=$(gradient_start_value "$key" "$fallback")
|
||||
;;
|
||||
shell_gradient)
|
||||
value=$(shell_gradient_value "$key" "$fallback")
|
||||
;;
|
||||
*)
|
||||
return
|
||||
;;
|
||||
@@ -264,7 +286,7 @@ add_gradient_function_values() {
|
||||
[[ -n ${seen[$token]:-} ]] && continue
|
||||
seen[$token]=1
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Omarchy shell surfaces. Colors derive from colors.toml; sizes and the
|
||||
# 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]
|
||||
# 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,
|
||||
# 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-fill-alpha = 0.04
|
||||
normal-border = "{{ foreground }}"
|
||||
normal-border-width = 1
|
||||
normal-border-alpha = 0.4
|
||||
|
||||
# Hover-cursor: mouse hover and the panel keyboard cursor.
|
||||
hover-cursor-color = "{{ foreground }}"
|
||||
hover-cursor-fill-alpha = 0.08
|
||||
hover-cursor-border = "{{ foreground }}"
|
||||
hover-cursor-border-width = 1
|
||||
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.
|
||||
focus-color = "{{ foreground }}"
|
||||
focus-fill-alpha = 0.08
|
||||
focus-border = "{{ foreground }}"
|
||||
focus-border-width = 1
|
||||
focus-border-alpha = 0.25
|
||||
|
||||
# Selected: persistent chosen/current state.
|
||||
selected-color = "{{ foreground }}"
|
||||
selected-fill-alpha = 0.18
|
||||
selected-border = "{{ foreground }}"
|
||||
selected-border-width = 0
|
||||
selected-border-alpha = 1.0
|
||||
|
||||
@@ -107,11 +114,15 @@ base-size = 12
|
||||
|
||||
[popups]
|
||||
# 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-alpha = 1.0
|
||||
text = "{{ foreground }}"
|
||||
border = "{{ accent }}"
|
||||
border = "{{ shell_gradient hyprland_active_border accent }}"
|
||||
border-alpha = 1.0
|
||||
# border-width = 2
|
||||
|
||||
[tooltip]
|
||||
# Hover tooltips across the bar, panels, and buttons. background-alpha of
|
||||
@@ -119,18 +130,18 @@ border-alpha = 1.0
|
||||
background = "{{ background }}"
|
||||
background-alpha = 0.97
|
||||
text = "{{ foreground }}"
|
||||
border = "{{ foreground }}"
|
||||
border = "{{ shell_gradient hyprland_active_border foreground }}"
|
||||
border-alpha = 1.0
|
||||
|
||||
[notifications]
|
||||
background = "{{ background }}"
|
||||
background-alpha = 1.0
|
||||
text = "{{ foreground }}"
|
||||
# Conventionally matches the Hyprland active-window border. If a theme
|
||||
# defines a gradient active border, the shell uses its first stop because
|
||||
# QML Rectangle borders are flat colors.
|
||||
border = "{{ gradient_start hyprland_active_border accent }}"
|
||||
# Conventionally matches the Hyprland active-window border. Border accepts
|
||||
# either a solid color or the full active-border gradient.
|
||||
border = "{{ shell_gradient hyprland_active_border accent }}"
|
||||
border-alpha = 1.0
|
||||
# border-width = 2
|
||||
countdown = "{{ accent }}"
|
||||
|
||||
[launcher]
|
||||
@@ -141,14 +152,14 @@ countdown = "{{ accent }}"
|
||||
background = "{{ background }}"
|
||||
background-alpha = 0.95
|
||||
text = "{{ foreground }}"
|
||||
border = "{{ foreground }}"
|
||||
border = "{{ shell_gradient hyprland_active_border foreground }}"
|
||||
border-alpha = 1.0
|
||||
scrim = "{{ background }}"
|
||||
scrim-alpha = 0.5
|
||||
selected-background = "{{ foreground }}"
|
||||
selected-background-alpha = 0.08
|
||||
selected-text = "{{ accent }}"
|
||||
selected-border = "{{ foreground }}"
|
||||
selected-border = "{{ shell_gradient hyprland_active_border foreground }}"
|
||||
selected-border-alpha = 0.25
|
||||
|
||||
[menu]
|
||||
@@ -158,14 +169,14 @@ selected-border-alpha = 0.25
|
||||
background = "{{ background }}"
|
||||
background-alpha = 1.0
|
||||
text = "{{ foreground }}"
|
||||
border = "{{ foreground }}"
|
||||
border = "{{ shell_gradient hyprland_active_border foreground }}"
|
||||
border-alpha = 1.0
|
||||
scrim = "{{ background }}"
|
||||
scrim-alpha = 0.5
|
||||
selected-background = "{{ foreground }}"
|
||||
selected-background-alpha = 0.08
|
||||
selected-text = "{{ accent }}"
|
||||
selected-border = "{{ foreground }}"
|
||||
selected-border = "{{ shell_gradient hyprland_active_border foreground }}"
|
||||
selected-border-alpha = 0.25
|
||||
|
||||
[polkit]
|
||||
@@ -178,7 +189,7 @@ background = "{{ background }}"
|
||||
background-alpha = 1.0
|
||||
text = "{{ foreground }}"
|
||||
text-error = "{{ color1 }}"
|
||||
border = "{{ accent }}"
|
||||
border = "{{ shell_gradient hyprland_active_border accent }}"
|
||||
border-error = "{{ color1 }}"
|
||||
border-alpha = 1.0
|
||||
scrim = "{{ background }}"
|
||||
@@ -196,8 +207,8 @@ background = "{{ background }}"
|
||||
background-alpha = 0.8
|
||||
text = "{{ foreground }}"
|
||||
text-error = "{{ color1 }}"
|
||||
border = "{{ foreground }}"
|
||||
border-active = "{{ accent }}"
|
||||
border = "{{ shell_gradient hyprland_active_border foreground }}"
|
||||
border-active = "{{ shell_gradient hyprland_active_border accent }}"
|
||||
border-error = "{{ color1 }}"
|
||||
border-alpha = 1.0
|
||||
# selection is the text-selection tint inside the input field.
|
||||
|
||||
+20
-11
@@ -126,10 +126,13 @@ customizes, `shell.json` is canonical — there is no deep-merge.
|
||||
|
||||
## 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 +
|
||||
sizing in `themes/<name>/shell.toml`. Defaults are generated from
|
||||
`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
|
||||
`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)`),
|
||||
the type scale (`Style.font.*`), and bar dimensions
|
||||
(`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
|
||||
|
||||
`[controls]` standardizes reusable control chrome (buttons, dropdowns,
|
||||
tab strips, etc.) around four states: `normal`, `hover-cursor`, `focus`,
|
||||
and `selected`. State colors accept palette roles (`foreground`,
|
||||
`accent`, `urgent`, `background`) or hex strings; the default template
|
||||
ships hex values. Fill/border alphas are applied to that state's color.
|
||||
and `selected`. State colors and border tokens accept palette roles
|
||||
(`foreground`, `accent`, `urgent`, `background`) or hex strings; border
|
||||
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
|
||||
their own `selected-*` tokens and do **not** inherit from `[controls]`.
|
||||
`[controls]` only governs the shared button/dropdown chrome.
|
||||
|
||||
| State | Color token | Fill alpha | Border width | Border alpha |
|
||||
|-------|-------------|------------|--------------|--------------|
|
||||
| Normal idle chrome | `normal-color` | `normal-fill-alpha` | `normal-border-width` | `normal-border-alpha` |
|
||||
| Hover / keyboard cursor | `hover-cursor-color` | `hover-cursor-fill-alpha` | `hover-cursor-border-width` | `hover-cursor-border-alpha` |
|
||||
| Qt activeFocus | `focus-color` | `focus-fill-alpha` | `focus-border-width` | `focus-border-alpha` |
|
||||
| Persistent selected/current | `selected-color` | `selected-fill-alpha` | `selected-border-width` | `selected-border-alpha` |
|
||||
| State | Color token | Fill alpha | Border token | Border width | 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` | `hover-cursor-border-width` | `hover-cursor-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` | `selected-border-width` | `selected-border-alpha` |
|
||||
|
||||
The template ships `focus-*` adjacent to `hover-cursor-*` with the same
|
||||
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
|
||||
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"
|
||||
focus-color = "accent"
|
||||
selected-color = "foreground"
|
||||
focus-border = "rgba(33ccffee) rgba(00ff99ee) 45deg"
|
||||
|
||||
# Keep selected fills but remove selected-state borders.
|
||||
selected-border-width = 0
|
||||
|
||||
+296
@@ -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
|
||||
```
|
||||
@@ -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" }
|
||||
}
|
||||
@@ -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
@@ -2,11 +2,12 @@ pragma Singleton
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import "BorderGeometry.js" as Geometry
|
||||
|
||||
// Color surfaces for the shell. Foundational palette (foreground, background,
|
||||
// accent, urgent) comes from theme/colors.toml. Per-surface roles come from
|
||||
// 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.
|
||||
QtObject {
|
||||
id: root
|
||||
@@ -34,9 +35,32 @@ QtObject {
|
||||
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) {
|
||||
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 {
|
||||
@@ -130,8 +154,9 @@ QtObject {
|
||||
|
||||
// Single TOML walker for shell.toml. Both Color (surface roles) and Style
|
||||
// (typography, spacing, bar, control states) consume the resulting dict.
|
||||
// Accepts quoted strings and bare numeric values; tolerates inline comments.
|
||||
// Numbers are kept as strings here — readers coerce when they pull a value.
|
||||
// Accepts quoted strings, bare numeric values, bare width lists, and bare
|
||||
// role names; tolerates inline comments. Numbers are kept as strings here —
|
||||
// readers coerce when they pull a value.
|
||||
function loadShell(raw) {
|
||||
var parsed = {}
|
||||
var text = String(raw || "")
|
||||
@@ -145,8 +170,9 @@ QtObject {
|
||||
if (sectionMatch) { section = sectionMatch[1]; continue }
|
||||
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 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 kv = stringKv || numKv || bareKv
|
||||
var kv = stringKv || numKv || widthKv || bareKv
|
||||
if (!kv || !section) continue
|
||||
parsed[section + "." + kv[1]] = kv[2]
|
||||
}
|
||||
|
||||
@@ -414,8 +414,9 @@ QtObject {
|
||||
if (key === "scale") nextSpacingScale = fval
|
||||
else spacingOut[key] = fval
|
||||
}
|
||||
} else if (section === "controls") {
|
||||
} else if (section === "controls" || section === "style") {
|
||||
// Strings are passed through; styleRawNum/styleString coerce on read.
|
||||
// [style] is the legacy name for [controls].
|
||||
styleOut[key] = raw
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
module qs.Commons
|
||||
singleton Border 1.0 Border.qml
|
||||
singleton Color 1.0 Color.qml
|
||||
singleton Style 1.0 Style.qml
|
||||
singleton Util 1.0 Util.qml
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -17,7 +17,7 @@ import qs.Commons
|
||||
//
|
||||
// Emits `hovered(bool)` so panels with their own keyboard cursor model
|
||||
// can update state on mouse enter/leave.
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: root
|
||||
|
||||
property string text: ""
|
||||
@@ -46,6 +46,11 @@ Rectangle {
|
||||
property real verticalPadding: Style.spacing.controlPaddingY
|
||||
property bool leftAlign: false
|
||||
|
||||
leftPadding: horizontalPadding
|
||||
rightPadding: horizontalPadding
|
||||
topPadding: verticalPadding
|
||||
bottomPadding: verticalPadding
|
||||
|
||||
// Tooltip palette. Auto-rendered if tooltipText is set. Defaults pull
|
||||
// from [tooltip] in shell.toml; override per-instance only when a button
|
||||
// intentionally wants a tooltip that diverges from the theme.
|
||||
@@ -62,13 +67,21 @@ Rectangle {
|
||||
Keys.onEnterPressed: if (focusable) root.clicked()
|
||||
Keys.onSpacePressed: if (focusable) root.clicked()
|
||||
|
||||
implicitWidth: row.implicitWidth + horizontalPadding * 2
|
||||
implicitHeight: row.implicitHeight + verticalPadding * 2
|
||||
implicitWidth: row.implicitWidth + horizontalPadding * 2 + borderLeft + borderRight
|
||||
implicitHeight: row.implicitHeight + verticalPadding * 2 + borderTop + borderBottom
|
||||
radius: Style.cornerRadius
|
||||
|
||||
readonly property bool hot: mouseArea.containsMouse || hasCursor
|
||||
readonly property bool _showFocusRing: focusable && activeFocus
|
||||
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)
|
||||
: _showFocusRing ? Style.focusFillFor(root.foreground, root.accent)
|
||||
@@ -84,17 +97,7 @@ Rectangle {
|
||||
// default for plain buttons; explicitly bordered buttons keep their
|
||||
// normal border when selected unless selected-border-width opts in to a
|
||||
// dedicated selected border.
|
||||
border.color: _showFocusRing ? Style.focusBorderFor(root.foreground, root.accent)
|
||||
: hot ? Style.hoverBorderFor(root.foreground, root.accent)
|
||||
: selected ? (Style.selectedBorderWidth > 0 ? Style.selectedBorderFor(root.foreground, root.accent) : Style.normalBorderFor(root.foreground, root.accent))
|
||||
: bordered ? Style.normalBorderFor(root.foreground, root.accent)
|
||||
: "transparent"
|
||||
|
||||
border.width: _showFocusRing ? Style.focusBorderWidth
|
||||
: hot ? Style.hoverBorderWidth
|
||||
: selected ? (Style.selectedBorderWidth > 0 ? Style.selectedBorderWidth : (bordered ? Style.normalBorderWidth : 0))
|
||||
: bordered ? Style.normalBorderWidth
|
||||
: 0
|
||||
borderSpec: _borderSpec
|
||||
|
||||
Behavior on color { ColorAnimation { duration: 120 } }
|
||||
|
||||
@@ -103,10 +106,9 @@ Rectangle {
|
||||
text: root.tooltipText
|
||||
delay: 400
|
||||
padding: 0
|
||||
background: Rectangle {
|
||||
background: BorderSurface {
|
||||
color: root.tooltipBackground
|
||||
border.color: root.tooltipBorder
|
||||
border.width: Math.max(1, Style.normalBorderWidth)
|
||||
borderSpec: root._tooltipBorderSpec
|
||||
radius: 0
|
||||
}
|
||||
contentItem: Text {
|
||||
@@ -114,10 +116,10 @@ Rectangle {
|
||||
color: root.tooltipForeground
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: Style.font.bodySmall
|
||||
leftPadding: Style.spacing.controlPaddingX
|
||||
rightPadding: Style.spacing.controlPaddingX
|
||||
topPadding: Style.spacing.controlPaddingY
|
||||
bottomPadding: Style.spacing.controlPaddingY
|
||||
leftPadding: Border.left(root._tooltipBorderSpec) + Style.spacing.controlPaddingX
|
||||
rightPadding: Border.right(root._tooltipBorderSpec) + Style.spacing.controlPaddingX
|
||||
topPadding: Border.top(root._tooltipBorderSpec) + Style.spacing.controlPaddingY
|
||||
bottomPadding: Border.bottom(root._tooltipBorderSpec) + Style.spacing.controlPaddingY
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +127,7 @@ Rectangle {
|
||||
id: row
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
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
|
||||
spacing: Style.spacing.controlGap
|
||||
|
||||
|
||||
@@ -46,20 +46,24 @@ Item {
|
||||
|
||||
MouseArea { anchors.fill: parent; onClicked: root.canceled() }
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: card
|
||||
width: Math.min(parent.width - Style.space(96), Style.space(370))
|
||||
height: Style.space(132)
|
||||
anchors.centerIn: parent
|
||||
color: root.background
|
||||
border.color: root.selectedText
|
||||
border.width: Style.normalBorderWidth
|
||||
borderSpec: Border.flat(root.selectedText, Style.normalBorderWidth)
|
||||
padding: Style.space(18)
|
||||
radius: root.cornerRadius
|
||||
|
||||
MouseArea { anchors.fill: parent; onClicked: {} }
|
||||
|
||||
Item {
|
||||
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 {
|
||||
anchors.left: parent.left
|
||||
@@ -80,7 +84,7 @@ Item {
|
||||
Repeater {
|
||||
model: [root.cancelText, root.confirmText]
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
required property int index
|
||||
required property string modelData
|
||||
|
||||
@@ -92,10 +96,9 @@ Item {
|
||||
color: selected
|
||||
? (destructive ? Util.alpha(Color.urgent, 0.22) : root.selectedBackground)
|
||||
: "transparent"
|
||||
border.color: destructive
|
||||
borderSpec: Border.flat(destructive
|
||||
? (selected ? Color.urgent : Util.alpha(Color.urgent, 0.56))
|
||||
: (selected ? root.selectedText : Util.alpha(root.foreground, 0.38))
|
||||
border.width: Style.normalBorderWidth
|
||||
: (selected ? root.selectedText : Util.alpha(root.foreground, 0.38)), Style.normalBorderWidth)
|
||||
radius: 0
|
||||
|
||||
Text {
|
||||
|
||||
@@ -11,7 +11,7 @@ import qs.Commons
|
||||
// hover-cursor border. `outline` remains as a compatibility flag for
|
||||
// callers that used to request border-only rows, but slider rows still
|
||||
// receive the same hover-cursor background as every other row.
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: root
|
||||
|
||||
property bool hasCursor: false
|
||||
@@ -27,19 +27,13 @@ Rectangle {
|
||||
radius: Style.cornerRadius
|
||||
|
||||
color: hasCursor ? fill : (current ? currentFill : "transparent")
|
||||
|
||||
border.color: root.hasCursor
|
||||
? Style.hoverBorderFor(root.foreground, root.accent)
|
||||
borderSpec: root.hasCursor
|
||||
? Border.controlSpec("hover-cursor", root.foreground, root.accent)
|
||||
: (root.current
|
||||
? Style.selectedBorderFor(root.foreground, root.accent)
|
||||
? Border.controlSpec("selected", root.foreground, root.accent)
|
||||
: (root.bordered
|
||||
? Style.normalBorderFor(root.foreground, root.accent)
|
||||
: "transparent"))
|
||||
border.width: root.hasCursor
|
||||
? Style.hoverBorderWidth
|
||||
: (root.current
|
||||
? Style.selectedBorderWidth
|
||||
: (root.bordered ? Style.normalBorderWidth : 0))
|
||||
? Border.controlSpec("normal", root.foreground, root.accent)
|
||||
: Border.none()))
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: 60 }
|
||||
|
||||
+13
-9
@@ -26,6 +26,7 @@ Item {
|
||||
property color background: Color.popups.background
|
||||
property color popupBorder: Color.popups.border
|
||||
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 int rowHeight: Style.spacing.controlHeight
|
||||
property int popupRowHeight: Style.spacing.popupRowHeight
|
||||
@@ -78,7 +79,7 @@ Item {
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: trigger
|
||||
width: parent.width
|
||||
height: root.rowHeight
|
||||
@@ -86,10 +87,10 @@ Item {
|
||||
|
||||
readonly property bool _focused: trigger.activeFocus
|
||||
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)
|
||||
border.color: Style.controlBorder(trigger._focused, trigger._hot, root.foreground, root.accent)
|
||||
border.width: Style.controlBorderWidth(trigger._focused, trigger._hot)
|
||||
borderSpec: _borderSpec
|
||||
|
||||
activeFocusOnTab: true
|
||||
|
||||
@@ -112,8 +113,8 @@ Item {
|
||||
anchors.left: parent.left
|
||||
anchors.right: chevron.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Style.spacing.controlPaddingX
|
||||
anchors.rightMargin: Style.spacing.md
|
||||
anchors.leftMargin: trigger.borderLeft + Style.spacing.controlPaddingX
|
||||
anchors.rightMargin: trigger.borderRight + Style.spacing.md
|
||||
text: root.currentLabel()
|
||||
color: root.foreground
|
||||
font.family: root.fontFamily
|
||||
@@ -125,7 +126,7 @@ Item {
|
||||
id: chevron
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.rightMargin: Style.spacing.controlGap
|
||||
anchors.rightMargin: trigger.borderRight + Style.spacing.controlGap
|
||||
text: ""
|
||||
color: Qt.darker(root.foreground, 1.2)
|
||||
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,
|
||||
root.popupRowHeight * 8 + 7 * Style.spacing.labelGap + Style.spacing.xxs)
|
||||
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
|
||||
|
||||
background: Rectangle {
|
||||
background: BorderSurface {
|
||||
color: root.background
|
||||
border.color: root.popupBorder
|
||||
border.width: Style.normalBorderWidth
|
||||
borderSpec: root.popupBorderSpec
|
||||
radius: Style.cornerRadius
|
||||
}
|
||||
|
||||
|
||||
@@ -285,15 +285,15 @@ PanelWindow {
|
||||
|
||||
// --- card ----------------------------------------------------------------
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: card
|
||||
x: root.cardOrigin.x
|
||||
y: root.cardOrigin.y
|
||||
width: root.contentWidth
|
||||
height: root.contentHeight
|
||||
color: Color.popups.background
|
||||
border.color: Color.popups.border
|
||||
border.width: Math.max(1, Style.space(2))
|
||||
borderSpec: Border.surfaceSpec("popups", "border", Color.popups.border, Math.max(1, Style.space(2)))
|
||||
padding: root.padding
|
||||
radius: Style.cornerRadius
|
||||
opacity: root.open || root.popoutSwitching ? 1.0 : 0
|
||||
|
||||
@@ -309,7 +309,10 @@ PanelWindow {
|
||||
Item {
|
||||
id: contentHolder
|
||||
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
|
||||
|
||||
Behavior on opacity {
|
||||
|
||||
+21
-19
@@ -37,6 +37,7 @@ Item {
|
||||
property color background: Color.popups.background
|
||||
property color popupBorder: Color.popups.border
|
||||
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 int rowHeight: Style.spacing.controlHeight
|
||||
property int popupRowHeight: Style.spacing.popupRowHeight
|
||||
@@ -266,7 +267,7 @@ Item {
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: trigger
|
||||
width: parent.width
|
||||
height: root.rowHeight
|
||||
@@ -274,10 +275,10 @@ Item {
|
||||
|
||||
readonly property bool _focused: trigger.activeFocus
|
||||
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)
|
||||
border.color: Style.controlBorder(trigger._focused, trigger._hot, root.foreground, root.accent)
|
||||
border.width: Style.controlBorderWidth(trigger._focused, trigger._hot)
|
||||
borderSpec: _borderSpec
|
||||
|
||||
activeFocusOnTab: true
|
||||
|
||||
@@ -300,8 +301,8 @@ Item {
|
||||
anchors.left: parent.left
|
||||
anchors.right: chevron.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Style.spacing.controlPaddingX
|
||||
anchors.rightMargin: Style.spacing.md
|
||||
anchors.leftMargin: trigger.borderLeft + Style.spacing.controlPaddingX
|
||||
anchors.rightMargin: trigger.borderRight + Style.spacing.md
|
||||
text: root.selectionLabel() || root.triggerLabel || root.noSelectionText
|
||||
color: root.selectionLabel() ? root.foreground : Qt.darker(root.foreground, 1.5)
|
||||
font.family: root.fontFamily
|
||||
@@ -313,7 +314,7 @@ Item {
|
||||
id: chevron
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.rightMargin: Style.spacing.controlGap
|
||||
anchors.rightMargin: trigger.borderRight + Style.spacing.controlGap
|
||||
text: ""
|
||||
color: Qt.darker(root.foreground, 1.2)
|
||||
font.family: root.fontFamily
|
||||
@@ -359,6 +360,10 @@ Item {
|
||||
// when there isn't room, otherwise the popup overflows the window.
|
||||
implicitHeight: Math.min(_availableBelow, _idealContent, _maxRowsHeight)
|
||||
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
|
||||
|
||||
Connections {
|
||||
@@ -369,10 +374,9 @@ Item {
|
||||
function onHeightChanged() { popup.reposition() }
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
background: BorderSurface {
|
||||
color: root.background
|
||||
border.color: root.popupBorder
|
||||
border.width: Style.normalBorderWidth
|
||||
borderSpec: root.popupBorderSpec
|
||||
radius: Style.cornerRadius
|
||||
}
|
||||
|
||||
@@ -432,7 +436,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: refreshButton
|
||||
visible: root.arrayFrom(root.optionsCommand).length > 0
|
||||
enabled: !root.loadingOptions
|
||||
@@ -442,10 +446,9 @@ Item {
|
||||
color: refreshHover.hovered
|
||||
? Style.hoverFillFor(root.foreground, root.accent)
|
||||
: Style.normalFillFor(root.foreground, root.accent)
|
||||
border.color: refreshHover.hovered
|
||||
? Style.hoverBorderFor(root.foreground, root.accent)
|
||||
: Style.normalBorderFor(root.foreground, root.accent)
|
||||
border.width: refreshHover.hovered ? Style.hoverBorderWidth : Style.normalBorderWidth
|
||||
borderSpec: refreshHover.hovered
|
||||
? Border.controlSpec("hover-cursor", root.foreground, root.accent)
|
||||
: Border.controlSpec("normal", root.foreground, root.accent)
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
@@ -550,17 +553,16 @@ Item {
|
||||
anchors.rightMargin: Style.spacing.controlPaddingX
|
||||
spacing: Style.spacing.rowGap
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: checkbox
|
||||
width: Style.space(16)
|
||||
height: Style.space(16)
|
||||
radius: Math.max(2, Style.cornerRadius / 2)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: selected ? Style.selectedFillFor(root.foreground, root.accent) : "transparent"
|
||||
border.color: selected
|
||||
? Style.selectedBorderFor(root.foreground, root.accent)
|
||||
: Style.normalBorderFor(root.foreground, root.accent)
|
||||
border.width: selected ? Style.selectedBorderWidth : Style.normalBorderWidth
|
||||
borderSpec: selected
|
||||
? Border.controlSpec("selected", root.foreground, root.accent)
|
||||
: Border.controlSpec("normal", root.foreground, root.accent)
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
|
||||
@@ -44,15 +44,20 @@ Column {
|
||||
font.family: root.fontFamily
|
||||
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)
|
||||
|
||||
background: Rectangle {
|
||||
readonly property bool _focused: spin.activeFocus
|
||||
readonly property bool _hot: root._hovered || root.hasCursor
|
||||
|
||||
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)
|
||||
background: BorderSurface {
|
||||
color: Style.controlFill(spin._focused, spin._hot, root.foreground, root.accent)
|
||||
borderSpec: spin._borderSpec
|
||||
radius: Style.cornerRadius
|
||||
|
||||
HoverHandler {
|
||||
|
||||
@@ -24,7 +24,7 @@ import qs.Commons
|
||||
// Use this when a PanelActionButton is itself the cursor target (rather
|
||||
// than living inside a CursorSurface row). Emits `hovered(bool)` on
|
||||
// pointer enter/leave so the panel can update its cursor state to match.
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: root
|
||||
|
||||
property string iconText: ""
|
||||
@@ -53,22 +53,18 @@ Rectangle {
|
||||
|
||||
readonly property bool _showFocusRing: focusable && activeFocus
|
||||
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
|
||||
? Style.focusFillFor(hoverColor, hoverColor)
|
||||
: (_hot
|
||||
? Style.hoverFillFor(hoverColor, hoverColor)
|
||||
: "transparent")
|
||||
border.width: _showFocusRing
|
||||
? Style.focusBorderWidth
|
||||
: (_hot && bordered
|
||||
? Style.hoverBorderWidth
|
||||
: (bordered ? Style.normalBorderWidth : 0))
|
||||
border.color: _showFocusRing
|
||||
? Style.focusBorderFor(hoverColor, hoverColor)
|
||||
: (_hot && bordered
|
||||
? Style.hoverBorderFor(hoverColor, hoverColor)
|
||||
: Style.normalBorderFor(foreground, Color.accent))
|
||||
borderSpec: _borderSpec
|
||||
|
||||
Behavior on color { ColorAnimation { duration: 60 } }
|
||||
|
||||
|
||||
@@ -56,15 +56,14 @@ Item {
|
||||
height: 1
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: detailPill
|
||||
visible: root.detail !== ""
|
||||
implicitWidth: detailText.implicitWidth + Style.space(10)
|
||||
implicitHeight: detailText.implicitHeight + Style.space(4)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: "transparent"
|
||||
border.color: Style.normalBorderFor(root.foreground)
|
||||
border.width: Style.normalBorderWidth
|
||||
borderSpec: Border.controlSpec("normal", root.foreground, Color.accent)
|
||||
radius: Style.cornerRadius
|
||||
|
||||
Text {
|
||||
|
||||
@@ -55,14 +55,13 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: knob
|
||||
width: root.knobSize
|
||||
height: root.knobSize
|
||||
radius: root.knobSize / 2
|
||||
color: root.knobColor
|
||||
border.color: root.bar ? root.bar.background : "#101315"
|
||||
border.width: Math.max(1, Style.space(2))
|
||||
borderSpec: Border.flat(root.bar ? root.bar.background : "#101315", Math.max(1, Style.space(2)))
|
||||
anchors.verticalCenter: track.verticalCenter
|
||||
x: Math.max(0, Math.min(track.width - width, track.width * root.progress - width / 2))
|
||||
scale: root._hot ? 1.15 : 1.0
|
||||
|
||||
@@ -24,13 +24,14 @@ ToolTip {
|
||||
property string fontFamily: Style.font.family
|
||||
property real fontSize: Style.font.bodySmall
|
||||
|
||||
readonly property var panelBorderSpec: Border.localOrSurfaceSpec("tooltip", "border", panelBorder, Color.tooltip.border, Style.normalBorderWidth)
|
||||
|
||||
delay: 400
|
||||
padding: 0
|
||||
|
||||
background: Rectangle {
|
||||
background: BorderSurface {
|
||||
color: root.panelBackground
|
||||
border.color: root.panelBorder
|
||||
border.width: Style.normalBorderWidth
|
||||
borderSpec: root.panelBorderSpec
|
||||
radius: Style.cornerRadius
|
||||
}
|
||||
|
||||
@@ -39,9 +40,9 @@ ToolTip {
|
||||
color: root.panelForeground
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: root.fontSize
|
||||
leftPadding: Style.spacing.controlPaddingX
|
||||
rightPadding: Style.spacing.controlPaddingX
|
||||
topPadding: Style.spacing.controlPaddingY
|
||||
bottomPadding: Style.spacing.controlPaddingY
|
||||
leftPadding: Border.left(root.panelBorderSpec) + Style.spacing.controlPaddingX
|
||||
rightPadding: Border.right(root.panelBorderSpec) + Style.spacing.controlPaddingX
|
||||
topPadding: Border.top(root.panelBorderSpec) + Style.spacing.controlPaddingY
|
||||
bottomPadding: Border.bottom(root.panelBorderSpec) + Style.spacing.controlPaddingY
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ PopupWindow {
|
||||
property int contentWidth: Style.space(280)
|
||||
property int contentHeight: Style.space(200)
|
||||
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 centerOnBar: false
|
||||
// "click" — uses HyprlandFocusGrab so clicking outside dismisses the popup.
|
||||
@@ -145,12 +146,12 @@ PopupWindow {
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: card
|
||||
anchors.fill: parent
|
||||
color: Color.popups.background
|
||||
border.color: root.borderColor
|
||||
border.width: Math.max(1, Style.space(2))
|
||||
borderSpec: root.borderSpec
|
||||
padding: root.padding
|
||||
radius: Style.cornerRadius
|
||||
opacity: root.open ? 1.0 : 0
|
||||
|
||||
@@ -161,7 +162,10 @@ PopupWindow {
|
||||
Item {
|
||||
id: contentHolder
|
||||
anchors.fill: parent
|
||||
anchors.margins: root.padding
|
||||
anchors.topMargin: card.contentTopInset
|
||||
anchors.rightMargin: card.contentRightInset
|
||||
anchors.bottomMargin: card.contentBottomInset
|
||||
anchors.leftMargin: card.contentLeftInset
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
|
||||
@@ -29,6 +29,7 @@ Item {
|
||||
property color background: Color.popups.background
|
||||
property color popupBorder: Color.popups.border
|
||||
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 int rowHeight: Style.spacing.controlHeight
|
||||
property int popupRowHeight: Style.spacing.popupRowHeight
|
||||
@@ -100,7 +101,7 @@ Item {
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: trigger
|
||||
width: parent.width
|
||||
height: root.rowHeight
|
||||
@@ -108,10 +109,10 @@ Item {
|
||||
|
||||
readonly property bool _focused: trigger.activeFocus
|
||||
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)
|
||||
border.color: Style.controlBorder(trigger._focused, trigger._hot, root.foreground, root.accent)
|
||||
border.width: Style.controlBorderWidth(trigger._focused, trigger._hot)
|
||||
borderSpec: _borderSpec
|
||||
|
||||
activeFocusOnTab: true
|
||||
|
||||
@@ -134,8 +135,8 @@ Item {
|
||||
anchors.left: parent.left
|
||||
anchors.right: chevron.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Style.spacing.controlPaddingX
|
||||
anchors.rightMargin: Style.spacing.md
|
||||
anchors.leftMargin: trigger.borderLeft + Style.spacing.controlPaddingX
|
||||
anchors.rightMargin: trigger.borderRight + Style.spacing.md
|
||||
text: root.currentLabel() || root.triggerLabel || root.placeholderText
|
||||
color: (root.currentLabel() || root.triggerLabel) ? root.foreground : Qt.darker(root.foreground, 1.5)
|
||||
font.family: root.fontFamily
|
||||
@@ -147,7 +148,7 @@ Item {
|
||||
id: chevron
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.rightMargin: Style.spacing.controlGap
|
||||
anchors.rightMargin: trigger.borderRight + Style.spacing.controlGap
|
||||
text: ""
|
||||
color: Qt.darker(root.foreground, 1.2)
|
||||
font.family: root.fontFamily
|
||||
@@ -172,12 +173,15 @@ Item {
|
||||
Math.min(resultList.contentHeight + Style.space(50),
|
||||
root.popupRowHeight * 6 + 5 * Style.spacing.labelGap + Style.space(50)))
|
||||
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
|
||||
|
||||
background: Rectangle {
|
||||
background: BorderSurface {
|
||||
color: root.background
|
||||
border.color: root.popupBorder
|
||||
border.width: Style.normalBorderWidth
|
||||
borderSpec: root.popupBorderSpec
|
||||
radius: Style.cornerRadius
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ TextField {
|
||||
|
||||
readonly property bool _focused: activeFocus
|
||||
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
|
||||
font.family: Style.font.family
|
||||
@@ -43,15 +44,14 @@ TextField {
|
||||
selectedTextColor: foreground
|
||||
placeholderTextColor: Qt.darker(foreground, 1.6)
|
||||
|
||||
leftPadding: horizontalPadding
|
||||
rightPadding: horizontalPadding
|
||||
topPadding: verticalPadding
|
||||
bottomPadding: verticalPadding
|
||||
leftPadding: horizontalPadding + Border.left(_borderSpec)
|
||||
rightPadding: horizontalPadding + Border.right(_borderSpec)
|
||||
topPadding: verticalPadding + Border.top(_borderSpec)
|
||||
bottomPadding: verticalPadding + Border.bottom(_borderSpec)
|
||||
|
||||
background: Rectangle {
|
||||
background: BorderSurface {
|
||||
color: Style.controlFill(root._focused, root._hot, root.foreground, root.accent)
|
||||
border.color: Style.controlBorder(root._focused, root._hot, root.foreground, root.accent)
|
||||
border.width: Style.controlBorderWidth(root._focused, root._hot)
|
||||
borderSpec: root._borderSpec
|
||||
radius: Style.cornerRadius
|
||||
}
|
||||
}
|
||||
|
||||
+9
-10
@@ -12,7 +12,7 @@ import qs.Commons
|
||||
// `rounded` auto-detects from Style.cornerRadius so the switch follows
|
||||
// the theme: pill shape when Hyprland corners are rounded, square on sharp.
|
||||
// Callers can override per-instance.
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: root
|
||||
|
||||
property string label: ""
|
||||
@@ -52,10 +52,10 @@ Rectangle {
|
||||
radius: Style.cornerRadius
|
||||
|
||||
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)
|
||||
border.color: Style.controlBorder(activeFocus, _hot, foreground, accent)
|
||||
border.width: Style.controlBorderWidth(activeFocus, _hot)
|
||||
borderSpec: _borderSpec
|
||||
|
||||
Behavior on color { ColorAnimation { duration: 100 } }
|
||||
|
||||
@@ -64,8 +64,8 @@ Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Style.spacing.rowPaddingX
|
||||
anchors.rightMargin: Style.spacing.rowPaddingX
|
||||
anchors.leftMargin: root.borderLeft + Style.spacing.rowPaddingX
|
||||
anchors.rightMargin: root.borderRight + Style.spacing.rowPaddingX
|
||||
spacing: Style.spacing.rowPaddingX
|
||||
|
||||
Column {
|
||||
@@ -94,7 +94,7 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: track
|
||||
width: root.trackWidth
|
||||
height: root.trackHeight
|
||||
@@ -102,10 +102,9 @@ Rectangle {
|
||||
color: root.checked
|
||||
? Style.selectedFillFor(root.foreground, root.accent)
|
||||
: Style.normalFillFor(root.foreground, root.accent)
|
||||
border.color: root.checked
|
||||
? Style.selectedBorderFor(root.foreground, root.accent)
|
||||
: Style.normalBorderFor(root.foreground, root.accent)
|
||||
border.width: root.checked ? Style.selectedBorderWidth : Style.normalBorderWidth
|
||||
borderSpec: root.checked
|
||||
? Border.controlSpec("selected", root.foreground, root.accent)
|
||||
: Border.controlSpec("normal", root.foreground, root.accent)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Behavior on color { ColorAnimation { duration: 120 } }
|
||||
|
||||
@@ -2,6 +2,8 @@ module qs.Ui
|
||||
|
||||
BarIndicator 1.0 BarIndicator.qml
|
||||
BarWidget 1.0 BarWidget.qml
|
||||
BorderOverlay 1.0 BorderOverlay.qml
|
||||
BorderSurface 1.0 BorderSurface.qml
|
||||
Button 1.0 Button.qml
|
||||
ButtonGroup 1.0 ButtonGroup.qml
|
||||
ConfirmDialog 1.0 ConfirmDialog.qml
|
||||
|
||||
@@ -798,13 +798,12 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: tooltipBubble
|
||||
implicitWidth: tooltipLabel.implicitWidth + 20
|
||||
implicitHeight: tooltipLabel.implicitHeight + 14
|
||||
color: Color.tooltip.background
|
||||
border.color: Color.tooltip.border
|
||||
border.width: 1
|
||||
borderSpec: Border.surfaceSpec("tooltip", "border", Color.tooltip.border, 1)
|
||||
radius: Style.cornerRadius
|
||||
|
||||
Text {
|
||||
@@ -904,11 +903,10 @@ Item {
|
||||
width: ghostWindow.ghostWidth + ghostWindow.ghostPadding * 2
|
||||
height: ghostWindow.ghostHeight + ghostWindow.ghostPadding * 2
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
anchors.fill: parent
|
||||
color: root.transparent ? "transparent" : root.background
|
||||
border.color: root.barForeground
|
||||
border.width: 1
|
||||
borderSpec: Border.flat(root.barForeground, 1)
|
||||
radius: Math.min(Style.cornerRadius, height / 2)
|
||||
opacity: root.transparent ? 0.45 : 0.94
|
||||
}
|
||||
@@ -1234,13 +1232,12 @@ Item {
|
||||
|
||||
HoverHandler { id: moduleHover }
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
visible: slot.dragSource
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.space(1)
|
||||
color: root.transparent ? "transparent" : root.background
|
||||
border.color: root.barForeground
|
||||
border.width: 1
|
||||
borderSpec: Border.flat(root.barForeground, 1)
|
||||
radius: Math.min(Style.cornerRadius, height / 2)
|
||||
opacity: root.transparent ? 0.22 : 0.32
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ Item {
|
||||
property color background: Color.menu.background
|
||||
property color foreground: Color.menu.text
|
||||
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 selectedBackground: Color.menu.selectedBackground
|
||||
property color selectedText: Color.menu.selectedText
|
||||
@@ -275,15 +276,15 @@ Item {
|
||||
onClicked: root.close()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: card
|
||||
width: root.cardWidth
|
||||
height: root.cardHeight
|
||||
radius: root.cornerRadius
|
||||
anchors.centerIn: parent
|
||||
color: root.background
|
||||
border.color: root.border
|
||||
border.width: Math.max(1, Style.space(2))
|
||||
borderSpec: root.borderSpec
|
||||
padding: root.contentMargin
|
||||
|
||||
MouseArea { anchors.fill: parent; onClicked: {} }
|
||||
|
||||
@@ -357,7 +358,10 @@ Item {
|
||||
|
||||
Column {
|
||||
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
|
||||
|
||||
Rectangle {
|
||||
@@ -365,7 +369,6 @@ Item {
|
||||
height: root.headerHeight
|
||||
radius: root.cornerRadius
|
||||
color: "transparent"
|
||||
border.width: 0
|
||||
|
||||
Text {
|
||||
anchors.left: parent.left
|
||||
@@ -415,7 +418,6 @@ Item {
|
||||
height: root.rowHeight
|
||||
radius: root.cornerRadius
|
||||
color: hasCursor ? root.selectedBackground : "transparent"
|
||||
border.width: 0
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -376,13 +376,12 @@ Item {
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
width: parent.width
|
||||
implicitHeight: conventionsCol.implicitHeight + Style.spacing.rowPaddingX * 2
|
||||
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04)
|
||||
radius: Style.cornerRadius
|
||||
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10)
|
||||
border.width: 1
|
||||
borderSpec: Border.flat(Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10), 1)
|
||||
|
||||
Column {
|
||||
id: conventionsCol
|
||||
@@ -458,13 +457,12 @@ Item {
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
width: parent.width
|
||||
implicitHeight: typeCol.implicitHeight + Style.spacing.rowPaddingX * 2
|
||||
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04)
|
||||
radius: Style.cornerRadius
|
||||
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10)
|
||||
border.width: 1
|
||||
borderSpec: Border.flat(Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10), 1)
|
||||
|
||||
Column {
|
||||
id: typeCol
|
||||
@@ -676,13 +674,12 @@ Item {
|
||||
font.pixelSize: Style.font.caption
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
width: parent.width
|
||||
implicitHeight: shCol.implicitHeight + Style.spacing.rowPaddingX * 2
|
||||
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04)
|
||||
radius: Style.cornerRadius
|
||||
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10)
|
||||
border.width: 1
|
||||
borderSpec: Border.flat(Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10), 1)
|
||||
|
||||
Column {
|
||||
id: shCol
|
||||
@@ -732,13 +729,12 @@ Item {
|
||||
font.pixelSize: Style.font.caption
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
width: parent.width
|
||||
implicitHeight: sepCol.implicitHeight + Style.spacing.rowPaddingX * 2
|
||||
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04)
|
||||
radius: Style.cornerRadius
|
||||
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10)
|
||||
border.width: 1
|
||||
borderSpec: Border.flat(Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10), 1)
|
||||
|
||||
Column {
|
||||
id: sepCol
|
||||
@@ -777,13 +773,12 @@ Item {
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
width: parent.width
|
||||
implicitHeight: csCol.implicitHeight + Style.spacing.rowPaddingX * 2
|
||||
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04)
|
||||
radius: Style.cornerRadius
|
||||
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10)
|
||||
border.width: 1
|
||||
borderSpec: Border.flat(Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10), 1)
|
||||
|
||||
Column {
|
||||
id: csCol
|
||||
@@ -862,13 +857,12 @@ Item {
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
width: parent.width
|
||||
implicitHeight: buttonRow.implicitHeight + Style.spacing.rowPaddingX * 2
|
||||
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04)
|
||||
radius: Style.cornerRadius
|
||||
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10)
|
||||
border.width: 1
|
||||
borderSpec: Border.flat(Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10), 1)
|
||||
|
||||
Row {
|
||||
id: buttonRow
|
||||
@@ -1019,13 +1013,12 @@ Item {
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
width: parent.width
|
||||
implicitHeight: choiceRow.implicitHeight + Style.spacing.rowPaddingX * 2
|
||||
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04)
|
||||
radius: Style.cornerRadius
|
||||
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10)
|
||||
border.width: 1
|
||||
borderSpec: Border.flat(Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10), 1)
|
||||
|
||||
ButtonGroup {
|
||||
id: choiceRow
|
||||
@@ -1070,13 +1063,12 @@ Item {
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
width: parent.width
|
||||
implicitHeight: pabCol.implicitHeight + Style.spacing.rowPaddingX * 2
|
||||
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04)
|
||||
radius: Style.cornerRadius
|
||||
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10)
|
||||
border.width: 1
|
||||
borderSpec: Border.flat(Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10), 1)
|
||||
|
||||
Row {
|
||||
id: pabCol
|
||||
@@ -1162,7 +1154,7 @@ Item {
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: tipSwatch
|
||||
width: Style.space(140)
|
||||
height: Style.space(36)
|
||||
@@ -1170,10 +1162,9 @@ Item {
|
||||
color: tipMouse.containsMouse || focused
|
||||
? Style.hoverFillFor(root.foreground, root.accent)
|
||||
: Style.normalFillFor(root.foreground, root.accent)
|
||||
border.color: focused
|
||||
? Style.hoverBorderFor(root.foreground, root.accent)
|
||||
: Style.normalBorderFor(root.foreground, root.accent)
|
||||
border.width: focused ? Style.hoverBorderWidth : Style.normalBorderWidth
|
||||
borderSpec: focused
|
||||
? Border.controlSpec("hover-cursor", root.foreground, root.accent)
|
||||
: Border.controlSpec("normal", root.foreground, root.accent)
|
||||
radius: Style.cornerRadius
|
||||
onFocusedChanged: if (focused) root.ensureCursorVisible(this)
|
||||
|
||||
@@ -1306,13 +1297,12 @@ Item {
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
width: parent.width
|
||||
implicitHeight: tfCol.implicitHeight + Style.spacing.rowPaddingX * 2
|
||||
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04)
|
||||
radius: Style.cornerRadius
|
||||
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10)
|
||||
border.width: 1
|
||||
borderSpec: Border.flat(Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10), 1)
|
||||
|
||||
Column {
|
||||
id: tfCol
|
||||
@@ -1390,12 +1380,11 @@ Item {
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
width: parent.width
|
||||
implicitHeight: numberDemo.implicitHeight + Style.spacing.rowPaddingX * 2
|
||||
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04)
|
||||
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.12)
|
||||
border.width: 1
|
||||
borderSpec: Border.flat(Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.12), 1)
|
||||
radius: Style.cornerRadius
|
||||
|
||||
NumberField {
|
||||
@@ -1502,13 +1491,12 @@ Item {
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
width: parent.width
|
||||
implicitHeight: ddCol.implicitHeight + Style.spacing.rowPaddingX * 2
|
||||
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04)
|
||||
radius: Style.cornerRadius
|
||||
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10)
|
||||
border.width: 1
|
||||
borderSpec: Border.flat(Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10), 1)
|
||||
|
||||
Column {
|
||||
id: ddCol
|
||||
@@ -1558,13 +1546,12 @@ Item {
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
width: parent.width
|
||||
implicitHeight: sddCol.implicitHeight + Style.spacing.rowPaddingX * 2
|
||||
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04)
|
||||
radius: Style.cornerRadius
|
||||
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10)
|
||||
border.width: 1
|
||||
borderSpec: Border.flat(Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10), 1)
|
||||
|
||||
Column {
|
||||
id: sddCol
|
||||
@@ -1630,13 +1617,12 @@ Item {
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
width: parent.width
|
||||
implicitHeight: composedCol.implicitHeight + Style.spacing.rowPaddingX * 2
|
||||
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.04)
|
||||
radius: Style.cornerRadius
|
||||
border.color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10)
|
||||
border.width: 1
|
||||
borderSpec: Border.flat(Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.10), 1)
|
||||
|
||||
Column {
|
||||
id: composedCol
|
||||
|
||||
@@ -3,6 +3,7 @@ import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import QtQuick
|
||||
import qs.Commons
|
||||
import qs.Ui
|
||||
import "EmojiSearch.js" as EmojiSearch
|
||||
|
||||
Item {
|
||||
@@ -25,6 +26,7 @@ Item {
|
||||
property color background: Color.menu.background
|
||||
property color foreground: Color.menu.text
|
||||
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 selectedBackground: Color.menu.selectedBackground
|
||||
property color selectedText: Color.menu.selectedText
|
||||
@@ -175,15 +177,15 @@ Item {
|
||||
onClicked: root.dismiss()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: card
|
||||
width: root.cardWidth
|
||||
height: root.cardHeight
|
||||
radius: root.cornerRadius
|
||||
anchors.centerIn: parent
|
||||
color: root.background
|
||||
border.color: root.border
|
||||
border.width: Math.max(1, Style.space(2))
|
||||
borderSpec: root.borderSpec
|
||||
padding: root.contentMargin
|
||||
|
||||
MouseArea { anchors.fill: parent; onClicked: {} }
|
||||
|
||||
@@ -232,7 +234,10 @@ Item {
|
||||
|
||||
Column {
|
||||
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
|
||||
|
||||
Rectangle {
|
||||
@@ -240,7 +245,6 @@ Item {
|
||||
height: root.headerHeight
|
||||
radius: root.cornerRadius
|
||||
color: "transparent"
|
||||
border.width: 0
|
||||
|
||||
Text {
|
||||
anchors.left: parent.left
|
||||
@@ -278,7 +282,6 @@ Item {
|
||||
height: root.cellHeight
|
||||
radius: root.cornerRadius
|
||||
color: hasCursor ? root.selectedBackground : "transparent"
|
||||
border.width: 0
|
||||
|
||||
Text {
|
||||
text: parent.emoji
|
||||
|
||||
@@ -37,9 +37,12 @@ Item {
|
||||
property color background: Color.launcher.background
|
||||
property color foreground: Color.launcher.text
|
||||
property color border: Color.launcher.border
|
||||
property var borderSpec: Border.surfaceSpec("launcher", "border", border, 2)
|
||||
property color scrim: Color.launcher.scrim
|
||||
property color selectedBackground: Color.launcher.selectedBackground
|
||||
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 int cardWidth: 644
|
||||
@@ -342,15 +345,15 @@ Item {
|
||||
onClicked: root.dismiss()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: card
|
||||
width: Math.min(root.cardWidth, panel.width - Style.gapsOut * 2)
|
||||
height: Math.min(root.cardHeight, panel.height - Style.gapsOut * 2)
|
||||
radius: Style.cornerRadius
|
||||
anchors.centerIn: parent
|
||||
color: root.background
|
||||
border.color: root.border
|
||||
border.width: 2
|
||||
borderSpec: root.borderSpec
|
||||
padding: root.contentMargin
|
||||
clip: true
|
||||
|
||||
MouseArea { anchors.fill: parent; onClicked: {} }
|
||||
@@ -435,7 +438,10 @@ Item {
|
||||
|
||||
Column {
|
||||
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
|
||||
|
||||
Rectangle {
|
||||
@@ -471,7 +477,7 @@ Item {
|
||||
spacing: 0
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
delegate: Rectangle {
|
||||
delegate: BorderSurface {
|
||||
id: row
|
||||
required property int index
|
||||
required property string name
|
||||
@@ -484,12 +490,12 @@ Item {
|
||||
height: root.rowHeight
|
||||
radius: 0
|
||||
color: row.hasCursor ? root.selectedBackground : "transparent"
|
||||
border.width: 0
|
||||
borderSpec: row.hasCursor ? root.selectedBorderSpec : Border.none()
|
||||
|
||||
Item {
|
||||
id: iconSlot
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 14
|
||||
anchors.leftMargin: row.borderLeft + 14
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: root.iconSlotWidth
|
||||
height: parent.height
|
||||
@@ -519,7 +525,7 @@ Item {
|
||||
anchors.left: iconSlot.right
|
||||
anchors.leftMargin: 14
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 14
|
||||
anchors.rightMargin: row.borderRight + 14
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: row.name
|
||||
color: row.hasCursor ? root.selectedText : root.foreground
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import qs.Commons
|
||||
import qs.Ui
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -26,6 +27,9 @@ Item {
|
||||
readonly property int fieldFontSize: Style.font.heading
|
||||
readonly property int passwordDotFontSize: Math.round(fieldFontSize * 1.3)
|
||||
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 passwordTextEdited(string password)
|
||||
@@ -97,22 +101,23 @@ Item {
|
||||
onPositionChanged: root.wakeRequested()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: inputField
|
||||
width: root.fieldWidth
|
||||
height: root.fieldHeight
|
||||
anchors.centerIn: parent
|
||||
color: Color.lock.background
|
||||
border.color: root.failureMessage.length > 0 ? Color.lock.borderError : (root.authenticatingPassword ? Color.lock.borderActive : Color.lock.border)
|
||||
border.width: root.outlineThickness
|
||||
borderSpec: root.inputBorderSpec
|
||||
radius: Style.cornerRadius
|
||||
clip: true
|
||||
|
||||
TextInput {
|
||||
id: passwordInput
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: root.outlineThickness + 18
|
||||
anchors.rightMargin: root.outlineThickness + 18
|
||||
anchors.topMargin: inputField.borderTop
|
||||
anchors.rightMargin: inputField.borderRight + 18
|
||||
anchors.bottomMargin: inputField.borderBottom
|
||||
anchors.leftMargin: inputField.borderLeft + 18
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
horizontalAlignment: TextInput.AlignHCenter
|
||||
activeFocusOnPress: true
|
||||
|
||||
+17
-11
@@ -3,6 +3,7 @@ import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import QtQuick
|
||||
import qs.Commons
|
||||
import qs.Ui
|
||||
import "MenuModel.js" as MenuModel
|
||||
|
||||
Item {
|
||||
@@ -77,9 +78,12 @@ Item {
|
||||
property color background: Color.menu.background
|
||||
property color foreground: Color.menu.text
|
||||
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 selectedBackground: Color.menu.selectedBackground
|
||||
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
|
||||
property int contentMargin: Style.spacing.panelPadding
|
||||
property int headerHeight: Math.max(Style.space(34), Style.font.title + Style.spacing.controlPaddingY * 2)
|
||||
@@ -787,15 +791,15 @@ Item {
|
||||
onClicked: root.cancel()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: card
|
||||
width: root.cardWidth
|
||||
height: root.cardHeight
|
||||
radius: root.cornerRadius
|
||||
anchors.centerIn: parent
|
||||
color: root.background
|
||||
border.color: root.border
|
||||
border.width: Math.max(1, Style.space(2))
|
||||
borderSpec: root.borderSpec
|
||||
padding: root.contentMargin
|
||||
|
||||
MouseArea { anchors.fill: parent; onClicked: {} }
|
||||
|
||||
@@ -842,7 +846,10 @@ Item {
|
||||
|
||||
Column {
|
||||
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
|
||||
|
||||
Rectangle {
|
||||
@@ -850,7 +857,6 @@ Item {
|
||||
height: root.headerHeight
|
||||
radius: root.cornerRadius
|
||||
color: "transparent"
|
||||
border.width: 0
|
||||
|
||||
Text {
|
||||
anchors.left: parent.left
|
||||
@@ -898,7 +904,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
delegate: BorderSurface {
|
||||
id: row
|
||||
required property int index
|
||||
required property string itemId
|
||||
@@ -918,7 +924,7 @@ Item {
|
||||
height: root.rowHeightForDetail(row.detail)
|
||||
radius: root.cornerRadius
|
||||
color: row.hasCursor ? root.selectedBackground : "transparent"
|
||||
border.width: 0
|
||||
borderSpec: row.hasCursor ? root.selectedBorderSpec : Border.none()
|
||||
|
||||
Rectangle {
|
||||
visible: false
|
||||
@@ -927,7 +933,7 @@ Item {
|
||||
radius: Math.min(root.cornerRadius, Style.space(4))
|
||||
color: root.selectedBackground
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Style.space(8)
|
||||
anchors.leftMargin: row.borderLeft + Style.space(8)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
@@ -942,14 +948,14 @@ Item {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
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
|
||||
}
|
||||
|
||||
Column {
|
||||
id: contentColumn
|
||||
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.rightMargin: Style.space(6)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -982,7 +988,7 @@ Item {
|
||||
id: trail
|
||||
width: Style.space(14)
|
||||
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
|
||||
spacing: 0
|
||||
|
||||
|
||||
@@ -1055,7 +1055,7 @@ BarWidget {
|
||||
}
|
||||
}
|
||||
|
||||
component SectionCard: Rectangle {
|
||||
component SectionCard: BorderSurface {
|
||||
id: section
|
||||
property string title: ""
|
||||
property string subtitle: ""
|
||||
@@ -1064,17 +1064,20 @@ BarWidget {
|
||||
|
||||
Layout.fillWidth: true
|
||||
color: card
|
||||
border.color: Qt.rgba(foreground.r, foreground.g, foreground.b, 0.05)
|
||||
border.width: 1
|
||||
borderSpec: Border.flat(Qt.rgba(foreground.r, foreground.g, foreground.b, 0.05), 1)
|
||||
padding: 12
|
||||
radius: Style.cornerRadius
|
||||
implicitHeight: body.implicitHeight + 22
|
||||
implicitHeight: body.implicitHeight + contentTopInset + contentBottomInset
|
||||
|
||||
ColumnLayout {
|
||||
id: body
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
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
|
||||
|
||||
PanelSectionHeader {
|
||||
|
||||
@@ -130,14 +130,13 @@ BarWidget {
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: dndPill
|
||||
Layout.preferredHeight: Math.max(Style.space(24), Style.font.bodySmall + Style.spacing.controlPaddingY * 2)
|
||||
Layout.preferredWidth: dndLabel.implicitWidth + dndGlyph.implicitWidth + Style.space(18)
|
||||
radius: Math.min(Style.space(12), root.cardRadius + Style.space(6))
|
||||
color: dndOn ? root.colAccent : root.colSurface
|
||||
border.color: dndOn ? root.colAccent : root.colBorder
|
||||
border.width: Style.normalBorderWidth
|
||||
borderSpec: Border.flat(dndOn ? root.colAccent : root.colBorder, Style.normalBorderWidth)
|
||||
|
||||
readonly property bool dndOn: !!root.notificationService && root.notificationService.doNotDisturb
|
||||
|
||||
@@ -228,13 +227,12 @@ BarWidget {
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
Layout.preferredWidth: actionLabel.implicitWidth + Style.space(16)
|
||||
Layout.preferredHeight: Math.max(Style.space(22), Style.font.bodySmall + Style.spacing.controlPaddingY * 2)
|
||||
radius: Math.min(Style.space(6), root.cardRadius)
|
||||
color: actionArea.containsMouse ? root.colBorder : "transparent"
|
||||
border.color: root.colBorder
|
||||
border.width: Style.normalBorderWidth
|
||||
borderSpec: Border.flat(root.colBorder, Style.normalBorderWidth)
|
||||
|
||||
Text {
|
||||
id: actionLabel
|
||||
@@ -272,7 +270,7 @@ BarWidget {
|
||||
: (onPending ? root.notificationService.pendingModel : root.notificationService.pastModel)
|
||||
visible: count > 0
|
||||
|
||||
delegate: Rectangle {
|
||||
delegate: BorderSurface {
|
||||
id: rowCard
|
||||
required property int index
|
||||
required property string app
|
||||
@@ -293,8 +291,7 @@ BarWidget {
|
||||
implicitHeight: rowContent.implicitHeight + Style.spacing.panelGap
|
||||
radius: root.cardRadius
|
||||
color: "transparent"
|
||||
border.color: root.colBorder
|
||||
border.width: Style.normalBorderWidth
|
||||
borderSpec: Border.flat(root.colBorder, Style.normalBorderWidth)
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
@@ -307,8 +304,8 @@ BarWidget {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Style.space(12)
|
||||
anchors.rightMargin: Style.space(12)
|
||||
anchors.leftMargin: rowCard.borderLeft + Style.space(12)
|
||||
anchors.rightMargin: rowCard.borderRight + Style.space(12)
|
||||
spacing: Style.space(10)
|
||||
|
||||
Item {
|
||||
|
||||
@@ -6,9 +6,10 @@ import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Ui
|
||||
import "../NotificationLogic.js" as NotificationLogic
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: root
|
||||
|
||||
property string app: ""
|
||||
@@ -48,6 +49,7 @@ Rectangle {
|
||||
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 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) {
|
||||
return NotificationLogic.sanitizeBody(s, app, appIcon)
|
||||
@@ -62,13 +64,12 @@ Rectangle {
|
||||
}
|
||||
|
||||
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.
|
||||
implicitHeight: mainColumn.implicitHeight + border.width * 2
|
||||
implicitHeight: mainColumn.implicitHeight + borderTop + borderBottom
|
||||
radius: cornerRadius
|
||||
color: Color.notifications.background
|
||||
border.color: Color.notifications.border
|
||||
border.width: Math.max(1, Style.space(2))
|
||||
borderSpec: cardBorderSpec
|
||||
clip: true
|
||||
|
||||
HoverHandler { id: hoverTracker }
|
||||
@@ -86,9 +87,9 @@ Rectangle {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: root.border.width
|
||||
anchors.leftMargin: root.border.width
|
||||
anchors.rightMargin: root.border.width
|
||||
anchors.topMargin: root.borderTop
|
||||
anchors.leftMargin: root.borderLeft
|
||||
anchors.rightMargin: root.borderRight
|
||||
spacing: 0
|
||||
|
||||
// Text content.
|
||||
|
||||
@@ -3,6 +3,7 @@ import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Ui
|
||||
import "OsdModel.js" as OsdModel
|
||||
|
||||
Item {
|
||||
@@ -79,7 +80,7 @@ Item {
|
||||
// never blocks clicks to the desktop below it.
|
||||
mask: Region {}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: card
|
||||
width: root.mediaOsd ? root.mediaCardWidth : root.cardWidth
|
||||
height: Math.max(Style.space(68), Style.font.displayLarge + Style.spacing.panelGap)
|
||||
@@ -87,15 +88,16 @@ Item {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: Style.space(67)
|
||||
color: Util.alpha(Color.background, 0.97)
|
||||
border.color: Color.popups.border
|
||||
border.width: Math.max(1, Style.space(2))
|
||||
borderSpec: Border.surfaceSpec("popups", "border", Color.popups.border, Math.max(1, Style.space(2)))
|
||||
radius: Style.cornerRadius
|
||||
opacity: root.opened ? 1 : 0
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Style.space(16)
|
||||
anchors.rightMargin: Style.space(16)
|
||||
anchors.topMargin: card.borderTop
|
||||
anchors.rightMargin: card.borderRight + Style.space(16)
|
||||
anchors.bottomMargin: card.borderBottom
|
||||
anchors.leftMargin: card.borderLeft + Style.space(16)
|
||||
spacing: Style.space(16)
|
||||
Text {
|
||||
width: Style.space(28)
|
||||
|
||||
@@ -741,15 +741,14 @@ Panel {
|
||||
|
||||
Item { width: Math.max(0, parent.width - parent.children[0].width - heroDetailPill.implicitWidth); height: 1 }
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: heroDetailPill
|
||||
visible: heroSsid.detail !== ""
|
||||
implicitWidth: heroDetail.implicitWidth + Style.space(10)
|
||||
implicitHeight: heroDetail.implicitHeight + Style.space(4)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: "transparent"
|
||||
border.color: Style.normalBorderFor(root.bar.foreground)
|
||||
border.width: Style.normalBorderWidth
|
||||
borderSpec: Border.controlSpec("normal", root.bar.foreground, Color.accent)
|
||||
radius: Style.cornerRadius
|
||||
|
||||
Text {
|
||||
@@ -1139,12 +1138,11 @@ Panel {
|
||||
font.pixelSize: Style.font.subtitle
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
anchors.fill: parent
|
||||
visible: row.forgetFocused
|
||||
color: Style.hoverFillFor(root.bar.urgent, root.bar.urgent)
|
||||
border.color: Style.hoverBorderFor(root.bar.urgent, root.bar.urgent)
|
||||
border.width: Style.hoverBorderWidth
|
||||
borderSpec: Border.controlSpec("hover-cursor", root.bar.urgent, root.bar.urgent)
|
||||
radius: Style.cornerRadius
|
||||
z: -1
|
||||
}
|
||||
@@ -1256,7 +1254,7 @@ Panel {
|
||||
Component.onCompleted: if (visible) Qt.callLater(forceActiveFocus)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: statusMsgWrapper
|
||||
visible: row.isBusy || row.isFailed
|
||||
anchors.left: parent.left
|
||||
@@ -1264,8 +1262,7 @@ Panel {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: Style.spacing.controlHeight
|
||||
color: Style.normalFillFor(root.bar.foreground)
|
||||
border.color: Style.normalBorderFor(root.bar.foreground)
|
||||
border.width: Style.normalBorderWidth
|
||||
borderSpec: Border.controlSpec("normal", root.bar.foreground, Color.accent)
|
||||
radius: Style.cornerRadius
|
||||
|
||||
Text {
|
||||
|
||||
@@ -1022,10 +1022,9 @@ Panel {
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
background: Rectangle {
|
||||
background: BorderSurface {
|
||||
color: Color.background
|
||||
border.color: root.dim
|
||||
border.width: 1
|
||||
borderSpec: Border.flat(root.dim, 1)
|
||||
radius: Style.cornerRadius
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import QtQuick
|
||||
import qs.Commons
|
||||
import qs.Ui
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -42,7 +43,7 @@ Item {
|
||||
rotation: -45
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
visible: root.warning
|
||||
width: Math.max(7, parent.width * 0.42)
|
||||
height: width
|
||||
@@ -50,8 +51,7 @@ Item {
|
||||
color: root.badgeColor
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
border.color: Color.popups.background
|
||||
border.width: 1
|
||||
borderSpec: Border.flat(Color.popups.background, 1)
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
|
||||
@@ -4,6 +4,7 @@ import Quickshell.Io
|
||||
import Quickshell.Services.Polkit
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Ui
|
||||
import "PolkitModel.js" as PolkitModel
|
||||
|
||||
Item {
|
||||
@@ -16,6 +17,7 @@ Item {
|
||||
property color foreground: Color.polkit.text
|
||||
property color border: Color.polkit.border
|
||||
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
|
||||
readonly property int cornerRadius: Style.cornerRadius
|
||||
property int contentMargin: Style.spacing.panelPadding
|
||||
@@ -212,7 +214,7 @@ Item {
|
||||
onClicked: root.refocus()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: card
|
||||
width: root.cardWidth
|
||||
height: root.cardHeight
|
||||
@@ -220,8 +222,8 @@ Item {
|
||||
anchors.centerIn: parent
|
||||
anchors.horizontalCenterOffset: root.shakeOffset
|
||||
color: root.background
|
||||
border.color: root.errorFlash ? root.borderError : root.border
|
||||
border.width: Math.max(1, Style.space(2))
|
||||
borderSpec: root.borderSpec
|
||||
padding: root.contentMargin
|
||||
|
||||
MouseArea { anchors.fill: parent; onClicked: root.refocus() }
|
||||
|
||||
@@ -245,7 +247,10 @@ Item {
|
||||
Row {
|
||||
id: cardRow
|
||||
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)
|
||||
|
||||
Text {
|
||||
|
||||
@@ -2,6 +2,7 @@ import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import QtQuick
|
||||
import qs.Commons
|
||||
import qs.Ui
|
||||
import "ReminderFlowModel.js" as ReminderFlowModel
|
||||
|
||||
Item {
|
||||
@@ -20,6 +21,7 @@ Item {
|
||||
property color background: Color.menu.background
|
||||
property color foreground: Color.menu.text
|
||||
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
|
||||
readonly property int cornerRadius: Style.cornerRadius
|
||||
property int contentMargin: Style.spacing.panelPadding
|
||||
@@ -110,15 +112,15 @@ Item {
|
||||
onClicked: root.dismiss()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: card
|
||||
width: root.cardWidth
|
||||
height: root.cardHeight
|
||||
radius: root.cornerRadius
|
||||
anchors.centerIn: parent
|
||||
color: root.background
|
||||
border.color: root.border
|
||||
border.width: Math.max(1, Style.space(2))
|
||||
borderSpec: root.borderSpec
|
||||
padding: root.contentMargin
|
||||
|
||||
MouseArea { anchors.fill: parent; onClicked: {} }
|
||||
|
||||
@@ -148,7 +150,10 @@ Item {
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: root.contentMargin
|
||||
anchors.topMargin: card.contentTopInset
|
||||
anchors.rightMargin: card.contentRightInset
|
||||
anchors.bottomMargin: card.contentBottomInset
|
||||
anchors.leftMargin: card.contentLeftInset
|
||||
|
||||
Text {
|
||||
anchors.left: parent.left
|
||||
|
||||
@@ -117,13 +117,12 @@ BarWidget {
|
||||
spacing: Style.space(10)
|
||||
width: parent.width
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
width: Style.space(64)
|
||||
height: Style.space(64)
|
||||
radius: Style.spacing.labelGap
|
||||
color: Style.normalFillFor(root.bar.foreground, Color.accent)
|
||||
border.color: Style.normalBorderFor(root.bar.foreground, Color.accent)
|
||||
border.width: Style.normalBorderWidth
|
||||
borderSpec: Border.controlSpec("normal", root.bar.foreground, Color.accent)
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
@@ -230,7 +229,7 @@ BarWidget {
|
||||
Repeater {
|
||||
model: root.sourcePlayers
|
||||
|
||||
Rectangle {
|
||||
BorderSurface {
|
||||
id: sourceRow
|
||||
required property var modelData
|
||||
|
||||
@@ -244,16 +243,15 @@ BarWidget {
|
||||
height: sourceInner.implicitHeight + Style.space(10)
|
||||
radius: Style.spacing.labelGap
|
||||
color: selected ? Style.selectedFillFor(root.bar.foreground, Color.accent) : "transparent"
|
||||
border.color: selected ? Style.selectedBorderFor(root.bar.foreground, Color.accent) : "transparent"
|
||||
border.width: selected ? Style.normalBorderWidth : 0
|
||||
borderSpec: selected ? Border.controlSpec("normal", root.bar.foreground, Color.accent) : Border.none()
|
||||
|
||||
Row {
|
||||
id: sourceInner
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Style.space(8)
|
||||
anchors.rightMargin: Style.space(8)
|
||||
anchors.leftMargin: sourceRow.borderLeft + Style.space(8)
|
||||
anchors.rightMargin: sourceRow.borderRight + Style.space(8)
|
||||
spacing: Style.space(8)
|
||||
|
||||
Text {
|
||||
|
||||
@@ -249,8 +249,10 @@ EOF
|
||||
cat >"$USER_THEMED/gradient-test.txt.tpl" <<'EOF'
|
||||
hypr={{ hypr_gradient 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-shell={{ gradient_start missing accent }}
|
||||
fallback-shell-gradient={{ shell_gradient missing accent }}
|
||||
EOF
|
||||
|
||||
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 '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-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"
|
||||
|
||||
jq -e '.name == "omarchy-system" and .vars.panel == "#0f0f0f" and .vars.border == "#4d4d4d" and .colors.accent == "accent"' "$NEXT_THEME/pi.json" >/dev/null
|
||||
|
||||
Executable
+44
@@ -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
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user