;;; ef-themes.el --- Colorful and legible themes -*- lexical-binding:t -*-

;; Copyright (C) 2022-2026  Free Software Foundation, Inc.

;; Author: Protesilaos <info@protesilaos.com>
;; Maintainer: Protesilaos <info@protesilaos.com>
;; URL: https://github.com/protesilaos/ef-themes
;; Version: 2.2.1
;; Package-Requires: ((emacs "28.1") (modus-themes "5.0.0"))
;; Keywords: faces, theme, accessibility

;; This file is NOT part of GNU Emacs.

;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:
;;
;; The `ef-themes' are a collection of light and dark themes for GNU
;; Emacs whose goal is to provide colorful ("pretty") yet legible
;; options for users who want something with a bit more flair than the
;; `modus-themes' (also designed by me).
;;
;; "Ef" is a Greek word (ευ), commonly used as a prefix to denote
;; something good, nice, and/or easy.  For example, eftopia (ευτοπία)
;; is the opposite of dystopia (δυστοπία): a good place as opposed to
;; a bad place.
;;
;; The backronym of the `ef-themes' is: Eclectic Fashion in Themes
;; Hides Exaggerated Markings, Embellishments, and Sparkles.

;;; Code:



(require 'modus-themes)
(eval-when-compile (require 'subr-x))

;;;; Basics for building on top of Modus

(defgroup ef-themes ()
  "Colorful and legible themes.
The `ef-themes' are built on top of the `modus-themes'.  To make all the
Modus commands that operate on a theme only consider Ef themes, enable the
`ef-themes-take-over-modus-themes-mode'.  Or, if you prefer to blend Ef
and Modus into a single group, enable `modus-themes-include-derivatives-mode'."
  :group 'faces
  :group 'modus-themes
  :link '(info-link "(ef-themes) Top")
  :link '(info-link "(modus-themes) Top")
  :prefix "ef-themes-"
  :tag "Ef Themes")

(defconst ef-themes-light-themes
  '(ef-arbutus
    ef-arcadia
    ef-cyprus
    ef-day
    ef-deuteranopia-light
    ef-duo-light
    ef-eagle
    ef-elea-light
    ef-frost
    ef-kassio
    ef-light
    ef-maris-light
    ef-melissa-light
    ef-orange
    ef-reverie
    ef-spring
    ef-summer
    ef-trio-light
    ef-tritanopia-light)
  "List of symbols with the light Ef themes.")

(defconst ef-themes-dark-themes
  '(ef-atlantis
    ef-autumn
    ef-bio
    ef-cherie
    ef-dark
    ef-deuteranopia-dark
    ef-dream
    ef-duo-dark
    ef-elea-dark
    ef-fig
    ef-maris-dark
    ef-melissa-dark
    ef-night
    ef-owl
    ef-rosa
    ef-symbiosis
    ef-trio-dark
    ef-tritanopia-dark
    ef-winter)
  "List of symbols with the dark Ef themes.")

(defvaralias 'ef-themes-collection 'ef-themes-items
  "Alias for `ef-themes-items'.")

(defconst ef-themes-items
  (append ef-themes-light-themes ef-themes-dark-themes)
  "Symbols of all the Ef themes.")

(defconst ef-themes-with-properties
  '((ef-arbutus ef-themes "Legible light pink theme with red and green colors." light ef-arbutus-palette nil ef-arbutus-palette-overrides)
    (ef-arcadia ef-themes "Legible light theme with verdant colors." light ef-arcadia-palette nil ef-arcadia-palette-overrides)
    (ef-atlantis ef-themes "Legible dark theme with aquatic colors." dark ef-atlantis-palette nil ef-atlantis-palette-overrides)
    (ef-autumn ef-themes "Legible dark brown-black theme with warm colors (red, yellow, green, teal)." dark ef-autumn-palette nil ef-autumn-palette-overrides)
    (ef-bio ef-themes "Legible dark green-black theme with green, teal, blue, purple colors." dark ef-bio-palette nil ef-bio-palette-overrides)
    (ef-cherie ef-themes "Legible dark purple-black theme with warm colors (mostly pink, magenta, gold)." dark ef-cherie-palette nil ef-cherie-palette-overrides)
    (ef-cyprus ef-themes "Legible light ochre theme with green, yellow, teal, red colors." light ef-cyprus-palette nil ef-cyprus-palette-overrides)
    (ef-dark ef-themes "Legible dark theme with blue, magenta, cyan, purple colors." dark ef-dark-palette nil ef-dark-palette-overrides)
    (ef-day ef-themes "Legible light sandy theme with warm, varied colors (yellow, red, green, purple)." light ef-day-palette nil ef-day-palette-overrides)
    (ef-deuteranopia-dark ef-themes "Legible dark theme, optimized for red-green color deficiency." dark ef-deuteranopia-dark-palette nil ef-deuteranopia-dark-palette-overrides)
    (ef-deuteranopia-light ef-themes "Legible light theme, optimized for red-green color deficiency." light ef-deuteranopia-light-palette nil ef-deuteranopia-light-palette-overrides)
    (ef-dream ef-themes "Legible dark purple grey theme with gold and nuanced colors." dark ef-dream-palette nil ef-dream-palette-overrides)
    (ef-duo-dark ef-themes "Legible dark theme with mostly blue and orange colors." dark ef-duo-dark-palette nil ef-duo-dark-palette-overrides)
    (ef-duo-light ef-themes "Legible light theme with mostly blue and yellow colors." light ef-duo-light-palette nil ef-duo-light-palette-overrides)
    (ef-eagle ef-themes "Legible light beige theme with brown, red, and desatured colors." light ef-eagle-palette nil ef-eagle-palette-overrides)
    (ef-elea-dark ef-themes "Legible dark green theme with brown, magenta, and green colors." dark ef-elea-dark-palette nil ef-elea-dark-palette-overrides)
    (ef-elea-light ef-themes "Legible light green theme with brown, magenta, and green colors." light ef-elea-light-palette nil ef-elea-light-palette-overrides)
    (ef-fig ef-themes "Legible dark theme with warm green, yellow, and magenta colors." dark ef-fig-palette nil ef-fig-palette-overrides)
    (ef-frost ef-themes "Legible light cool theme with blue, cyan, teal, purple colors." light ef-frost-palette nil ef-frost-palette-overrides)
    (ef-kassio ef-themes "Legible light grey theme with blue, red, magenta, and earthly colors." light ef-kassio-palette nil ef-kassio-palette-overrides)
    (ef-light ef-themes "Legible light theme with blue, magenta, cyan, purple colors." light ef-light-palette nil ef-light-palette-overrides)
    (ef-maris-dark ef-themes "Legible dark marine theme with blue, cyan, and green colors." dark ef-maris-dark-palette nil ef-maris-dark-palette-overrides)
    (ef-maris-light ef-themes "Legible light marine theme with blue, cyan, and green colors." light ef-maris-light-palette nil ef-maris-light-palette-overrides)
    (ef-melissa-dark ef-themes "Legible dark honeybee theme with warm colors (yellow, red, green, cyan)." dark ef-melissa-dark-palette nil ef-melissa-dark-palette-overrides)
    (ef-melissa-light ef-themes "Legible light honeybee theme with warm colors (yellow, red, green, cyan)." light ef-melissa-light-palette nil ef-melissa-light-palette-overrides)
    (ef-night ef-themes "Legible dark sky blue theme with mostly green, blue, purple colors." dark ef-night-palette nil ef-night-palette-overrides)
    (ef-orange ef-themes "Legible light theme with orange, yellow, and green colors." light ef-orange-palette nil ef-orange-palette-overrides)
    (ef-owl ef-themes "Legible dark grey theme with blue, cyan, magenta and desaturated colors." dark ef-owl-palette nil ef-owl-palette-overrides)
    (ef-reverie ef-themes "Legible light lavender theme with gold and nuanced colors." light ef-reverie-palette nil ef-reverie-palette-overrides)
    (ef-rosa ef-themes "Legible dark rosewood theme with magenta and green colors." dark ef-rosa-palette nil ef-rosa-palette-overrides)
    (ef-spring ef-themes "Legible light floral theme with cool, varied colors (green, cyan, red)." light ef-spring-palette nil ef-spring-palette-overrides)
    (ef-summer ef-themes "Legible light warm pink theme with magenta, purple, gold, cyan colors." light ef-summer-palette nil ef-summer-palette-overrides)
    (ef-symbiosis ef-themes "Legible dark brown-black theme with blue, green, cyan, gold colors." dark ef-symbiosis-palette nil ef-symbiosis-palette-overrides)
    (ef-trio-dark ef-themes "Legible dark purple grey theme with magenta, blue, teal colors." dark ef-trio-dark-palette nil ef-trio-dark-palette-overrides)
    (ef-trio-light ef-themes "Legible light purple grey theme with magenta, blue, and teal colors." light ef-trio-light-palette nil ef-trio-light-palette-overrides)
    (ef-tritanopia-dark ef-themes "Legible dark theme, optimized for blue-yellow color deficiency." dark ef-tritanopia-dark-palette nil ef-tritanopia-dark-palette-overrides)
    (ef-tritanopia-light ef-themes "Legible light theme, optimized for blue-yellow color deficiency." light ef-tritanopia-light-palette nil ef-tritanopia-light-palette-overrides)
    (ef-winter ef-themes "Legible dark purple-black theme with magenta, purple, fawn, teal colors." dark ef-winter-palette nil ef-winter-palette-overrides)))

(defvar ef-themes--declared-p nil)

(defun ef-themes-declare-themes ()
  "Declare the Ef themes."
  (unless ef-themes--declared-p
    (dolist (theme ef-themes-with-properties)
      (apply #'modus-themes-declare theme)
      (modus-themes-register (car theme)))
    (setq ef-themes--declared-p t)))

(ef-themes-declare-themes)

(defconst ef-themes-palette-common
  '((fringe unspecified)
    (property variable)
    (fg-region unspecified)

    (bg-diff-context bg-dim)

    (bg-tab-bar bg-alt)
    (bg-tab-current bg-main)
    (bg-tab-other bg-active)

    (fg-link-symbolic fg-alt)
    (underline-link border)
    (underline-link-visited border)
    (underline-link-symbolic border)

    (border-mode-line-active border)
    (bg-mode-line-inactive bg-alt)
    (fg-mode-line-inactive fg-dim)
    (border-mode-line-inactive border)

    (bg-line-number-active unspecified)
    (fg-line-number-active accent-0)
    (bg-line-number-inactive unspecified)

    (bg-prominent-err bg-err)
    (bg-prominent-warning bg-warning)
    (bg-prominent-note bg-info)
    (fg-prominent-err err)
    (fg-prominent-warning warning)
    (fg-prominent-note info)

    (bg-added-fringe bg-info)
    (bg-changed-fringe bg-warning)
    (bg-removed-fringe bg-err)

    (bg-space unspecified)
    (fg-space border)

    (bg-active-argument bg-warning)
    (fg-active-argument warning)
    (bg-active-value bg-info)
    (fg-active-value info)

    (bg-mark-delete bg-err)
    (fg-mark-delete err)
    (bg-mark-select bg-info)
    (fg-mark-select info)
    (bg-mark-other bg-warning)
    (fg-mark-other warning)

    (fg-search-current fg-main)
    (fg-search-lazy fg-main)
    (fg-search-static fg-main)
    (fg-search-replace fg-main)

    (fg-search-rx-group-0 fg-main)
    (fg-search-rx-group-1 fg-main)
    (fg-search-rx-group-2 fg-main)
    (fg-search-rx-group-3 fg-main)

    (fg-completion-match-0 accent-0)
    (fg-completion-match-1 accent-1)
    (fg-completion-match-2 accent-2)
    (fg-completion-match-3 accent-3)

    (fg-heading-0 rainbow-0)
    (fg-heading-1 rainbow-1)
    (fg-heading-2 rainbow-2)
    (fg-heading-3 rainbow-3)
    (fg-heading-4 rainbow-4)
    (fg-heading-5 rainbow-5)
    (fg-heading-6 rainbow-6)
    (fg-heading-7 rainbow-7)
    (fg-heading-8 rainbow-8))
  "Common palette mappings for the Ef themes.")

;;;; Compatibility with older versions of the Ef themes

(defvar ef-themes--aliased-p nil)

(defun ef-themes-define-alias (prefix suffix &optional is-function)
  "Make alias for the Modus themes symbol with PREFIX and SUFFIX.
If IS-FUNCTION is non-nil, use the appropriate calls for functions, else
assume this is a variable."
  (let ((our-symbol (intern (format "%s-%s" prefix suffix)))
        (modus-symbol (intern-soft (format "modus-themes-%s" suffix))))
    (when (symbolp modus-symbol)
      (funcall
       (if is-function
           #'defalias
         #'defvaralias)
       our-symbol
       modus-symbol
       (format "`%s' is an alias for `%s'.
Since version 2.0.0, `ef-themes' derive from the `modus-themes'.
You can configure the `ef-themes' via the user options of the
`modus-themes'.

To make all the Modus commands that operate on a theme consider only Ef
themes, enable `ef-themes-take-over-modus-themes-mode'.  Or, if you
prefer to blend Ef and Modus into a single group, enable
`modus-themes-include-derivatives-mode' instead.

Alternatively, use the commands `ef-themes-rotate', `ef-themes-select',
`ef-themes-load-random', `ef-themes-load-random-dark',
`ef-themes-load-random-light', `ef-themes-list-colors',
`ef-themes-list-colors-current'.  They are all designed to only consider
Ef themes." our-symbol modus-symbol)))))

(defun ef-themes-define-compatibility-aliases ()
  "Define aliases for the user options of the Modus themes."
  (unless ef-themes--aliased-p
    (dolist (user-option-suffix '( disable-other-themes to-toggle to-rotate after-load-theme-hook
                                   post-load-hook italic-constructs bold-constructs variable-pitch-ui mixed-fonts
                                   headings completions prompts common-palette-overrides))
      (ef-themes-define-alias "ef-themes" user-option-suffix))
    (dolist (function-suffix '(load-theme with-colors get-color-value))
      (ef-themes-define-alias "ef-themes" function-suffix :is-function))
    (setq ef-themes--aliased-p t)))

(ef-themes-define-compatibility-aliases)

;;;; Limit the Modus themes to only Ef themes

;;;###autoload
(define-minor-mode ef-themes-take-over-modus-themes-mode
  "When enabled, all Modus themes commands consider only Ef themes.
Alternatively, use the commands `ef-themes-rotate', `ef-themes-select',
`ef-themes-load-random', `ef-themes-load-random-dark',
`ef-themes-load-random-light', `ef-themes-list-colors',
`ef-themes-list-colors-current'.  They are all designed to only consider
Ef themes."
  :global t
  :init-value nil)

(cl-defmethod modus-themes-get-themes (&context (ef-themes-take-over-modus-themes-mode (eql t)))
  "Return list of Ef themes, per `ef-themes-take-over-modus-themes-mode'."
  (if-let* ((themes (modus-themes-get-all-known-themes 'ef-themes))
            (sorted-a-z (sort themes #'string-lessp))
            (sorted-light-dark (modus-themes-sort sorted-a-z 'light)))
      sorted-light-dark
    ef-themes-items))

;;;; Convenience commands

;;;###autoload (autoload 'ef-themes-toggle "ef-themes")
(modus-themes-define-derivative-command ef-themes toggle)

;;;###autoload (autoload 'ef-themes-rotate "ef-themes")
(modus-themes-define-derivative-command ef-themes rotate)

;;;###autoload (autoload 'ef-themes-select "ef-themes")
(modus-themes-define-derivative-command ef-themes select)

;;;###autoload (autoload 'ef-themes-select-dark "ef-themes")
(modus-themes-define-derivative-command ef-themes select-dark)

;;;###autoload (autoload 'ef-themes-select-light "ef-themes")
(modus-themes-define-derivative-command ef-themes select-light)

;;;###autoload (autoload 'ef-themes-load-random "ef-themes")
(modus-themes-define-derivative-command ef-themes load-random)

;;;###autoload (autoload 'ef-themes-load-random-dark "ef-themes")
(modus-themes-define-derivative-command ef-themes load-random-dark)

;;;###autoload (autoload 'ef-themes-load-random-light "ef-themes")
(modus-themes-define-derivative-command ef-themes load-random-light)

;;;###autoload (autoload 'ef-themes-list-colors "ef-themes")
(modus-themes-define-derivative-command ef-themes list-colors)

;;;###autoload (autoload 'ef-themes-list-colors-current "ef-themes")
(modus-themes-define-derivative-command ef-themes list-colors-current)

;;;; Add themes from package to path

;;;###autoload
(when load-file-name
  (let ((dir (file-name-directory load-file-name)))
    (unless (file-equal-p dir (expand-file-name "themes/" data-directory))
      (add-to-list 'custom-theme-load-path dir))))

(provide 'ef-themes)
;;; ef-themes.el ends here
