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; }