From 35873462f39fb6caf2d161454ef0190bf4a955b3 Mon Sep 17 00:00:00 2001 From: Gabriel Arazas Date: Tue, 4 Jun 2024 20:40:29 +0800 Subject: [PATCH] bahaghari/lib: update and refactor `colors.rgb` Now the RGB colorspace object doesn't have the methods built into the set as that basically screws a lot of things when exporting it to the output. Also, the alpha component should be handled nicely now. --- subprojects/bahaghari/lib/colors/rgb.nix | 101 ++++++++++++++--------- subprojects/bahaghari/tests/lib/rgb.nix | 41 ++++++++- 2 files changed, 101 insertions(+), 41 deletions(-) diff --git a/subprojects/bahaghari/lib/colors/rgb.nix b/subprojects/bahaghari/lib/colors/rgb.nix index 8f40166a..df50f06b 100644 --- a/subprojects/bahaghari/lib/colors/rgb.nix +++ b/subprojects/bahaghari/lib/colors/rgb.nix @@ -5,8 +5,13 @@ { pkgs, lib, self }: rec { - /* Generates an RGB colorspace object to be generated with its method for - convenience. + # A bunch of metadata for this implementation. Might be useful if you want to + # create functions from this subset. + valueMin = 0.0; + valueMax = 255.0; + + /* Generates an RGB colorspace object. The point is to provide some + typechecking if the values passed are correct. Type: RGB :: Attrs -> Attrs @@ -14,23 +19,13 @@ rec { RGB { r = 242.0; g = 12; b = 23; } => { # The individual red, green, and blue components. - - # And several methods. - __functor = { - toHex = ; - lighten = ; - }; } */ - RGB = { r, g, b }@color: + RGB = { r, g, b, ... }@color: assert lib.assertMsg (isRgb color) - "bahaghariLib.colors.rgb.RGB: given color does not have valid RGB value"; - { + "bahaghariLib.colors.rgb.RGB: Given object has invalid values (only 0-255 are allowed)"; + lib.optionalAttrs (color ? a) { inherit (color) a; } // { inherit r g b; - __functor = { - toHex = self: toHex color; - lighten = self: lighten color; - }; }; /* Returns a boolean to check if it's a valid RGB Nix object or not. @@ -49,12 +44,12 @@ rec { isRgb { r = 123; g = 123; b = 123; } => true */ - isRgb = { r, g, b }@color: + isRgb = { r, g, b, ... }@color: let - isWithinRGBRange = v: self.math.isWithinRange 0 255 v; + isWithinRGBRange = v: self.math.isWithinRange valueMin valueMax v; isValidRGB = v: self.isNumber v && isWithinRGBRange v; - in - lib.lists.all (v: isValidRGB v) (lib.attrValues color); + colors = [ r g b ] ++ lib.optionals (color ? a) [ color.a ]; + in lib.lists.all (v: isValidRGB v) colors; /* Converts the color to a 6-digit hex string. Unfortunately, it cannot handle floats very well so we'll have to round these up. @@ -67,13 +62,30 @@ rec { */ toHex = { r, g, b, ... }: let - r' = self.math.round r; - g' = self.math.round g; - b' = self.math.round b; - rH = self.hex.pad 2 (self.hex.fromDec r'); - gH = self.hex.pad 2 (self.hex.fromDec g'); - bH = self.hex.pad 2 (self.hex.fromDec b'); - in "${rH}${gH}${bH}"; + components = builtins.map (c: + let c' = self.math.round c; + in self.hex.pad 2 (self.hex.fromDec c')) [ r g b ]; + in lib.concatStringsSep "" components; + + /* Converts the color to a 8-digit hex string (RGBA). If no `a` component, it + will be assumed to be at maximum value. Unfortunately, it cannot handle + floats very well so we'll have to round these up. + + Type: toHex :: RGB -> String + + Example: + toHex' { r = 231; g = 12; b = 21; } + => "E70C15FF" + + toHex' { r = 231; g = 12; b = 21; a = 0; } + => "E70C1500" + */ + toHex' = { r, g, b, a ? valueMax, ... }: + let + components = builtins.map (c: + let c' = self.math.round c; + in self.hex.pad 2 (self.hex.fromDec c')) [ r g b a ]; + in lib.concatStringsSep "" components; /* Converts a valid hex string into an RGB object. @@ -85,6 +97,12 @@ rec { fromHex "FFF" => { r = 255; g = 255; b = 255; } + + fromHex "FFFF" + => { r = 255; g = 255; b = 255; a = 255; } + + fromHex "FFFFFFFF" + => { r = 255; g = 255; b = 255; a = 255; } */ fromHex = hex: let @@ -92,8 +110,15 @@ rec { r = lib.lists.elemAt hex' 0; g = lib.lists.elemAt hex' 1; b = lib.lists.elemAt hex' 2; - in - RGB { inherit r g b; }; + a = + if lib.lists.length hex' == 4 then + lib.lists.elemAt hex' 3 + else + null; + in RGB { + inherit r g b; + ${self.optionalNull (a != null) "a"} = a; + }; /* Given a percentage, uniformly lighten the given RGB color. @@ -105,11 +130,10 @@ rec { in lighten color 50 */ - lighten = { r, g, b, ... }: percentage: - let - grow' = c: self.math.grow' 0 255 percentage; - in - RGB { + lighten = { r, g, b, ... }: + percentage: + let grow' = c: self.math.grow' valueMin valueMax percentage; + in RGB { r = grow' r; g = grow' g; b = grow' b; @@ -134,9 +158,10 @@ rec { hexMatch = hex: let length = lib.stringLength hex; - genMatch = r: n: lib.concatStringsSep "" (lib.genList (_: "([[:xdigit:]]{${builtins.toString n}})") r); - nonAlphaGenMatch = genMatch 3; - withAlphaGenMatch = genMatch 4; + generateRegex = r: n: + lib.strings.replicate r "([[:xdigit:]]{${builtins.toString n}})"; + nonAlphaGenMatch = generateRegex 3; + withAlphaGenMatch = generateRegex 4; regex = if (length == 6) then @@ -153,8 +178,8 @@ rec { scale = self.trivial.scale { inMin = 0; inMax = 15; - outMin = 0; - outMax = 255; + outMin = valueMin; + outMax = valueMax; }; match = lib.strings.match regex hex; diff --git a/subprojects/bahaghari/tests/lib/rgb.nix b/subprojects/bahaghari/tests/lib/rgb.nix index 8c54a1c3..ca9ee582 100644 --- a/subprojects/bahaghari/tests/lib/rgb.nix +++ b/subprojects/bahaghari/tests/lib/rgb.nix @@ -5,7 +5,7 @@ let # actual results. Also, it will mess up the result comparison since comparing # functions is reference-based so it will always fail. normalizeData = colors: - lib.attrsets.removeAttrs colors [ "__functor" ]; + lib.attrsets.removeAttrs colors [ "__functor" "methods" ]; rgbSample = self.colors.rgb.RGB { r = 255; @@ -15,8 +15,7 @@ let # A modified version of RGB that normalizes data out-of-the-boxly. RGB = colors: normalizeData (self.colors.rgb.RGB colors); -in -lib.runTests { +in lib.runTests { testsBasicRgb = { expr = RGB { r = 34; @@ -30,6 +29,21 @@ lib.runTests { }; }; + testsBasicRgb2 = { + expr = RGB { + r = 23; + g = 65; + b = 241; + a = 255; + }; + expected = { + r = 23; + g = 65; + b = 241; + a = 255; + }; + }; + testsFromHex = { expr = normalizeData (self.colors.rgb.fromHex "FFFFFF"); expected = normalizeData (self.colors.rgb.RGB { @@ -54,6 +68,7 @@ lib.runTests { r = 255; g = 255; b = 255; + a = 255; }); }; @@ -63,6 +78,7 @@ lib.runTests { r = 255; g = 255; b = 255; + a = 255; }); }; @@ -80,6 +96,25 @@ lib.runTests { expected = "173A69"; }; + testsToHexVariant = { + expr = self.colors.rgb.toHex' (RGB { + r = 255; + g = 56; + b = 105; + }); + expected = "FF3869FF"; + }; + + testsToHexVariant2 = { + expr = self.colors.rgb.toHex' (RGB { + r = 255; + g = 56; + b = 105; + a = 34; + }); + expected = "FF386922"; + }; + testsHexMatch = { expr = self.colors.rgb.hexMatch "FFF"; expected = [ 255 255 255 ];