diff --git a/doc/src/configuration.md b/doc/src/configuration.md index e3686e2c5..e4154b4b8 100644 --- a/doc/src/configuration.md +++ b/doc/src/configuration.md @@ -91,23 +91,26 @@ To set a wallpaper, provide a path or an arbitrary derivation: ``` If `stylix.base16Scheme` is undeclared, Stylix generates a color scheme based on -the wallpaper using a [genetic -algorithm](https://en.wikipedia.org/wiki/Genetic_algorithm). Note that more -colorful images tend to yield better results. The algorithm's polarity can be -schewed towards a dark or light theme with: +the wallpaper using [Matugen](https://github.com/InioX/matugen). Note that more +colorful images tend to yield better results. + +Set the polarity with: - ```nix { - stylix.polarity = "dark"; + stylix.colorGeneration.polarity = "dark"; } ``` - ```nix { - stylix.polarity = "light"; + stylix.colorGeneration.polarity = "light"; } ``` +Customize the generated color scheme with the `stylix.colorGeneration.scheme` +and `stylix.colorGeneration.contrast` options. + The generated color scheme can be viewed at `/etc/stylix/palette.html` on NixOS, or at `~/.config/stylix/palette.html` on Home Manager. diff --git a/doc/src/testbeds.md b/doc/src/testbeds.md index 9a20adb6f..9b47c08da 100644 --- a/doc/src/testbeds.md +++ b/doc/src/testbeds.md @@ -76,7 +76,6 @@ github:nix-community/stylix └───packages └───x86_64-linux ├───doc: package 'stylix-book' - ├───palette-generator: package 'palette-generator' ├───"testbed:gnome:cursorless": package 'testbed-gnome-cursorless' ├───"testbed:gnome:dark": package 'testbed-gnome-dark' ├───"testbed:gnome:imageless": package 'testbed-gnome-imageless' diff --git a/flake/dev/dev-shell.nix b/flake/dev/dev-shell.nix index 53ff15f15..edbe9ffd5 100644 --- a/flake/dev/dev-shell.nix +++ b/flake/dev/dev-shell.nix @@ -39,26 +39,19 @@ ''; in { - devShells = { - default = pkgs.mkShell { - # Install git-hooks when activating the shell - shellHook = config.pre-commit.installationScript; + devShells.default = pkgs.mkShell { + # Install git-hooks when activating the shell + shellHook = config.pre-commit.installationScript; - packages = [ - stylix-check - build-and-run-docs - inputs'.home-manager.packages.default - config.formatter - ] - ++ config.pre-commit.settings.enabledPackages; + packages = [ + stylix-check + build-and-run-docs + inputs'.home-manager.packages.default + config.formatter + ] + ++ config.pre-commit.settings.enabledPackages; - inputsFrom = [ config.treefmt.build.devShell ]; - }; - - ghc = pkgs.mkShell { - inputsFrom = [ config.devShells.default ]; - packages = [ pkgs.ghc ]; - }; + inputsFrom = [ config.treefmt.build.devShell ]; }; }; } diff --git a/flake/modules.nix b/flake/modules.nix index c44238f92..267fd7427 100644 --- a/flake/modules.nix +++ b/flake/modules.nix @@ -11,8 +11,6 @@ { stylix = { inherit inputs; - paletteGenerator = - self.packages.${pkgs.stdenv.hostPlatform.system}.palette-generator; base16 = inputs.base16.lib args; homeManagerIntegration.module = self.homeModules.stylix; }; @@ -31,8 +29,6 @@ { stylix = { inherit inputs; - paletteGenerator = - self.packages.${pkgs.stdenv.hostPlatform.system}.palette-generator; base16 = inputs.base16.lib args; }; } @@ -50,8 +46,6 @@ { stylix = { inherit inputs; - paletteGenerator = - self.packages.${pkgs.stdenv.hostPlatform.system}.palette-generator; base16 = inputs.base16.lib args; homeManagerIntegration.module = self.homeModules.stylix; }; @@ -69,8 +63,6 @@ ../stylix/droid { stylix = { - paletteGenerator = - self.packages.${pkgs.stdenv.hostPlatform.system}.palette-generator; base16 = inputs.base16.lib args; homeManagerIntegration.module = self.homeModules.stylix; }; diff --git a/flake/packages.nix b/flake/packages.nix index 4e11148c0..ba768f368 100644 --- a/flake/packages.nix +++ b/flake/packages.nix @@ -11,7 +11,6 @@ serve-docs = pkgs.callPackage ../doc/server.nix { inherit (config.packages) doc; }; - palette-generator = pkgs.callPackage ../palette-generator { }; }; }; } diff --git a/modules/fnott/hm.nix b/modules/fnott/hm.nix index 984459b43..5e8b48a06 100644 --- a/modules/fnott/hm.nix +++ b/modules/fnott/hm.nix @@ -49,10 +49,10 @@ mkTarget { } ) ( - { polarity, icons }: + { colorGeneration, icons }: { services.fnott.settings.main."icon-theme" = - if (polarity == "dark") then icons.dark else icons.light; + if (colorGeneration.polarity == "dark") then icons.dark else icons.light; } ) ]; diff --git a/modules/fuzzel/hm.nix b/modules/fuzzel/hm.nix index 29b4836f7..9e6fc1b19 100644 --- a/modules/fuzzel/hm.nix +++ b/modules/fuzzel/hm.nix @@ -33,10 +33,10 @@ mkTarget { } ) ( - { polarity, icons }: + { colorGeneration, icons }: { programs.fuzzel.settings.main."icon-theme" = - if (polarity == "dark") then icons.dark else icons.light; + if (colorGeneration.polarity == "dark") then icons.dark else icons.light; } ) ]; diff --git a/modules/glance/hm.nix b/modules/glance/hm.nix index 6448d12b9..3fa3420d4 100644 --- a/modules/glance/hm.nix +++ b/modules/glance/hm.nix @@ -5,9 +5,9 @@ mkTarget { configElements = [ ( - { polarity }: + { colorGeneration }: { - services.glance.settings.theme.light = polarity == "light"; + services.glance.settings.theme.light = colorGeneration.polarity == "light"; } ) ( diff --git a/modules/glance/nixos.nix b/modules/glance/nixos.nix index 6448d12b9..3fa3420d4 100644 --- a/modules/glance/nixos.nix +++ b/modules/glance/nixos.nix @@ -5,9 +5,9 @@ mkTarget { configElements = [ ( - { polarity }: + { colorGeneration }: { - services.glance.settings.theme.light = polarity == "light"; + services.glance.settings.theme.light = colorGeneration.polarity == "light"; } ) ( diff --git a/modules/gnome/hm.nix b/modules/gnome/hm.nix index f94add89b..baaad1256 100644 --- a/modules/gnome/hm.nix +++ b/modules/gnome/hm.nix @@ -42,13 +42,13 @@ mkTarget { } ) ( - { polarity }: + { colorGeneration }: { # We show the same colours regardless of this setting, and the quick # settings tile is removed. The value is still used by Epiphany to # request dark mode for websites which support it. dconf.settings."org/gnome/desktop/interface".color-scheme = - if polarity == "dark" then "prefer-dark" else "default"; + if colorGeneration.polarity == "dark" then "prefer-dark" else "default"; } ) ( diff --git a/modules/gnome/nixos.nix b/modules/gnome/nixos.nix index 0e2cbe828..913438737 100644 --- a/modules/gnome/nixos.nix +++ b/modules/gnome/nixos.nix @@ -11,7 +11,7 @@ let }; cursorCfg = config.stylix.cursor; iconCfg = config.stylix.icons; - inherit (config.stylix) polarity; + inherit (config.stylix.colorGeneration) polarity; in { diff --git a/modules/kubecolor/hm.nix b/modules/kubecolor/hm.nix index 0670526cb..db0c5d32f 100644 --- a/modules/kubecolor/hm.nix +++ b/modules/kubecolor/hm.nix @@ -5,10 +5,9 @@ mkTarget { configElements = [ ( - { polarity }: + { colorGeneration }: { - programs.kubecolor.settings.preset = - if polarity == "either" then "" else polarity; + programs.kubecolor.settings.preset = colorGeneration.polarity; } ) ( diff --git a/modules/obsidian/hm.nix b/modules/obsidian/hm.nix index ecaa66e57..dab92fc5a 100644 --- a/modules/obsidian/hm.nix +++ b/modules/obsidian/hm.nix @@ -33,14 +33,14 @@ mkTarget { { cfg, colors, - polarity, + colorGeneration, }: { programs.obsidian.defaultSettings.cssSnippets = with colors.withHashtag; [ { name = "Stylix Config"; text = '' - .theme-${polarity} { + .theme-${colorGeneration.polarity} { /* Base Colors */ --color-base-00: ${base00}; --color-base-05: ${base00}; @@ -66,7 +66,7 @@ mkTarget { { name = "Stylix Config"; text = '' - .theme-${polarity} { + .theme-${colorGeneration.polarity} { /* Base Colors */ --color-base-00: ${base00}; --color-base-05: ${base00}; diff --git a/modules/qt/hm.nix b/modules/qt/hm.nix index 44098703a..776e6e499 100644 --- a/modules/qt/hm.nix +++ b/modules/qt/hm.nix @@ -33,13 +33,17 @@ config = lib.mkIf (config.stylix.enable && config.stylix.targets.qt.enable) ( let icons = - if (config.stylix.polarity == "dark") then + if (config.stylix.colorGeneration.polarity == "dark") then config.stylix.icons.dark else config.stylix.icons.light; recommendedStyles = { - gnome = if config.stylix.polarity == "dark" then "adwaita-dark" else "adwaita"; + gnome = + if config.stylix.colorGeneration.polarity == "dark" then + "adwaita-dark" + else + "adwaita"; kde = "breeze"; qtct = "kvantum"; }; diff --git a/modules/qt/nixos.nix b/modules/qt/nixos.nix index 9b0d3f72b..84af74afa 100644 --- a/modules/qt/nixos.nix +++ b/modules/qt/nixos.nix @@ -1,7 +1,11 @@ { lib, config, ... }: let recommendedStyle = { - gnome = if config.stylix.polarity == "dark" then "adwaita-dark" else "adwaita"; + gnome = + if config.stylix.colorGeneration.polarity == "dark" then + "adwaita-dark" + else + "adwaita"; kde = "breeze"; qtct = "kvantum"; }; diff --git a/modules/qutebrowser/hm.nix b/modules/qutebrowser/hm.nix index 1f2e0158f..f5ab48865 100644 --- a/modules/qutebrowser/hm.nix +++ b/modules/qutebrowser/hm.nix @@ -29,10 +29,10 @@ mkTarget { } ) ( - { polarity }: + { colorGeneration }: { programs.qutebrowser.settings.colors.webpage.preferred_color_scheme = lib.mkIf ( - polarity == "dark" + colorGeneration.polarity == "dark" ) "dark"; } ) diff --git a/modules/regreet/nixos.nix b/modules/regreet/nixos.nix index ce77a5ebd..0e7988074 100644 --- a/modules/regreet/nixos.nix +++ b/modules/regreet/nixos.nix @@ -63,10 +63,10 @@ mkTarget { } ) ( - { polarity }: + { colorGeneration }: { programs.regreet.settings.GTK.application_prefer_dark_theme = - polarity == "dark"; + colorGeneration.polarity == "dark"; } ) ( @@ -104,11 +104,11 @@ mkTarget { } ) ( - { polarity, icons }: + { colorGeneration, icons }: { programs.regreet.iconTheme = { inherit (icons) package; - name = if (polarity == "dark") then icons.dark else icons.light; + name = if (colorGeneration.polarity == "dark") then icons.dark else icons.light; }; } ) diff --git a/modules/vicinae/hm.nix b/modules/vicinae/hm.nix index fc17baa3a..ad6903e51 100644 --- a/modules/vicinae/hm.nix +++ b/modules/vicinae/hm.nix @@ -10,7 +10,7 @@ mkTarget { configElements = lib.optionals (options.services ? vicinae) [ ( - { colors, polarity }: + { colors, colorGeneration }: { services.vicinae = { settings.theme.name = "stylix"; @@ -18,7 +18,7 @@ mkTarget { meta = { name = "stylix"; description = "theme generated by stylix"; - variant = if polarity == "either" then "light" else polarity; + variant = colorGeneration.polarity; }; colors = with colors.withHashtag; { core = { diff --git a/palette-generator/Ai/Evolutionary.hs b/palette-generator/Ai/Evolutionary.hs deleted file mode 100644 index c6a0c8be3..000000000 --- a/palette-generator/Ai/Evolutionary.hs +++ /dev/null @@ -1,127 +0,0 @@ -{-# LANGUAGE MultiParamTypeClasses #-} - -module Ai.Evolutionary ( Species(..), evolve ) where - -import Data.Ord (Down (Down), comparing) -import Data.Vector ((!)) -import qualified Data.Vector as V -import Data.Vector.Algorithms.Intro (selectBy) -import System.Random (randomRIO) -import Text.Printf (printf) - -numSurvivors :: Int -numSurvivors = 500 - -numNewborns :: Int -numNewborns = 50000 - numSurvivors - -mutationProbability :: Double -mutationProbability = 0.75 - -randomFromVector :: V.Vector a -> IO a -randomFromVector vector = do - index <- randomRIO (0, V.length vector - 1) - return $ vector ! index - -{- | -A genotype is a value which is generated by the genetic algorithm. - -The environment is used to specify the problem for which -we are trying to find the optimal genotype. --} -class Species environment genotype where - -- | Randomly generate a new genotype. - generate :: environment -> IO genotype - - -- | Randomly mutate a single genotype. - mutate :: environment -> genotype -> IO genotype - - -- | Randomly combine two genotypes. - crossover :: environment -> genotype -> genotype -> IO genotype - - -- | Score a genotype. Higher numbers are better. - fitness :: environment -> genotype -> Double - -initialPopulation :: Species e g - => e -- ^ Environment - -> IO (V.Vector g) -- ^ Population -initialPopulation environment - = V.replicateM numSurvivors (generate environment) - --- | Expand a population by crossovers followed by mutations. -evolvePopulation :: Species e g - => e -- ^ Environment - -> V.Vector g -- ^ Survivors from previous generation - -> IO (V.Vector g) -- ^ New population -evolvePopulation environment population = do - let randomCrossover = do - a <- randomFromVector population - b <- randomFromVector population - crossover environment a b - - randomMutation chromosome = do - r <- randomRIO (0.0, 1.0) - if r <= mutationProbability - then mutate environment chromosome - else return chromosome - - newborns <- V.replicateM numNewborns randomCrossover - let nonElites = V.tail population V.++ newborns - nonElites' <- V.mapM randomMutation nonElites - return $ V.head population `V.cons` nonElites' - -selectSurvivors :: Species e g - => e -- ^ Environment - -> V.Vector g -- ^ Original population - -> (Double, V.Vector g) -- ^ Best fitness, survivors -selectSurvivors environment population = - let -- Fitness is stored to avoid calculating it for each comparison. - calculateFitness g = (fitness environment g, g) - getFitness = fst - getGenotype = snd - compareFitness = comparing $ Down . fst - - -- Moves k best genotypes to the front, but doesn't sort them further. - selectBest k vector = selectBy compareFitness vector k - - selected = V.modify (selectBest 1) - $ V.take numSurvivors - $ V.modify (selectBest numSurvivors) - $ V.map calculateFitness population - - in ( getFitness $ V.head selected - , V.map getGenotype selected - ) - -shouldContinue :: [Double] -- ^ Fitness history - -> Bool -shouldContinue (x:y:_) = x /= y -shouldContinue _ = True - -evolutionLoop :: Species e g - => e -- ^ Environment - -> [Double] -- ^ Fitness history - -> V.Vector g -- ^ Survivors from previous generation - -> IO (V.Vector g) -- ^ Final population -evolutionLoop environment history survivors = - do - population <- evolvePopulation environment survivors - - let (bestFitness, survivors') = selectSurvivors environment population - history' = bestFitness : history - - printf "Generation: %3i Fitness: %7.1f\n" - (length history') (head history') - - if shouldContinue history' - then evolutionLoop environment history' survivors' - else return survivors' - --- | Run the genetic algorithm. -evolve :: Species e g - => e -- ^ Environment - -> IO g -- ^ Optimal genotype -evolve environment = do - population <- initialPopulation environment - survivors <- evolutionLoop environment [] population - return $ V.head survivors diff --git a/palette-generator/Data/Colour.hs b/palette-generator/Data/Colour.hs deleted file mode 100644 index 45dc33d38..000000000 --- a/palette-generator/Data/Colour.hs +++ /dev/null @@ -1,70 +0,0 @@ -module Data.Colour ( LAB(..), RGB(..), deltaE, lab2rgb, rgb2lab ) where - -data LAB = LAB { lightness :: Double - , channelA :: Double - , channelB :: Double - } - -data RGB = RGB { red :: Double - , green :: Double - , blue :: Double - } - --- Based on https://github.com/antimatter15/rgb-lab/blob/master/color.js - -deltaE :: LAB -> LAB -> Double -deltaE (LAB l1 a1 b1) (LAB l2 a2 b2) = - let deltaL = l1 - l2 - deltaA = a1 - a2 - deltaB = b1 - b2 - c1 = sqrt $ a1^2 + b1^2 - c2 = sqrt $ a2^2 + b2^2 - deltaC = c1 - c2 - deltaH = deltaA^2 + deltaB^2 - deltaC^2 - deltaH' = if deltaH < 0 then 0 else sqrt deltaH - sc = 1 + 0.045 * c1 - sh = 1 + 0.015 * c1 - deltaCkcsc = deltaC / sc - deltaHkhsh = deltaH' / sh - i = deltaL^2 + deltaCkcsc^2 + deltaHkhsh^2 - in if i < 0 then 0 else sqrt i - --- | Convert a 'LAB' colour to a 'RGB' colour -lab2rgb :: LAB -> RGB -lab2rgb (LAB l a bx) = - let y = (l + 16) / 116 - x = a / 500 + y - z = y - bx / 200 - x' = 0.95047 * (if x^3 > 0.008856 then x^3 else (x - 16/116) / 7.787) - y' = if y^3 > 0.008856 then y^3 else (y - 16/116) / 7.787 - z' = 1.08883 * (if z^3 > 0.008856 then z^3 else (z - 16/116) / 7.787) - r = x' * 3.2406 + y' * (-1.5372) + z' * (-0.4986) - g = x' * (-0.9689) + y' * 1.8758 + z' * 0.0415 - b = x' * 0.0557 + y' * (-0.204) + z' * 1.0570 - r' = if r > 0.0031308 then 1.055 * r**(1/2.4) - 0.055 else 12.92 * r - g' = if g > 0.0031308 then 1.055 * g**(1/2.4) - 0.055 else 12.92 * g - b' = if b > 0.0031308 then 1.055 * b**(1/2.4) - 0.055 else 12.92 * b - in RGB { red = max 0 (min 1 r') * 255 - , green = max 0 (min 1 g') * 255 - , blue = max 0 (min 1 b') * 255 - } - --- | Convert a 'RGB' colour to a 'LAB' colour -rgb2lab :: RGB -> LAB -rgb2lab (RGB r g b) = - let r' = r / 255 - g' = g / 255 - b' = b / 255 - r'' = if r' > 0.04045 then ((r' + 0.055) / 1.055)**2.4 else r' / 12.92 - g'' = if g' > 0.04045 then ((g' + 0.055) / 1.055)**2.4 else g' / 12.92 - b'' = if b' > 0.04045 then ((b' + 0.055) / 1.055)**2.4 else b' / 12.92 - x = (r'' * 0.4124 + g'' * 0.3576 + b'' * 0.1805) / 0.95047 - y = r'' * 0.2126 + g'' * 0.7152 + b'' * 0.0722 - z = (r'' * 0.0193 + g'' * 0.1192 + b'' * 0.9505) / 1.08883 - x' = if x > 0.008856 then x**(1/3) else (7.787 * x) + 16/116 - y' = if y > 0.008856 then y**(1/3) else (7.787 * y) + 16/116 - z' = if z > 0.008856 then z**(1/3) else (7.787 * z) + 16/116 - in LAB { lightness = (116 * y') - 16 - , channelA = 500 * (x' - y') - , channelB = 200 * (y' - z') - } diff --git a/palette-generator/Stylix/Main.hs b/palette-generator/Stylix/Main.hs deleted file mode 100644 index 2170d6243..000000000 --- a/palette-generator/Stylix/Main.hs +++ /dev/null @@ -1,39 +0,0 @@ -import Ai.Evolutionary (evolve) -import Codec.Picture (DynamicImage, convertRGB8, readImage) -import Data.Colour (lab2rgb) -import qualified Data.Vector as V -import Stylix.Output (makeOutputTable) -import Stylix.Palette () -import System.Environment (getArgs) -import System.Exit (die) -import System.Random (mkStdGen, setStdGen) -import Text.JSON (encode) - --- | Load an image file. -loadImage :: String -- ^ Path to the file - -> IO DynamicImage -loadImage input = either error id <$> readImage input - -mainProcess :: (String, String, String) -> IO () -mainProcess (polarity, input, output) = do - putStrLn $ "Processing " ++ input - - -- Random numbers must be deterministic when running inside Nix. - setStdGen $ mkStdGen 0 - - image <- loadImage input - palette <- evolve (polarity, convertRGB8 image) - let outputTable = makeOutputTable $ V.map lab2rgb palette - - writeFile output $ encode outputTable - putStrLn $ "Saved to " ++ output - -parseArguments :: [String] -> Either String (String, String, String) -parseArguments [polarity, input, output] = Right (polarity, input, output) -parseArguments [_, _] = Left "Please specify an output file" -parseArguments [_] = Left "Please specify an image" -parseArguments [] = Left "Please specify a polarity: either, light or dark" -parseArguments _ = Left "Too many arguments" - -main :: IO () -main = either die mainProcess . parseArguments =<< getArgs diff --git a/palette-generator/Stylix/Output.hs b/palette-generator/Stylix/Output.hs deleted file mode 100644 index 682d42123..000000000 --- a/palette-generator/Stylix/Output.hs +++ /dev/null @@ -1,27 +0,0 @@ -module Stylix.Output ( makeOutputTable ) where - -import Data.Colour (RGB (RGB)) -import qualified Data.Vector as V -import Data.Word (Word8) -import Text.JSON (JSObject, toJSObject) -import Text.Printf (printf) - -toHexNum :: Double -> Word8 -toHexNum = truncate - -{- | -Convert a colour to a hexadecimal string. - ->>> toHex (RGB 255 255 255) -"ffffff" --} -toHex :: RGB -> String -toHex (RGB r g b) = printf "%02x%02x%02x" (toHexNum r) (toHexNum g) (toHexNum b) - --- | Convert a palette to the JSON format expected by Stylix's NixOS modules. -makeOutputTable :: V.Vector RGB -> JSObject String -makeOutputTable - = toJSObject - . V.toList - . V.imap (\i c -> (printf "base%02X" i, c)) - . V.map toHex diff --git a/palette-generator/Stylix/Palette.hs b/palette-generator/Stylix/Palette.hs deleted file mode 100644 index 1b8b0d784..000000000 --- a/palette-generator/Stylix/Palette.hs +++ /dev/null @@ -1,94 +0,0 @@ -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE MultiParamTypeClasses #-} - -module Stylix.Palette ( ) where - -import Ai.Evolutionary (Species (..)) -import Codec.Picture (Image (imageHeight, imageWidth), - PixelRGB8 (PixelRGB8), pixelAt) -import Data.Colour (LAB (lightness), RGB (RGB), deltaE, rgb2lab) -import Data.List (delete) -import Data.Vector ((//)) -import qualified Data.Vector as V -import System.Random (randomRIO) - --- | Extract the primary scale from a palette. -primary :: V.Vector a -> V.Vector a -primary = V.take 8 - --- | Extract the accent colours from a palette. -accent :: V.Vector a -> V.Vector a -accent = V.drop 8 - -{- | -Combine two palettes by taking a colour from the left, -then the right, then the left, and so on until we have -taken enough colours for a new palette. --} -alternatingZip :: V.Vector a -> V.Vector a -> V.Vector a -alternatingZip = V.izipWith (\i a b -> if even i then a else b) - -randomFromImage :: Image PixelRGB8 -> IO LAB -randomFromImage image = do - x <- randomRIO (0, imageWidth image - 1) - y <- randomRIO (0, imageHeight image - 1) - let (PixelRGB8 r g b) = pixelAt image x y - color = RGB (fromIntegral r) (fromIntegral g) (fromIntegral b) - return $ rgb2lab color - -instance Species (String, Image PixelRGB8) (V.Vector LAB) where - generate (_, image) = V.replicateM 16 $ randomFromImage image - - crossover _ a b = return $ alternatingZip a b - - mutate (_, image) palette = do - index <- randomRIO (0, 15) - colour <- randomFromImage image - return $ palette // [(index, colour)] - - fitness (polarity, _) palette - = realToFrac $ accentDifference - (primarySimilarity/10) - scheme - where - -- The primary scale should use similar colours, to an extent. - primarySimilarity = maximum $ do - a <- primary palette - b <- primary palette - return $ deltaE a b - - -- The accent colours should be as different as possible. - accentDifference = minimum $ do - index_x <- [0..7] - index_y <- delete index_x [0..7] - let x = accent palette V.! index_x - y = accent palette V.! index_y - return $ deltaE x y - - -- Helpers for the function below. - lightnesses = V.map lightness palette - difference a b = abs $ a - b - - lightnessError primaryScale accentValue - -- The primary scale's lightnesses should match the given pattern. - = sum (V.zipWith difference primaryScale $ primary lightnesses) - -- The accent colours should all have the given lightness. - + sum (V.map (difference accentValue) $ accent lightnesses) - - scheme = case polarity of - "either" -> min lightScheme darkScheme - "light" -> lightScheme - "dark" -> darkScheme - _ -> error ("Invalid polarity: " ++ polarity) - - {- - For light themes, the background is bright and the text is dark. - The accent colours are slightly darker. - -} - lightScheme - = lightnessError (V.fromList [90, 70, 55, 35, 25, 10, 5, 5]) 40 - - {- - For dark themes, the background is dark and the text is bright. - The accent colours are slightly brighter. - -} - darkScheme - = lightnessError (V.fromList [10, 30, 45, 65, 75, 90, 95, 95]) 60 diff --git a/palette-generator/default.nix b/palette-generator/default.nix deleted file mode 100644 index ccd3fec5a..000000000 --- a/palette-generator/default.nix +++ /dev/null @@ -1,42 +0,0 @@ -{ haskellPackages, stdenvNoCC }: -let - ghc = haskellPackages.ghcWithPackages ( - ps: with ps; [ - JuicyPixels - json - random - vector-algorithms - ] - ); - - # `nix build .#palette-generator.doc && xdg-open result/index.html` - doc = stdenvNoCC.mkDerivation { - name = "palette-generator-haddock"; - - src = ./.; - buildInputs = [ ghc ]; - - buildPhase = '' - haddock $src/**/*.hs --html --ignore-all-exports --odir $out - ''; - dontInstall = true; - dontFixup = true; - }; -in -stdenvNoCC.mkDerivation { - name = "palette-generator"; - - src = ./.; - buildInputs = [ ghc ]; - - buildPhase = '' - ghc -O -threaded -Wall -Wno-type-defaults Stylix/Main.hs - ''; - installPhase = '' - install -D Stylix/Main $out/bin/palette-generator - ''; - - passthru = { inherit doc; }; - - meta.mainProgram = "palette-generator"; -} diff --git a/stylix/hm/icons.nix b/stylix/hm/icons.nix index b23c87426..b89e3070c 100644 --- a/stylix/hm/icons.nix +++ b/stylix/hm/icons.nix @@ -1,7 +1,7 @@ { config, lib, ... }: let cfg = config.stylix.icons; - inherit (config.stylix) polarity; + inherit (config.stylix.colorGeneration) polarity; in { config = lib.mkIf (config.stylix.enable && cfg.enable) { diff --git a/stylix/home-manager-integration.nix b/stylix/home-manager-integration.nix index a28866cc6..303727384 100644 --- a/stylix/home-manager-integration.nix +++ b/stylix/home-manager-integration.nix @@ -34,6 +34,27 @@ let ]; condition = homeConfig: config.stylix.image == homeConfig.stylix.image; } + { + path = [ + "stylix" + "colorGeneration" + "contrast" + ]; + } + { + path = [ + "stylix" + "colorGeneration" + "polarity" + ]; + } + { + path = [ + "stylix" + "colorGeneration" + "scheme" + ]; + } { path = [ "stylix" @@ -160,12 +181,6 @@ let condition = homeConfig: config.stylix.base16Scheme == homeConfig.stylix.base16Scheme; } - { - path = [ - "stylix" - "polarity" - ]; - } { path = [ "stylix" diff --git a/stylix/palette.nix b/stylix/palette.nix index 44e339f12..78791171f 100644 --- a/stylix/palette.nix +++ b/stylix/palette.nix @@ -10,21 +10,66 @@ let opts = options.stylix; in { + imports = lib.singleton ( + lib.mkRenamedOptionModule + [ "stylix" "polarity" ] + [ "stylix" "colorGeneration" "polarity" ] + ); options.stylix = { - polarity = lib.mkOption { - type = lib.types.enum [ - "either" - "light" - "dark" - ]; - default = "either"; - description = '' - Use this option to force a light or dark theme. + colorGeneration = { + contrast = lib.mkOption { + type = lib.types.addCheck lib.types.float (x: x >= -1.0 && x <= 1.0); + default = 0.0; + description = '' + The contrast of the generated color scheme, ranging from `-1.0` to + `1.0` (inclusive). + ''; + }; - By default we will select whichever is ranked better by the genetic - algorithm. This aims to get good contrast between the foreground and - background, as well as some variety in the highlight colours. - ''; + filter = lib.mkOption { + type = lib.types.enum [ + "catmull-rom" + "gaussian" + "lanczos3" + "nearest" + "triangle" + ]; + default = "lanczos3"; + example = "nearest"; + description = '' + [Matugen](https://github.com/InioX/matugen)'s resize filter. + ''; + }; + + polarity = lib.mkOption { + type = lib.types.enum [ + "dark" + "light" + ]; + default = "dark"; + example = "light"; + description = "Whether to apply the dark or light theme."; + }; + + scheme = lib.mkOption { + type = lib.types.enum [ + "content" + "expressive" + "fidelity" + "fruit-salad" + "monochrome" + "neutral" + "rainbow" + "tonal-spot" + "vibrant" + ]; + default = "content"; + example = "tonal-spot"; + description = '' + [Matugen](https://github.com/InioX/matugen)'s color scheme type. + ''; + apply = value: "scheme-${value}"; + }; }; image = lib.mkOption { @@ -80,25 +125,94 @@ in # and not anything indirect such as filling a template, otherwise # the output of the palette generator will not be protected from # garbage collection. - default = pkgs.runCommand "palette.json" { } '' - ${lib.getExe cfg.paletteGenerator} \ - "${cfg.polarity}" \ - ${lib.escapeShellArg cfg.image} \ - "$out" - ''; + default = + pkgs.runCommand "palette.json" + { + nativeBuildInputs = [ pkgs.matugen ]; + env = { + CONTRAST = toString cfg.colorGeneration.contrast; + FILTER = cfg.colorGeneration.filter; + IMAGE = cfg.image; + POLARITY = cfg.colorGeneration.polarity; + SCHEME = cfg.colorGeneration.scheme; + }; + } + '' + matugen \ + --contrast "$CONTRAST" \ + --dry-run \ + --include-image-in-json false \ + --json strip \ + --mode "$POLARITY" \ + --resize-filter "$FILTER" \ + --type "$SCHEME" \ + image \ + "$IMAGE" | + sed -E 's/"image":[[:space:]]*"[^"]*",?//g' \ + >$out''; }; - palette = lib.mkOption { - type = lib.types.attrs; - description = "The palette generated by the palette generator."; - readOnly = true; - internal = true; - default = (lib.importJSON cfg.generated.json) // { + palette = + lib.mkOption { + type = lib.types.attrs; + description = "The palette generated by the palette generator."; + readOnly = true; + internal = true; + default = + builtins.mapAttrs + ( + let + inherit (lib.importJSON cfg.generated.json) colors; + in + _: color: colors.${color}.${cfg.colorGeneration.polarity} + ) + ( + if cfg.colorGeneration.polarity == "dark" then + { + base00 = "surface_container_lowest"; + base01 = "surface_container"; + base02 = "surface_container_highest"; + base03 = "outline"; + base04 = "on_surface_variant"; + base05 = "on_surface"; + base06 = "secondary_fixed"; + base07 = "primary"; + base08 = "error"; + base09 = "tertiary"; + base0A = "secondary"; + base0B = "primary"; + base0C = "primary_fixed"; + base0D = "surface_tint"; + base0E = "tertiary_fixed"; + base0F = "on_error_container"; + } + + else + { + base00 = "surface"; + base01 = "surface_container"; + base02 = "surface_container_highest"; + base03 = "outline"; + base04 = "on_surface_variant"; + base05 = "on_surface"; + base06 = "tertiary_container"; + base07 = "on_primary_fixed_variant"; + base08 = "error"; + base09 = "tertiary"; + base0A = "secondary"; + base0B = "primary"; + base0C = "primary_container"; + base0D = "surface_tint"; + base0E = "secondary_fixed_dim"; + base0F = "inverse_surface"; + } + ); + } + // { author = "Stylix"; scheme = "Stylix"; slug = "stylix"; }; - }; fileTree = lib.mkOption { type = lib.types.raw; diff --git a/stylix/testbed/themes/cursorless.nix b/stylix/testbed/themes/cursorless.nix index 28d4e0b58..c3db5cf1a 100644 --- a/stylix/testbed/themes/cursorless.nix +++ b/stylix/testbed/themes/cursorless.nix @@ -8,6 +8,6 @@ in enable = true; image = images.dark; base16Scheme = "${tinted-schemes}/base16/catppuccin-macchiato.yaml"; - polarity = "dark"; + colorGeneration.polarity = "dark"; }; } diff --git a/stylix/testbed/themes/dark.nix b/stylix/testbed/themes/dark.nix index 4a4f2c5dc..a13d65782 100644 --- a/stylix/testbed/themes/dark.nix +++ b/stylix/testbed/themes/dark.nix @@ -8,7 +8,7 @@ in enable = true; image = images.dark; base16Scheme = "${tinted-schemes}/base16/catppuccin-macchiato.yaml"; - polarity = "dark"; + colorGeneration.polarity = "dark"; cursor = { name = "Vanilla-DMZ"; package = pkgs.vanilla-dmz; diff --git a/stylix/testbed/themes/imageless.nix b/stylix/testbed/themes/imageless.nix index f630cdcc0..8b133b148 100644 --- a/stylix/testbed/themes/imageless.nix +++ b/stylix/testbed/themes/imageless.nix @@ -6,7 +6,7 @@ in stylix = { enable = true; base16Scheme = "${tinted-schemes}/base16/catppuccin-macchiato.yaml"; - polarity = "dark"; + colorGeneration.polarity = "dark"; cursor = { name = "Vanilla-DMZ"; package = pkgs.vanilla-dmz; diff --git a/stylix/testbed/themes/light.nix b/stylix/testbed/themes/light.nix index 643dbf844..763e0d6ce 100644 --- a/stylix/testbed/themes/light.nix +++ b/stylix/testbed/themes/light.nix @@ -8,7 +8,7 @@ in enable = true; image = images.light; base16Scheme = "${tinted-schemes}/base16/catppuccin-latte.yaml"; - polarity = "light"; + colorGeneration.polarity = "light"; cursor = { name = "Vanilla-DMZ"; package = pkgs.vanilla-dmz; diff --git a/stylix/testbed/themes/schemeless.nix b/stylix/testbed/themes/schemeless.nix index 91c6789d3..fc0d27250 100644 --- a/stylix/testbed/themes/schemeless.nix +++ b/stylix/testbed/themes/schemeless.nix @@ -6,7 +6,7 @@ in stylix = { enable = true; image = images.light; - polarity = "light"; + colorGeneration.polarity = "light"; cursor = { name = "Vanilla-DMZ"; package = pkgs.vanilla-dmz;