Add an inward glow bleeding from the border over the window
Version Bump / check-bump-label (pull_request) Skipped
Version Bump / apply-version-bump (pull_request) Successful in 21s

New plugin:hyprchrome:glow_size (px, 0 = off) and glow_strength (0-1)
bleed the border's own gradient inward past the window edge, over the
window's own pixels. Off by default, so existing setups are unchanged.

Drawn into the hole the ring's even-odd fill already leaves behind, from
overlapping layers - one per px of depth - each covering the window edge
inward to a progressively greater depth. Overlapping rather than abutting
disjoint bands avoids an antialiasing seam at every shared edge; the cost
is that a layer composites over every deeper one, so its alpha is solved
outermost-inward from the target profile rather than being it.

That profile is strength * (1 - d/glow)^2.2: steep off the window edge,
easing into a tail that reaches zero tangentially so there's no ring
where it stops.

Corner handling, in order of how much it mattered:
- Each layer's outer edge is the ring's inner boundary verbatim, chamfer
  vertices and all. Anything else detaches the glow from the frame and
  opens a sliver of unpainted window at each corner.
- Each layer's inner edge - which is what the falloff's contours actually
  follow - is filleted in proportion to its own depth, so the glow reads
  as chamfered where it meets the frame and rounder as it fades inward.
- Those inner edges also shrink their chamfer by (2-sqrt2) per px of
  inset. Insetting a chamfered rect while holding the chamfer constant
  moves the 45-degree face in by d*sqrt(2) rather than d, which had the
  glow running ~41% deeper at every corner than along the sides.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-31 01:19:16 +02:00
co-authored by Claude Opus 5
parent 3b89aa1c0c
commit 1735a5c796
7 changed files with 244 additions and 33 deletions
+3 -1
View File
@@ -44,6 +44,8 @@ std::vector<UP<IPassElement>> ChromePassElement::draw() {
? hyprlandBorder
: (isActive ? PluginState->config.activeColor->value() : PluginState->config.inactiveColor->value()));
const float borderAlpha = gradient.FirstAlpha();
const float glowPx = static_cast<float>(PluginState->config.glowSize->value()) * monitor->m_scale;
const float glowStrength = static_cast<float>(PluginState->config.glowStrength->value());
const float titleBarHeightPx = static_cast<float>(PluginState->config.titlebarHeight->value()) * monitor->m_scale;
const float textSizePx = static_cast<float>(PluginState->config.titlebarTextSize->value()) * monitor->m_scale;
const std::string fontFamily = PluginState->config.titlebarFont->value();
@@ -73,7 +75,7 @@ std::vector<UP<IPassElement>> ChromePassElement::draw() {
// with title length - otherwise every title change would needlessly
// regenerate an identical border texture.
const float borderTitleBarWidthPx = geo.fullSpan ? geo.MaxTitleBarWidth() : geo.titleBarWidth;
const auto tex = data.decoration->GetBorderTexture({box.w, box.h}, extentPx, chamferPx, gradient, titleBarHeightPx, borderTitleBarWidthPx);
const auto tex = data.decoration->GetBorderTexture({box.w, box.h}, extentPx, chamferPx, gradient, titleBarHeightPx, borderTitleBarWidthPx, glowPx, glowStrength);
if (!tex || !tex->ok())
return {};