Skip to content

feat(theme): add outline support for bar, widgets and surfaces#118

Merged
prankstr merged 4 commits intomainfrom
feat/outline
May 2, 2026
Merged

feat(theme): add outline support for bar, widgets and surfaces#118
prankstr merged 4 commits intomainfrom
feat/outline

Conversation

@prankstr
Copy link
Copy Markdown
Owner

@prankstr prankstr commented May 2, 2026

Adds an optional CSS border (outline) around the bar, widgets, and surfaces.
image

This serves two purposes: the first is purely aesthetic, some people prefer the look and I want to expose it from toml rather than just CSS.

The second is to hide jagged compositor blur edges at rounded corners, since wl_region only supports axis-aligned rectangles, the blur isn't as nicely rounded and creates a staircase effect. When an outline is active, the blur region is trimmed by 1px at corners so the jagged edges hide behind the border.
screenshot-2026-05-02_13-14-13
screenshot-2026-05-02_13-13-30

Config:

[theme]
outline = true              # default: false
outline_width = 1           # 0-4, default: 1
outline_color = "accent"    # "accent", "foreground", "subtle" or hex, default "accent"
outline_opacity = 0.5       # 0.0-1.0, default: 1.0

[bar]
outline = true              # override theme default (optional)

[widgets]
outline = false             # override theme default (optional)

[widgets.battery]
outline_color = "foreground" # per-widget color override

CSS variables: --outline-width, --outline-color, --outline-opacity at :root, plus per-scope --bar-outline-width, --widget-outline-width, --surface-outline-width that resolve to var(--outline-width) or 0px

Per surface override other than bar/widget is excluded in v1

prankstr added 4 commits May 1, 2026 18:32
Introduces a general decorative outline feature: config schema, palette
computation, and CSS variable emission. No surface consumes the variables
yet — that lands in a follow-up commit. Default is outline = false, so
visual output is unchanged.

Config:
- theme.outline / outline_width / outline_color / outline_opacity
- bar.outline / widgets.outline as Option<bool> overrides that inherit
  from theme.outline when omitted
- widgets.<name>.outline_color for per-widget color overrides (width
  and opacity stay theme-level)

Validation:
- outline_width bounded to 0..=4 (wider decorative borders should use
  custom CSS via style.css)
- outline_opacity bounded to 0.0..=1.0
- outline_color accepts the symbolic names "subtle" / "accent" /
  "foreground" or hex (#rgb / #rrggbb); per-widget overrides use the
  same rules, with the widget path included in error messages

Theme palette:
- ThemePalette gains effective per-scope enable flags
  (bar/widget/surface_outline_enabled) and resolved width/color/opacity.
- css_vars_block emits base --outline-* variables and per-scope
  --bar-outline-* / --widget-outline-* / --surface-outline-* aliases.
  Width per scope resolves to var(--outline-width) when enabled or 0px
  when disabled; color/opacity are simple aliases in v1 to give future
  scope overrides a clean extension point without renaming.
- In islands mode (bar.background_opacity == 0.0) an inherited bar
  outline is suppressed so it doesn't draw a phantom 1px frame around
  an invisible bar; an explicit bar.outline = true still wins.
- resolve_outline_color is exported so per-widget CSS generation can
  reuse it.

Hot reload:
- config_theme_changed picks up bar.outline / widgets.outline so
  toggling them takes effect without a restart.
- ConfigManager exposes bar/widget/surface_outline_visible() accessors
  for downstream consumers (used by the blur-region commit).
Wires the outline CSS variables introduced in the previous commit into
actual border declarations. Per-scope --*-outline-width resolves to 0px
when disabled, so this remains visually inert until users set
theme.outline = true (or a per-section override).

Bar / widgets:
- sectioned-bar.bar gains a border driven by --bar-outline-*
- .widget gains a border driven by --widget-outline-*; widget groups
  inherit the rule via .widget.widget-group, putting the outline around
  the group pill rather than each child

Popovers and surfaces:
- .vp-surface-popover (used by all layer-shell widget popovers — clock,
  calendar, battery, system, notifications, network, bluetooth, power)
  gains a border driven by --surface-outline-*
- The shared popover surface CSS in services/surfaces.rs (used by OSD,
  toasts, tray, media, Quick Settings) replaces its previous
  `border: none` with the same --surface-outline-* declaration
The Wayland blur region for a rounded surface is rasterized as a stack
of axis-aligned scanline rectangles (one per row of the rounded
corner). Without compensation, the outermost rectangle of each corner
extends to the same pixel as the CSS border-radius edge — but the
corner is anti-aliased, so the few sub-pixel-coverage edge pixels of a
semi-transparent outline see the blurred background through them.
Visually that reads as a brighter halo on the corner's anti-aliased
edge wherever an outline is enabled.

compute_rounded_rect_rects_with_corner_inset adds an optional 1-px
inward trim on each rounded-corner scanline, applied only when a
visible outline is present on the surface. add_rounded_rect_to_region_
with_outline gates the trim on outline_visible so the existing
non-outline path is unchanged.

Wired into the three surface-blur entry points
(apply_blur_surface_with_outline, apply_bar_blur_region with the bar
outline flag, apply_bar_island_blur_regions with the widget outline
flag) using the new visibility accessors on ConfigManager from the
plumbing commit.
- Add outline_opacity_pct > 0 guard to *_outline_visible() helpers so a
  fully transparent outline doesn't trigger blur corner inset
- Apply first widget's name CSS class to .widget.widget-group surface so
  per-widget outline_color overrides work for merge groups
- Narrow outline_color doc comment to accurately describe scope
- Add inline comment explaining why corner inset is 1px regardless of
  outline_width
@prankstr prankstr merged commit c67f6e1 into main May 2, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant