{ config, inputs, lib, pkgs, ... }: let cfg = config.theme; init = pkgs.writeShellApplication { name = "theme-init"; runtimeInputs = with pkgs; [ matugen ]; text = '' [[ ! -d "${cfg.configDir}" ]] && mkdir -p "${cfg.configDir}" [[ ! -L "${cfg.configDir}"/wallpaper ]] && ln -sf "${cfg.wallpaper}" "${cfg.configDir}"/wallpaper [[ ! -f "${cfg.configDir}"/mode ]] && echo "${cfg.mode}" > "${cfg.configDir}"/mode matugen image "${cfg.configDir}/wallpaper" \ --type scheme-${cfg.flavour} \ --mode "$(cat "${cfg.configDir}/mode")" \ --contrast ${builtins.toString cfg.contrast} ${cfg.initExtraConfig} wait ''; }; reload = pkgs.writeShellApplication { name = "theme-reload"; text = '' ${cfg.reloadExtraConfig} wait ''; }; theme = pkgs.writeShellApplication { name = "theme"; runtimeInputs = with pkgs; [ coreutils ]; runtimeEnv = { CONFIG = cfg.configDir; DEFAULT_WALLPAPER = cfg.wallpaper; DEFAULT_MODE = cfg.mode; INIT = lib.meta.getExe init; RELOAD = lib.meta.getExe reload; }; text = builtins.readFile ./theme.sh; }; in { # https://github.com/Theaninova/TheaninovOS/blob/master/modules/home-manager/theme/md3-evo.nix options.theme = with lib; with types; { enable = mkEnableOption "theme"; configDir = mkOption { type = str; default = "${config.xdg.configHome}/theme"; description = "The path to the theme config directory."; }; wallpaper = mkOption { type = path; description = "The path to the default wallpaper"; }; pkg = mkOption { type = package; default = theme; readOnly = true; description = "The package containing the `theme` script"; }; initExtraConfig = mkOption { type = lines; default = ""; description = "Extra configuration lines to add to the theme initialization script."; }; reloadExtraConfig = mkOption { type = lines; default = ""; description = "Extra configuration lines to add to the theme reload script."; }; template = mkOption { type = attrsOf ( submodule ( { name, config, ... }: { options = { source = mkOption { type = nullOr path; description = "Path of the source file or directory."; default = null; }; text = mkOption { type = nullOr str; description = "Text of the file."; default = null; }; target = mkOption { type = str; defaultText = literalExpression "name"; description = "Path to target relative to the user's {env}`HOME`."; }; }; config.target = mkDefault name; } ) ); default = { }; description = "Templates to fill with theme colors."; }; flavour = mkOption { type = enum [ "content" "expressive" "fidelity" "fruit-salad" "monochrome" "neutral" "rainbow" "tonal-spot" ]; default = "tonal-spot"; description = "The flavour of the theme."; }; mode = mkOption { type = enum [ "dark" "light" ]; default = "dark"; description = "The default mode of the theme."; }; contrast = mkOption { type = numbers.between (-1) 1; default = 0; description = "Use a modified contrast."; }; opacity = mkOption { type = numbers.between 0 1; default = 1; description = "The opacity of apps."; }; radius = mkOption { type = ints.unsigned; default = 8; description = "The radius of corners."; }; padding = mkOption { type = ints.unsigned; default = 8; description = "The padding of windows."; }; blur = mkOption { type = ints.unsigned; default = 0; description = "The blur amount of windows."; }; color = { semantic = { blend = mkOption { type = bool; default = true; description = "Blend the colors."; }; danger = mkOption { type = str; default = "#ff0000"; description = "The color of danger."; }; warning = mkOption { type = str; default = "#ffff00"; description = "The color of warning."; }; success = mkOption { type = str; default = "#00ff00"; description = "The color of success."; }; info = mkOption { type = str; default = "#0000ff"; description = "The color of info."; }; }; syntax = { blend = mkOption { type = bool; default = true; description = "Blend the colors."; }; keywords = mkOption { type = str; default = "#ff8000"; description = "The color of keywords."; }; functions = mkOption { type = str; default = "#0000ff"; description = "The color of functions."; }; properties = mkOption { type = str; default = "#ff00ff"; description = "The color of properties."; }; constants = mkOption { type = str; default = "#ff00ff"; description = "The color of constants."; }; strings = mkOption { type = str; default = "#00ff00"; description = "The color of variables."; }; numbers = mkOption { type = str; default = "#00ffff"; description = "The color of numbers."; }; structures = mkOption { type = str; default = "#ffff00"; description = "The color of structures."; }; types = mkOption { type = str; default = "#00ffff"; description = "The color of types."; }; }; basic = { blend = mkOption { type = bool; default = true; description = "Blend the colors."; }; red = mkOption { type = str; default = "#ff0000"; description = "The color of red."; }; orange = mkOption { type = str; default = "#ff8000"; description = "The color of orange."; }; yellow = mkOption { type = str; default = "#ffff00"; description = "The color of yellow."; }; green = mkOption { type = str; default = "#00ff00"; description = "The color of green."; }; cyan = mkOption { type = str; default = "#00ffff"; description = "The color of cyan."; }; blue = mkOption { type = str; default = "#0000ff"; description = "The color of blue."; }; magenta = mkOption { type = str; default = "#ff00ff"; description = "The color of magenta."; }; pink = mkOption { type = str; default = "#ffc0cb"; description = "The color of pink."; }; }; }; font = { sansSerif = { names = mkOption { type = listOf str; default = [ "Roboto" ]; description = "The sans serif font families."; }; packages = mkOption { type = listOf package; default = with pkgs; [ roboto ]; description = "The sans serif font packages."; }; }; serif = { names = mkOption { type = listOf str; default = [ "Roboto Serif" ]; description = "The serif font families."; }; packages = mkOption { type = listOf package; default = with pkgs; [ roboto-serif ]; description = "The serif font packages."; }; }; monospace = { names = mkOption { type = listOf str; default = [ "JetBrainsMono Nerd Font" ]; description = "The monospace font families."; }; packages = mkOption { type = listOf package; default = with pkgs; [ nerd-fonts.jetbrains-mono ]; description = "The monospace font packages."; }; }; emoji = { names = mkOption { type = listOf str; default = [ "Noto Emoji" "Font Awesome" ]; description = "The emoji font families."; }; packages = mkOption { type = listOf package; default = with pkgs; [ noto-fonts-color-emoji font-awesome ]; description = "The emoji font packages."; }; }; size = mkOption { type = ints.positive; default = 12; description = "The font size."; }; }; icon = { names = mkOption { type = listOf str; default = [ "Adwaita" ]; description = "The icon theme names."; }; packages = mkOption { type = listOf package; default = with pkgs; [ adwaita-icon-theme nixos-icons ]; description = "The icon theme packages."; }; }; cursor = { names = mkOption { type = listOf str; default = [ "Adwaita" ]; description = "The cursor names."; }; packages = mkOption { type = listOf package; default = with pkgs; [ adwaita-icon-theme ]; description = "The cursor theme packages."; }; size = mkOption { type = ints.positive; default = 32; description = "The cursor size."; }; }; }; config = lib.mkIf cfg.enable { home = { activation.themeInit = inputs.home-manager.lib.hm.dag.entryAfter [ "writeBoundary" "dconfSettings" ] "run ${lib.meta.getExe init}"; packages = with pkgs; [ matugen theme ] ++ cfg.font.sansSerif.packages ++ cfg.font.serif.packages ++ cfg.font.monospace.packages ++ cfg.font.emoji.packages ++ cfg.icon.packages ++ cfg.cursor.packages; pointerCursor = { name = builtins.head cfg.cursor.names; package = builtins.head cfg.cursor.packages; inherit (cfg.cursor) size; }; }; fonts.fontconfig = { enable = true; defaultFonts = { sansSerif = cfg.font.sansSerif.names; serif = cfg.font.serif.names; monospace = cfg.font.monospace.names; emoji = cfg.font.emoji.names; }; }; xdg.configFile."matugen/config.toml".source = ( (pkgs.formats.toml { }).generate "matugen" { config = { custom_colors = let mkColor = category: color: { color = cfg.color.${category}.${color}; blend = cfg.color.${category}.blend; }; in { danger = mkColor "semantic" "danger"; warning = mkColor "semantic" "warning"; success = mkColor "semantic" "success"; info = mkColor "semantic" "info"; red = mkColor "basic" "red"; orange = mkColor "basic" "orange"; yellow = mkColor "basic" "yellow"; green = mkColor "basic" "green"; cyan = mkColor "basic" "cyan"; blue = mkColor "basic" "blue"; magenta = mkColor "basic" "magenta"; pink = mkColor "basic" "pink"; keywords = mkColor "syntax" "keywords"; functions = mkColor "syntax" "functions"; properties = mkColor "syntax" "properties"; constants = mkColor "syntax" "constants"; strings = mkColor "syntax" "strings"; numbers = mkColor "syntax" "numbers"; structures = mkColor "syntax" "structures"; types = mkColor "syntax" "types"; }; custom_keywords = let zeroPad = hex: if builtins.stringLength hex == 1 then "0${hex}" else hex; percentageToHex = percentage: zeroPad (lib.trivial.toHexString (builtins.floor (percentage * 255))); in { flavour = cfg.flavour; contrast = builtins.toString cfg.contrast; radius = builtins.toString cfg.radius; padding = builtins.toString cfg.padding; padding_double = builtins.toString (cfg.padding * 2); blur = builtins.toString cfg.blur; opacity = builtins.toString cfg.opacity; opacity_hex = builtins.toString (percentageToHex cfg.opacity); opacity_shadow = builtins.toString (cfg.opacity * 0.75); opacity_shadow_hex = builtins.toString (percentageToHex (cfg.opacity * 0.75)); font_size = builtins.toString cfg.font.size; font_sans_serif = builtins.head cfg.font.sansSerif.names; font_sans_serif_all = builtins.concatStringsSep ", " cfg.font.sansSerif.names; font_serif = builtins.head cfg.font.serif.names; font_serif_all = builtins.concatStringsSep ", " cfg.font.serif.names; font_monospace = builtins.head cfg.font.monospace.names; font_monospace_all = builtins.concatStringsSep ", " cfg.font.monospace.names; font_emoji = builtins.head cfg.font.emoji.names; font_emoji_all = builtins.concatStringsSep ", " cfg.font.emoji.names; }; }; templates = builtins.mapAttrs (name: template: { input_path = template.source or (pkgs.writeText name template.text); output_path = template.target; }) cfg.template; } ); programs.zsh.initExtra = builtins.readFile ./theme.completion.sh; }; }