From b5372d46f368b92b3aa88eeda39c53371f127f8f Mon Sep 17 00:00:00 2001 From: rope Date: Mon, 31 Aug 2026 13:03:45 +0100 Subject: [PATCH] ink ladder: base03/base04 derive from base05, shared with hyprland base03/base04/base05 were three independent slots off the wallpaper, not one ink at three strengths. On wallhaven-k8dqr6 the raw ladder is red, blue and cream; on 5y39p9 base03 is a saturated gold against pale cream. That is why muted labels read as a second accent and why the shell frame never quite matched the window border beside it. base04 and base03 are now base05 mixed toward base00. The two factors and the mix live in settings/ui.nix and are used by all three consumers: Theme.qml, theming.nix's hypr.lua (applied at runtime) and hyprland.nix's build-time fallback. Same trick base01/base02 already used for surfaces. Verified on 5y39p9: hypr.lua now emits aca888 / 727761 from base05 f1e2b6 against base00 0a1f1c, matching what inkStep computes in the shell. --- settings/hyprland.nix | 19 +++++++++++++------ settings/quickshell.nix | 23 +++++++++++++++++++---- settings/theming.nix | 18 +++++++++++++----- settings/ui.nix | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 15 deletions(-) diff --git a/settings/hyprland.nix b/settings/hyprland.nix index 8021bbd..3cfc310 100644 --- a/settings/hyprland.nix +++ b/settings/hyprland.nix @@ -101,6 +101,13 @@ in c = config.lib.stylix.colors; rgb = hex: "rgb(${hex})"; rgba = hex: a: "rgba(${hex}${a})"; + # Build-time fallback for the border colours, until theme-apply first + # runs and hyprctl-evals the theme's own hypr.lua. Same two mixes as + # settings/theming.nix and quickshell's Theme.qml — see ui.nix. Not + # c.base04/c.base03: those are independent slots off the wallpaper and + # land in unrelated hues from the ink the shell frame is drawn in. + ink4 = ui.mixHex c.base05 c.base00 ui.inkMuted; + ink3 = ui.mixHex c.base05 c.base00 ui.inkFaint; in { # Stylix's Hyprland target injects settings.{general,decoration,group,misc} # as top-level keys, which render as hl.general()/hl.decoration()/… in Lua @@ -149,8 +156,8 @@ in # any palette. NOT base0D — a wallpaper-derived scheme can hand # base0D and base03 the same value (this one does: both 3fa8ab), # which left active and inactive windows indistinguishable. - "col.active_border" = rgb c.base04; - "col.inactive_border" = rgb c.base03; + "col.active_border" = rgb ink4; + "col.inactive_border" = rgb ink3; }; decoration = { @@ -169,13 +176,13 @@ in group = { # Same base04/base03 ramp as general.col.* above. - "col.border_active" = rgb c.base04; - "col.border_inactive" = rgb c.base03; + "col.border_active" = rgb ink4; + "col.border_inactive" = rgb ink3; "col.border_locked_active" = rgb c.base05; groupbar = { text_color = rgb c.base05; - "col.active" = rgb c.base04; - "col.inactive" = rgb c.base03; + "col.active" = rgb ink4; + "col.inactive" = rgb ink3; }; }; diff --git a/settings/quickshell.nix b/settings/quickshell.nix index 4e13732..6cd33f2 100644 --- a/settings/quickshell.nix +++ b/settings/quickshell.nix @@ -563,11 +563,26 @@ in readonly property color base00: "#" + pal.base00 readonly property color base01: Qt.lighter(base00, 1.7) // card readonly property color base02: Qt.lighter(base00, 2.4) // hover / chip / track - // ── Ink, wallpaper-derived: base05 primary, base04 muted, - // base03 disabled (also the border colour). - readonly property color base03: "#" + pal.base03 - readonly property color base04: "#" + pal.base04 + // ── Ink: ONE colour at three strengths. base05 is the + // wallpaper's ink; base04 and base03 are that same colour mixed + // toward base00 — not stylix's own base03/base04, which are + // independent slots off the image and land in unrelated hues. + // On wallhaven-k8dqr6 the raw ladder is red (base03), blue + // (base04) and cream (base05): three colours for one ramp, so + // a muted label read as a second accent rather than as quieter + // text. Same reasoning as base01/base02 above. + // + // The two mixes live in settings/ui.nix and are used verbatim + // by settings/theming.nix for hyprland's window borders, so the + // shell frame and the window borders stay the same colour. readonly property color base05: "#" + pal.base05 + function inkStep(t) { + return Qt.rgba(base05.r + (base00.r - base05.r) * t, + base05.g + (base00.g - base05.g) * t, + base05.b + (base00.b - base05.b) * t, 1); + } + readonly property color base04: inkStep(${toString ui.inkMuted}) + readonly property color base03: inkStep(${toString ui.inkFaint}) // The one themed accent: active, focused, selected, filled — // and every status colour below is harmonised toward it, so a // bad pick tints the whole bar rather than just the highlights. diff --git a/settings/theming.nix b/settings/theming.nix index ad9bd83..f2d782e 100644 --- a/settings/theming.nix +++ b/settings/theming.nix @@ -15,6 +15,7 @@ # target not listed here — walls/wallpaper.png remains its image. { config, pkgs, lib, inputs, ... }: let + ui = import ./ui.nix; # shared ink ladder (inkMuted / inkFaint / mixHex) isDesktop = lib.elem config.networking.hostName [ "FredOS-Gaming" "FredOS-Macbook" ]; homeDir = "/home/fred"; stateLink = "${homeDir}/.local/state/quickshell-theme"; @@ -282,14 +283,21 @@ let # # One line, not pretty-printed: hyprctl joins its argv into the request # string, so keeping the payload free of newlines keeps it intact. - hyprLua = s: pkgs.writeText "hypr.lua" ("hl.config({" + lib.concatStringsSep "," [ - ''general={["col.active_border"]="rgb(${s.base04})",["col.inactive_border"]="rgb(${s.base03})"}'' + # Borders take the DERIVED ink, not s.base04/s.base03 — quickshell's + # Theme.qml runs the same two mixes, and the shell frame sits directly + # against these borders. Reading the raw slots here is what left the two + # outlines a visible shade apart. + hyprLua = s: let + ink4 = ui.mixHex s.base05 s.base00 ui.inkMuted; + ink3 = ui.mixHex s.base05 s.base00 ui.inkFaint; + in pkgs.writeText "hypr.lua" ("hl.config({" + lib.concatStringsSep "," [ + ''general={["col.active_border"]="rgb(${ink4})",["col.inactive_border"]="rgb(${ink3})"}'' ''decoration={shadow={color="rgba(${s.base00}99)"}}'' - (''group={["col.border_active"]="rgb(${s.base04})",'' - + ''["col.border_inactive"]="rgb(${s.base03})",'' + (''group={["col.border_active"]="rgb(${ink4})",'' + + ''["col.border_inactive"]="rgb(${ink3})",'' + ''["col.border_locked_active"]="rgb(${s.base05})",'' + ''groupbar={text_color="rgb(${s.base05})",'' - + ''["col.active"]="rgb(${s.base04})",["col.inactive"]="rgb(${s.base03})"}}'') + + ''["col.active"]="rgb(${ink4})",["col.inactive"]="rgb(${ink3})"}}'') ''misc={background_color="rgb(${s.base00})"}'' ] + "})"); diff --git a/settings/ui.nix b/settings/ui.nix index 8069cf1..59510bb 100644 --- a/settings/ui.nix +++ b/settings/ui.nix @@ -13,4 +13,37 @@ # hyprland decoration.rounding = radius { radius = 20; + + # ── Ink ladder ──────────────────────────────────────────────────────── + # base05 is the wallpaper's ink. base04 and base03 are THAT colour mixed + # toward the background, not stylix's own base03/base04: those are three + # independent slots off the image and routinely land in unrelated hues — + # on walls/wallhaven-k8dqr6 the raw ladder is red (base03), blue (base04) + # and cream (base05). Mixing gives one colour at three strengths, which is + # the same trick quickshell's Theme.base01/base02 already use for surfaces. + # + # Read by settings/quickshell.nix (Theme.qml), settings/theming.nix + # (hypr.lua, applied at runtime) and settings/hyprland.nix (the build-time + # fallback), so the shell frame and hyprland's window borders are the same + # colour rather than two slots that happen to be near each other. + inkMuted = 0.30; # base04 — secondary text, hyprland's active border + inkFaint = 0.55; # base03 — disabled text, hyprland's inactive border + + # Mix two "rrggbb" strings. t = 0 keeps `a`, t = 1 lands on `b`. + mixHex = a: b: t: + let + digits = "0123456789abcdef"; + val = ch: { + "0"=0; "1"=1; "2"=2; "3"=3; "4"=4; "5"=5; "6"=6; "7"=7; + "8"=8; "9"=9; "a"=10; "b"=11; "c"=12; "d"=13; "e"=14; "f"=15; + "A"=10; "B"=11; "C"=12; "D"=13; "E"=14; "F"=15; + }.${ch}; + byte = s: i: val (builtins.substring i 1 s) * 16 + + val (builtins.substring (i + 1) 1 s); + chan = i: + let + n = builtins.floor (byte a i + (byte b i - byte a i) * t + 0.5); + hi = n / 16; + in builtins.substring hi 1 digits + builtins.substring (n - hi * 16) 1 digits; + in chan 0 + chan 2 + chan 4; }