5.10 Override color saturation

In the previous section we documented how one can override color values manually (Override colors). Here we use a programmatic approach which leverages the built-in color-saturate-name function to adjust the saturation of all color values used by the active Modus theme. Our goal is to prepare a counterpart of the active theme’s palette that holds modified color values, adjusted for a percent change in saturation. A positive number amplifies the effect, while a negative one will move towards a grayscale spectrum.

We start with a function that can be either called from Lisp or invoked interactively. In the former scenario, we pass to it the rate of change we want. While in the latter, a minibuffer prompt asks for a number to apply the desired effect. In either case, we intend to assign anew the value of modus-themes-operandi-color-overrides (light theme) and the same for modus-themes-vivendi-color-overrides (dark theme).

(defun my-modus-themes-saturate (percent)
  "Saturate current Modus theme palette overrides by PERCENT."
  (interactive
   (list (read-number "Saturation by percent: ")))
  (let* ((theme (modus-themes--current-theme))
         (palette (pcase theme
                    ('modus-operandi modus-themes-operandi-colors)
                    ('modus-vivendi modus-themes-vivendi-colors)
                    (_ (error "No Modus theme is active"))))
         (overrides (pcase theme
                      ('modus-operandi 'modus-themes-operandi-color-overrides)
                      ('modus-vivendi 'modus-themes-vivendi-color-overrides)
                      (_ (error "No Modus theme is active")))))
    (let (name cons colors)
      (dolist (cons palette)
        (setq name (color-saturate-name (cdr cons) percent))
        (setq name (format "%s" name))
        (setq cons `(,(car cons) . ,name))
        (push cons colors))
      (set overrides colors))
    (pcase theme
      ('modus-operandi (modus-themes-load-operandi))
      ('modus-vivendi (modus-themes-load-vivendi)))))

;; sample Elisp calls (or call `my-modus-themes-saturate' interactively)
(my-modus-themes-saturate 50)
(my-modus-themes-saturate -75)

Using the above has an immediate effect, as it reloads the active Modus theme.

The ‘my-modus-themes-saturate’ function stores new color values in the variables modus-themes-operandi-color-overrides and modus-themes-vivendi-color-overrides, meaning that it undoes changes implemented by the user on individual colors. To have both automatic saturation adjustment across the board and retain per-case edits to the palette, some tweaks to the above function are required. For example:

(defvar my-modus-themes-vivendi-extra-color-overrides
  '((fg-main . "#ead0c0")
    (bg-main . "#050515"))
  "My bespoke colors for `modus-vivendi'.")

(defvar my-modus-themes-operandi-extra-color-overrides
  '((fg-main . "#1a1a1a")
    (bg-main . "#fefcf4"))
  "My bespoke colors for `modus-operandi'.")

(defun my-modus-themes-saturate (percent)
  "Saturate current Modus theme palette overrides by PERCENT.
Preserve the color values stored in
`my-modus-themes-operandi-extra-color-overrides',
`my-modus-themes-vivendi-extra-color-overrides'."
  (interactive
   (list (read-number "Saturation by percent: ")))
  (let* ((theme (modus-themes--current-theme))
         (palette (pcase theme
                    ('modus-operandi modus-themes-operandi-colors)
                    ('modus-vivendi modus-themes-vivendi-colors)
                    (_ (error "No Modus theme is active"))))
         (overrides (pcase theme
                      ('modus-operandi 'modus-themes-operandi-color-overrides)
                      ('modus-vivendi 'modus-themes-vivendi-color-overrides)
                      (_ (error "No Modus theme is active"))))
         (extra-overrides (pcase theme
                            ('modus-operandi my-modus-themes-operandi-extra-color-overrides)
                            ('modus-vivendi my-modus-themes-vivendi-extra-color-overrides)
                            (_ (error "No Modus theme is active")))))
    (let (name cons colors)
      (dolist (cons palette)
        (setq name (color-saturate-name (cdr cons) percent))
        (setq name (format "%s" name))
        (setq cons `(,(car cons) . ,name))
        (push cons colors))
      (set overrides (append extra-overrides colors)))
    (pcase theme
      ('modus-operandi (modus-themes-load-operandi))
      ('modus-vivendi (modus-themes-load-vivendi)))))

To disable the effect, one must reset the aforementioned variables of the themes to nil. Or specify a command for it, such as by taking inspiration from the modus-themes-toggle we already provide:

(defun my-modus-themes-revert-overrides ()
  "Reset palette overrides and reload active Modus theme."
  (interactive)
  (setq modus-themes-operandi-color-overrides nil
        modus-themes-vivendi-color-overrides nil)
  (pcase (modus-themes--current-theme)
    ('modus-operandi (modus-themes-load-operandi))
    ('modus-vivendi (modus-themes-load-vivendi))))