{ pkgs, lib ? pkgs.lib, hex, float, ... }: let inherit (builtins) isInt isString isFloat trace div isAttrs hasAttr; inherit (lib.trivial) min max; inherit (lib.lists) head tail drop last; inherit (lib.strings) concatMapStrings fixedWidthString match toLower toInt; ## OPERATORS # Module operator implementation for floats div' = n: d: float.floor (div (float.ensureFloat n) (float.ensureFloat d)); mod' = n: d: let f = div' n d; in n - (float.fromInt f) * d; # Absolute operator implementation _abs = v: if v < 0 then (-v) else v; # Check if `v` is between `a` and `b` _inRange = a: b: v: (v <= max a b) && (v >= min a b); # Clamp `v` between `a` and `v` _clamp = a: b: v: min (max v (min a b)) (max a b); ## 8BIT # Check if `v` is in 8Bit format _is8Bit = v: _inRange 0.0 255.0 v; # Clamp 8bit value _clamp8Bit = _clamp 0.0 255.0; # Apply function to 8bit value and clamp the result _tclamp8Bit = f: v: _clamp8Bit (f v); ## UNARY # Check if input is in [0, 1] _isUnary = _inRange 0.0 1.0; # Clamp input to [0, 1] _clampUnary = _clamp 0.0 1.0; # Apply function to unary value and clamp the result _tclampUnary = f: v: _clampUnary (f v); # Check if input is in [0, 360] _isHue = _inRange 0.0 360.0; # Apply function to hue value and map the result in [0, 360) _tHue = f: v: mod' (f v) 360.0; # RGB constructor _rgba = { r, g, b, a ? 255.0 }: assert (_is8Bit r); assert (_is8Bit g); assert (_is8Bit b); { inherit r g b a; }; # HSLA constructor _hsla = { h, l, s ? 1.0, a ? 255.0 }: assert (_inRange 0.0 360.0 h); assert (_isUnary s); assert (_isUnary l); assert (_isUnary a); { inherit h s l a; }; in rec { inherit div' mod' _abs _inRange _clamp _is8Bit _clamp8Bit _tclamp8Bit _isUnary _clampUnary _tclampUnary _tHue _rgba _hsla; rgba = _rgba; isRgba = c: if isAttrs c && hasAttr "r" c && hasAttr "g" c && hasAttr "b" c && hasAttr "a" c then 0 <= c.r && c.r <= 255 && 0 <= c.g && c.g <= 255 && 0 <= c.b && c.b <= 255 && 0 <= c.a && c.a <= 255 else false; hsla = _hsla; isHsla = c: if isAttrs c hasAttr "h" c && hasAttr "s" c && hasAttr "l" c && hasAttr "a" c then 0 <= c.h && c.h <= 255 && 0 <= c.s && c.s <= 255 && 0 <= c.l && c.l <= 255 && 0 <= c.a && c.a <= 255 else false; ## CONVERSION # RGB to HSL rgbaToHsla = color: let c_color = _rgba color; r = c_color.r / 255.0; g = c_color.g / 255.0; b = c_color.b / 255.0; a = c_color.a / 255.0; c_min = min (min r g) b; c_max = max (max r g) b; delta = c_max - c_min; hue = ( if delta == 0.0 then 0.0 else if r == c_max then (mod' (((g - b) / delta) + 6) 6) else if g == c_max then (b - r) / delta + 2 else assert b == c_max; (r - g) / delta + 4 ) * 60; lightness = (c_min + c_max) / 2.0; saturation = if delta == 0.0 then 0.0 else delta / (1 - _abs (2.0 * lightness - 1.0)); in assert (isRgba color); _hsla { l = _clampUnary lightness; s = _clampUnary saturation; h = mod' hue 360.0; a = _clampUnary a; }; # HSL to RGB hslToRgb = color: let # check if `v` is in [a, b) _checkRange = a: b: v: a <= v && v < b; c_color = _hsla color; h = c_color.h; s = c_color.s; l = c_color.l; a = c_color.a; c = (1 - (_abs (2 * l - 1))) * s; x = c * (1 - _abs ((mod' (h / 60) 2) - 1)); m = l - (c / 2.0); r' = if _inRange 120 240 h then 0 else if _inRange 60 300 h then x else c; g' = if _inRange 60 180 h then c else if _inRange 0 240 h then x else 0; b' = if _inRange 180 300 h then c else if _inRange 120 360 h then x else 0; in assert (isHsla); _rgba { r = _clamp8Bit ((r' + m) * 255.0); g = _clamp8Bit ((g' + m) * 255.0); b = _clamp8Bit ((b' + m) * 255.0); a = _clamp8Bit (a * 255.0); }; ## TRANSFORM tRedRgba = f: color: assert (isRgba color); color // { r = _tclamp8Bit f color.r; }; tGreenRgba = f: color: assert (isRgba color); color // { g = _tclamp8Bit f color.g; }; tBlueRgba = f: color: assert (isRgba color); color // { b = _tclamp8Bit f color.b; }; tAlphaRgba = f: color: assert (isRgba color); color // { a = _tclamp8Bit f color.a; }; setRedRgba = r: color: tGreenRgba (v: r) color; setGreenRgba = g: color: tBlueRgba (v: g) color; setBlueRgba = b: color: tAlphaRgba (v: b) color; setAlphaRgba = a: color: tRedRgba (v: a) color; tRedHsla = f: color: assert (isHsla color); color // { h = _tHue f color.h; }; tGreenHsla = f: color: assert (isHsla color); color // { s = _tclampUnary f color.s; }; tBlueHsla = f: color: assert (isHsla color); color // { l = _tclampUnary f color.l; }; tAlphaHsla = f: color: assert (isHsla color); color // { a = _tclampUnary f color.a; }; setRedHsla = h: color: tGreenHsla (v: h) color; setGreenHsla = s: color: tBlueHsla (v: s) color; setBlueHsla = l: color: tAlphaHsla (v: l) color; setAlphaHsla = a: color: tRedHsla (v: a) color; ## RGB TRANSFORM # Add brightness value as integer value or percent value brighten = color: value: let directValue = if isInt value || isFloat value then value else null; positiveMatches = match "([[:digit:]]+)%" value; negativeMatches = match "-([[:digit:]]+)%" value; percentValue = if positiveMatches != null then toInt (head positiveMatches) else if negativeMatches != null then (-toInt (head negativeMatches)) else null; valueTransform = v: if directValue != null then _clamp8Bit (v + directValue) else _clamp8Bit (v * (100.0 + percentValue) / 100.0); in tBlueRgba valueTransform (tGreenRgba valueTransform (tRedRgba valueTransform color)); darken = color: value: let directValue = if isInt value || isFloat value then value else null; positiveMatches = match "([[:digit:]]+)%" value; negativeMatches = match "-([[:digit:]]+)%" value; percentValue = if positiveMatches != null then toInt (head positiveMatches) else if negativeMatches != null then (-toInt (head negativeMatches)) else null; valueTransform = v: if directValue != null then _clamp8Bit (v - directValue) else _clamp8Bit (v * (100.0 - percentValue) / 100.0); in tBlueRgba valueTransform (tGreenRgba valueTransform (tRedRgba valueTransform color)); ## DESERIALIZATION # Parse a hex color string to a RGBA color hexToRgba = s: let rgba = match "#([[:xdigit:]]{2})([[:xdigit:]]{2})([[:xdigit:]]{2})([[:xdigit:]]{2})" s; rgb = match "#([[:xdigit:]]{2})([[:xdigit:]]{2})([[:xdigit:]]{2})" s; hex_list = (if isNull rgb then rgba else rgb ++ [ "FF" ]); values = map (s: float.fromInt (hex.toDec s)) hex_list; in _rgba { r = head values; g = head (tail values); b = head (drop 2 values); a = last values; }; # Parse a hex color string to a HSLA color hexToHsla = s: let hsla = match "#([[:xdigit:]]{2})([[:xdigit:]]{2})([[:xdigit:]]{2})([[:xdigit:]]{2})" s; hsl = match "#([[:xdigit:]]{2})([[:xdigit:]]{2})([[:xdigit:]]{2})" s; hex_list = (if isNull hsl then hsla else hsl ++ [ "FF" ]); values = map (s: float.fromInt (hex.toDec s)) hex_list; in _hsla { h = head values; s = head (tail values); l = head (drop 2 values); a = last values; }; ## SERIALIZATION # Print RGB color as uppercase hex string toRGBHex = color: let inherit (color) r g b; in assert (isRgba color); ''#${concatMapStrings (v: fixedWidthString 2 "0" (hex.fromDec (float.round v))) [ r g b ]}''; # Print RGBA color as uppercase hex string toRGBAHex = color: let inherit (color) r g b a; in assert (isRgba color); ''#${concatMapStrings (v: fixedWidthString 2 "0" (hex.fromDec (float.round v))) [ r g b a ]}''; # Print RGBA color as uppercase hex string in the form ARGB (Polybar uses this format) toARGBHex = color: let inherit (color) r g b a; in assert (isRgba color); ''#${concatMapStrings (v: fixedWidthString 2 "0" (hex.fromDec (float.round v))) [ a r g b ]}''; # Print RGB color as lowercase hex string toRgbHex = color: toLower (toRGBHex color); # Print RGBA color as lowercase hex string toRgbaHex = color: toLower (toRGBAHex color); # Print RGBA color as lowercase hex string in the form argb (Polybar uses this format) toArgbHex = color: toLower (toArgbHex color); # Print RGB color as uppercase hex string toHSLHex = color: let inherit (color) h s l; in assert (isHsla color); ''#${concatMapStrings (v: fixedWidthString 2 "0" (hex.fromDec (float.round v))) [ h s l ]}''; # Print HSLA color as uppercase hex string toHSLAHex = color: let inherit (color) h s l a; in assert (isHsla color); ''#${concatMapStrings (v: fixedWidthString 2 "0" (hex.fromDec (float.round v))) [ h s l a ]}''; # Print HSL color as lowercase hex string toHslHex = color: toLower (toHSLHex color); # Print HSLA color as lowercase hex string toHslaHex = color: toLower (toHSLAHex color); ## CONSTANTS black = hexToRgba "#000000"; # RGB (0,0,0) HSL (0°,0%,0%) white = hexToRgba "#FFFFFF"; # RGB (255,255,255) HSL (0°,0%,100%) red = hexToRgba "#FF0000"; # RGB (255,0,0) HSL (0°,100%,50%) green = hexToRgba "#00FF00"; # RGB (0,255,0) HSL (120°,100%,50%) blue = hexToRgba "#0000FF"; # RGB (0,0,255) HSL (240°,100%,50%) yellow = hexToRgba "#FFFF00"; # RGB (255,255,0) HSL (60°,100%,50%) cyan = hexToRgba "#00FFFF"; # RGB (0,255,255) HSL (180°,100%,50%) magenta = hexToRgba "#FF00FF"; # RGB (255,0,255) HSL (300°,100%,50%) transparent = hexToRgba "#00000000"; # RGBA (0,0,0,0) HSLA (0°,0%,0%,0%) }