Theme and Appearance
Table of Contents
Visual customization including color theme, mode line, and UI elements.
1. Basic Settings
;;; theme-conf.el --- Summary -*- lexical-binding: t; -*- ;;; Commentary: ;;; Code: (let ((theme-file (expand-file-name "elisp/themes/hasliberg-theme.el" user-emacs-directory))) (if (file-exists-p theme-file) (progn (load-file theme-file) (load-theme 'hasliberg t)))) (provide 'theme-conf) ;;; theme-conf.el ends here
2. Hasliberg Theme
;;; hasliberg-theme.el --- Serene theme inspired by Swiss alps. -*- lexical-binding:t -*- ;; Copyright (C) 2024 Free Software Foundation, Inc. ;; Author: Ryota Sawada <rytswd@gmail.com> ;; Maintainer: Ryota Sawada <rytswd@gmail.com> ;; URL: https://github.com/rytswd/hasliberg-theme ;; Keywords: theme ;; Version: 0.1 ;; This program 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. ;; This program 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 this program. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; The theme is only meant to provide my own prefered colour setup. ;;; Acknowledgments: ;; A lot of configurations were inspired by ef-themes by Prot. ;;; Code: ;;;###theme-autoload (deftheme hasliberg "Theme inspired by Swiss alps" :background-mode 'dark :kind 'color-scheme) ;;;;---------------------------------------- ;;; Configuration ;;------------------------------------------ (defgroup hasliberg-theme nil "Options for hasliberg-theme." :group 'hasliberg-theme :prefix "hasliberg-theme-") (defconst hasliberg-theme-lch-type '(plist :options ((:luminance float) (:chroma float) (:hue float))) "A plist defining LuvLCh input.") (defun hasliberg-theme--validate-and-set-lch (symbol value) "Set SYMBOL to VALUE if it is a valid LCH colour. VALUE must be a plist containing :luminance, :chroma, and :hue with float values. Luminance should be between 0 and 100, chroma should be non-negative, and hue should be between 0 and 360." ;; This assumes the use of `hasliberg-theme-lch-type'. (let ((luminance (plist-get value :luminance)) (chroma (plist-get value :chroma)) (hue (plist-get value :hue)) (errors '())) (unless (and luminance chroma hue) (push "LCH value must include :luminance, :chroma, and :hue" errors)) (unless (and (floatp luminance) (floatp chroma) (floatp hue)) (push (format "LCH components must be float values: %S" value) errors)) (when (and luminance (or (< luminance 0) (> luminance 100))) (push (format "Luminance value %f must be between 0 and 100" luminance) errors)) ;; NOTE: I need to double check on the valid chroma value. ;; I took this value from https://facelessuser.github.io/coloraide/colors/lchuv/ (when (and chroma (or (< chroma 0) (> chroma 220))) (push (format "Chroma value %f must be non-negative" chroma) errors)) (when (and hue (or (< hue 0) (>= hue 360))) (push (format "Hue value %f must be between 0 and 360" hue) errors)) (if errors (error "Invalid LuvLCh value: %s" (string-join (reverse errors) "; ")) (set-default symbol value)))) (defcustom hasliberg-theme-dark-or-light 'dark "The theme variant, either `dark` or `light`." :type '(choice (const :tag "Dark" dark) (const :tag "Light" light)) :group 'hasliberg-theme :set (lambda (symbol value) (set-default symbol value) (hasliberg-theme--update)) :initialize 'custom-initialize-default) (defcustom hasliberg-theme-colour-background '(:luminance 17.877 :chroma 1.800 :hue 236.421) ;; #2A2C2E "The background colour, in LuvLCh values." :type hasliberg-theme-lch-type :set 'hasliberg-theme--validate-and-set-lch :group 'hasliberg-theme) (defcustom hasliberg-theme-colour-background-variant '(:luminance 6.265 :chroma 11.827 :hue 252.428) ;; #00142D "Another background colour with slight variation, in LuvLCh values." :type hasliberg-theme-lch-type :set 'hasliberg-theme--validate-and-set-lch :group 'hasliberg-theme) (defcustom hasliberg-theme-colour-neutral '(:luminance 95.074 :chroma 12.330 :hue 252.652) ;; #ECF1FF "The neutral / default font colour, in LuvLCh values." :type hasliberg-theme-lch-type :set 'hasliberg-theme--validate-and-set-lch :group 'hasliberg-theme) (defcustom hasliberg-theme-colour-primary '(:luminance 80.335 :chroma 40.438 :hue 241.234) ;; #A7CBF1 "The primary font colour, in LuvLCh values." :type hasliberg-theme-lch-type :set 'hasliberg-theme--validate-and-set-lch :group 'hasliberg-theme) (defcustom hasliberg-theme-colour-secondary '(:luminance 63.743 :chroma 48.347 :hue 251.617) ;; #809BCE "The secondary font colour, in LuvLCh values." :type hasliberg-theme-lch-type :set 'hasliberg-theme--validate-and-set-lch :group 'hasliberg-theme) (defcustom hasliberg-theme-colour-accent '(:luminance 77.610 :chroma 86.648 :hue 47.245) ;; #FBB151 "The accent font colour, used sparingly for call-to-action, etc., in LuvLCh values." :type hasliberg-theme-lch-type :set 'hasliberg-theme--validate-and-set-lch :group 'hasliberg-theme) (defcustom hasliberg-theme-colour-accent-variant '(:luminance 67.236 :chroma 86.052 :hue 335.603) ;; #FB74C3 "Another accent font colour with slight variation, used sparingly for call-to-action, etc., in LuvLCh values." :type hasliberg-theme-lch-type :set 'hasliberg-theme--validate-and-set-lch :group 'hasliberg-theme) (defcustom hasliberg-theme-colour-subtle '(:luminance 77.751 :chroma 14.617 :hue 235.776) ;; #B4C2CF "The subtle font colour to slightly mix up, in LuvLCh values." :type hasliberg-theme-lch-type :set 'hasliberg-theme--validate-and-set-lch :group 'hasliberg-theme) (defcustom hasliberg-theme-colour-subtle-variant '(:luminance 73.823 :chroma 11.749 :hue 180.830) ;; #A4BAB7 "Another subtle font colour with slight variation to mix up even more, in LuvLCh values." :type hasliberg-theme-lch-type :set 'hasliberg-theme--validate-and-set-lch :group 'hasliberg-theme) (defcustom hasliberg-theme-colour-info ;; '(:luminance 62.814 :chroma 70.124 :hue 123.247) ;; #5AAA46 '(:luminance 71.365 :chroma 21.506 :hue 257.597) ;; #A8AEC7 "The info font colour, in LuvLCh values." :type hasliberg-theme-lch-type :set 'hasliberg-theme--validate-and-set-lch :group 'hasliberg-theme) (defcustom hasliberg-theme-colour-warning '(:luminance 67.236 :chroma 86.052 :hue 335.603) ;; #FB74C3 "The warning font colour, in LuvLCh values." :type hasliberg-theme-lch-type :set 'hasliberg-theme--validate-and-set-lch :group 'hasliberg-theme) (defun hasliberg-theme-use-dark-standard-colour-palette () "Use dark standard colour palette for Hasliberg Theme setup." (interactive) (setopt hasliberg-theme-dark-or-light 'dark hasliberg-theme-colour-background '(:luminance 17.877 :chroma 1.800 :hue 236.421) ;; #2A2C2E hasliberg-theme-colour-background-variant '(:luminance 6.265 :chroma 11.827 :hue 252.428) ;; #00142D hasliberg-theme-colour-neutral '(:luminance 95.074 :chroma 12.330 :hue 252.652) ;; #ECF1FF hasliberg-theme-colour-primary '(:luminance 80.335 :chroma 40.438 :hue 241.234) ;; #A7CBF1 hasliberg-theme-colour-secondary '(:luminance 63.743 :chroma 48.347 :hue 251.617) ;; #809BCE hasliberg-theme-colour-accent '(:luminance 77.610 :chroma 86.648 :hue 47.245) ;; #FBB151 hasliberg-theme-colour-accent-variant '(:luminance 67.236 :chroma 86.052 :hue 335.603) ;; #FB74C3 hasliberg-theme-colour-subtle '(:luminance 77.751 :chroma 14.617 :hue 235.776) ;; #B4C2CF hasliberg-theme-colour-subtle-variant '(:luminance 73.823 :chroma 11.749 :hue 180.830) ;; #A4BAB7 hasliberg-theme-colour-info '(:luminance 62.814 :chroma 70.124 :hue 123.247) ;; #5AAA46 hasliberg-theme-colour-warning '(:luminance 67.236 :chroma 86.052 :hue 335.603) ;; #FB74C3 ) (hasliberg-theme--update) ) (defun hasliberg-theme-use-dark-orange-colour-palette () "Use dark standard colour palette for Hasliberg Theme setup." (interactive) (setopt hasliberg-theme-dark-or-light 'dark hasliberg-theme-colour-background '(:luminance 17.877 :chroma 1.800 :hue 236.421) ;; #2A2C2E hasliberg-theme-colour-background-variant '(:luminance 6.265 :chroma 11.827 :hue 252.428) ;; #00142D hasliberg-theme-colour-neutral '(:luminance 95.074 :chroma 12.330 :hue 252.652) ;; #ECF1FF hasliberg-theme-colour-primary '(:luminance 80.335 :chroma 40.438 :hue 41.234) ;; #EBBEA1 hasliberg-theme-colour-secondary '(:luminance 63.743 :chroma 48.347 :hue 51.617) ;; #BB9362 hasliberg-theme-colour-accent '(:luminance 77.610 :chroma 86.648 :hue 47.245) ;; #FBB151 hasliberg-theme-colour-accent-variant '(:luminance 67.236 :chroma 86.052 :hue 35.603) ;; #E78E4B hasliberg-theme-colour-subtle '(:luminance 77.751 :chroma 14.617 :hue 35.776) ;; #CFBCB4 hasliberg-theme-colour-subtle-variant '(:luminance 73.823 :chroma 11.749 :hue 80.830) ;; #B8B6A7 hasliberg-theme-colour-info '(:luminance 62.814 :chroma 70.124 :hue 23.247) ;; #D9816A hasliberg-theme-colour-warning '(:luminance 67.236 :chroma 86.052 :hue 35.603) ;; #E78E4B ) (hasliberg-theme--update) ) (defun hasliberg-theme-use-dark-monotonic-colour-palette () "Use dark standard colour palette for Hasliberg Theme setup." (interactive) (setopt hasliberg-theme-dark-or-light 'dark hasliberg-theme-colour-background '(:luminance 17.877 :chroma 1.800 :hue 236.421) ;; #2A2C2E hasliberg-theme-colour-background-variant '(:luminance 6.265 :chroma 11.827 :hue 252.428) ;; #00142D hasliberg-theme-colour-neutral '(:luminance 95.074 :chroma 12.330 :hue 252.652) ;; #ECF1FF hasliberg-theme-colour-primary '(:luminance 93.380 :chroma 2.848 :hue 192.490) ;; #E8EDED hasliberg-theme-colour-secondary '(:luminance 93.535 :chroma 3.844 :hue 169.497) ;; #E7EEEC hasliberg-theme-colour-accent '(:luminance 76.373 :chroma 32.215 :hue 238.249) ;; #A1C0DD hasliberg-theme-colour-accent-variant '(:luminance 76.373 :chroma 32.215 :hue 238.249) ;; #A1C0DD hasliberg-theme-colour-subtle '(:luminance 77.751 :chroma 14.617 :hue 35.776) ;; #CFBCB4 hasliberg-theme-colour-subtle-variant '(:luminance 73.823 :chroma 11.749 :hue 80.830) ;; #B8B6A7 hasliberg-theme-colour-info '(:luminance 67.245 :chroma 10.890 :hue 19.862) ;; #B1A09E hasliberg-theme-colour-warning '(:luminance 67.236 :chroma 86.052 :hue 35.603) ;; #E78E4B ) (hasliberg-theme--update) ) (defun hasliberg-theme-use-dark-nature-colour-palette () "Use dark standard colour palette for Hasliberg Theme setup." (interactive) (setopt hasliberg-theme-dark-or-light 'dark hasliberg-theme-colour-background '(:luminance 17.736 :chroma 13.978 :hue 146.889) ;; #113122 hasliberg-theme-colour-background-variant '(:luminance 25.925 :chroma 49.830 :hue 153.821) ;; #004E24 hasliberg-theme-colour-neutral '(:luminance 95.146 :chroma 12.143 :hue 253.229) ;; #ECF1FF hasliberg-theme-colour-primary '(:luminance 91.651 :chroma 82.416 :hue 128.406) ;; #93FF96 hasliberg-theme-colour-secondary '(:luminance 84.833 :chroma 52.641 :hue 148.810) ;; #86E7B8 hasliberg-theme-colour-accent '(:luminance 75.501 :chroma 82.984 :hue 110.610) ;; #91CB3E hasliberg-theme-colour-accent-variant '(:luminance 56.134 :chroma 27.575 :hue 241.601) ;; #7189A3 hasliberg-theme-colour-subtle '(:luminance 95.771 :chroma 17.004 :hue 91.625) ;; #F2F5DE hasliberg-theme-colour-subtle-variant '(:luminance 73.828 :chroma 11.880 :hue 80.278) ;; #B8B6A7 hasliberg-theme-colour-info '(:luminance 36.998 :chroma 22.023 :hue 101.245) ;; #515B3A hasliberg-theme-colour-warning '(:luminance 80.484 :chroma 80.188 :hue 67.991) ;; #E8C547 ) (hasliberg-theme--update) ) (defun hasliberg-theme-use-light-standard-colour-palette () "Use light standard colour palette for Hasliberg Theme setup." (interactive) (setopt hasliberg-theme-dark-or-light 'light hasliberg-theme-colour-background '(:luminance 95.074 :chroma 12.330 :hue 252.652) ;; #ECF1FF hasliberg-theme-colour-background-variant '(:luminance 90.425 :chroma 3.975 :hue 192.174) ;; #DEE5E5 hasliberg-theme-colour-neutral '(:luminance 17.877 :chroma 1.800 :hue 236.421) ;; #2A2C2E hasliberg-theme-colour-primary '(:luminance 30.335 :chroma 40.438 :hue 241.234) ;; #024C72 hasliberg-theme-colour-secondary '(:luminance 13.743 :chroma 48.347 :hue 251.617) ;; #00256D hasliberg-theme-colour-accent '(:luminance 52.814 :chroma 70.124 :hue 23.247) ;; #BC674E hasliberg-theme-colour-accent-variant '(:luminance 17.236 :chroma 86.052 :hue 335.603) ;; #830052 hasliberg-theme-colour-subtle '(:luminance 27.751 :chroma 14.617 :hue 235.776) ;; #324451 hasliberg-theme-colour-subtle-variant '(:luminance 23.823 :chroma 11.749 :hue 180.830) ;; #213E3A hasliberg-theme-colour-info '(:luminance 12.814 :chroma 70.124 :hue 123.247) ;; #003000 hasliberg-theme-colour-warning '(:luminance 17.236 :chroma 86.052 :hue 335.603) ;; #830052 ) (hasliberg-theme--update) ) (defun hasliberg-theme--update () "Based on the base colour input, update the shades, faces, and then reload." (hasliberg-theme--update-shades) (hasliberg-theme--update-all-faces) (load-theme 'hasliberg t)) (defvar hasliberg-theme--load-path nil "Variable to store the load path of Hasliberg Theme.") (unless hasliberg-theme--load-path (setq hasliberg-theme--load-path load-file-name)) (defun hasliberg-theme-reload () "Re-evaluate the file and reload the config." (interactive) (load-file hasliberg-theme--load-path) (load-theme 'hasliberg t)) (defun hasliberg-theme--lch-to-luv (lch) "Convert a colour from LCH to Luv. LCH is a plist with properties :luminance, :chroma, and :hue." (let* ((L (plist-get lch :luminance)) (C (plist-get lch :chroma)) (H-degree (plist-get lch :hue)) ;; Convert degrees to radians (H-radians (* pi (/ H-degree 180.0)))) (list :l L :u (* C (cos H-radians)) :v (* C (sin H-radians))))) (defun hasliberg-theme--luv-to-xyz (luv) "Convert a colour from Luv to XYZ. Luv is a plist with properties :l, :u and :v." (let* ((L (plist-get luv :l)) (u (plist-get luv :u)) (v (plist-get luv :v)) ;; Constants for D65 illuminant (ref-u 0.19783000664283) (ref-v 0.46831999493879) (up (/ (+ u (* 13 L ref-u)) (* 13 L))) (vp (/ (+ v (* 13 L ref-v)) (* 13 L))) ;; Results (Y (if (> L 7.9996) (expt (/ (+ L 16) 116.0) 3) (/ L 903.3))) ;; Ensure `vp` is not zero to avoid division by zero (X (if (zerop vp) 0 (/ (* 9 Y up) (* 4 vp)))) (Z (if (zerop vp) 0 (/ (* (- 12 (* 3 up) (* 20 vp)) Y) (* 4 vp))))) (list :x (* 100 X) :y (* 100 Y) :z (* 100 Z)))) (defun hasliberg-theme--xyz-to-rgb (xyz) "Convert a colour from XYZ to RGB. XYZ is a plist with properties :x, :y, and :z." (let* ((X (/ (plist-get xyz :x) 100.0)) (Y (/ (plist-get xyz :y) 100.0)) (Z (/ (plist-get xyz :z) 100.0)) ;; Linear transformation matrix for sRGB D65 (R-linear (+ (* X 3.2406) (* Y -1.5372) (* Z -0.4986))) (G-linear (+ (* X -0.9689) (* Y 1.8758) (* Z 0.0415))) (B-linear (+ (* X 0.0557) (* Y -0.2040) (* Z 1.0570))) ;; Apply gamma correction (R (if (<= R-linear 0.0031308) (* 12.92 R-linear) (- (* 1.055 (expt R-linear (/ 1.0 2.4))) 0.055))) (G (if (<= G-linear 0.0031308) (* 12.92 G-linear) (- (* 1.055 (expt G-linear (/ 1.0 2.4))) 0.055))) (B (if (<= B-linear 0.0031308) (* 12.92 B-linear) (- (* 1.055 (expt B-linear (/ 1.0 2.4))) 0.055)))) ;; Clamp the results to the range [0, 1] (list :r (min (max R 0.0) 1.0) :g (min (max G 0.0) 1.0) :b (min (max B 0.0) 1.0)))) (defun hasliberg-theme--rgb-to-hex (rgb) "Convert a colour from RGB to Hex. RGB is a plist with properties :r, :g, and :b, where each value is in the range [0, 1]." (let* ((r (round (* (plist-get rgb :r) 255))) (g (round (* (plist-get rgb :g) 255))) (b (round (* (plist-get rgb :b) 255)))) (format "#%02X%02X%02X" r g b))) (defun hasliberg-theme--lch-to-rgb (lch) "Convert a colour from LCH to RGB in Hex. LCH is a plist with properties :luminance, :chroma, and :hue." (let* ((luv (hasliberg-theme--lch-to-luv lch)) (xyz (hasliberg-theme--luv-to-xyz luv)) (rgb (hasliberg-theme--xyz-to-rgb xyz))) (hasliberg-theme--rgb-to-hex rgb))) (defun hasliberg-theme--hex-to-rgb (hex) "Convert a colour from Hex to RGB. HEX can be a string in the form \"#RRGGBB\", \"RRGGBB\", \"#RGB\", or \"RGB\"." ;; Normalize the hex string by removing a leading # (let* ((normalized-hex (if (eq (aref hex 0) ?#) (substring hex 1) hex)) ;; Expand 3-digit color code to 6-digit format if necessary (expanded-hex (if (= (length normalized-hex) 3) (apply 'concat (mapcar (lambda (c) (make-string 2 c)) normalized-hex)) normalized-hex))) ;; Ensure the string has the correct length of 6 characters (when (not (= (length expanded-hex) 6)) (error "Invalid hex colour format, expected 3 or 6 characters")) (list :r (/ (string-to-number (substring expanded-hex 0 2) 16) 255.0) :g (/ (string-to-number (substring expanded-hex 2 4) 16) 255.0) :b (/ (string-to-number (substring expanded-hex 4 6) 16) 255.0)))) (defun hasliberg-theme--rgb-to-xyz (rgb) "Convert a colour from RGB to XYZ. RGB is a plist with properties :r, :g, and :b, where each value is in the range [0, 1]." (let* ((linearize (lambda (c) (if (<= c 0.04045) (/ c 12.92) (expt (/ (+ c 0.055) 1.055) 2.4)))) (R-linear (funcall linearize (plist-get rgb :r))) (G-linear (funcall linearize (plist-get rgb :g))) (B-linear (funcall linearize (plist-get rgb :b)))) (list :x (* 100 (+ (* R-linear 0.4124) (* G-linear 0.3576) (* B-linear 0.1805))) :y (* 100 (+ (* R-linear 0.2126) (* G-linear 0.7152) (* B-linear 0.0722))) :z (* 100 (+ (* R-linear 0.0193) (* G-linear 0.1192) (* B-linear 0.9505)))))) (defun hasliberg-theme--xyz-to-luv (xyz) "Convert a colour from XYZ to Luv. XYZ is a plist with properties :x, :y, and :z." (let* ((X (/ (plist-get xyz :x) 100.0)) (Y (/ (plist-get xyz :y) 100.0)) (Z (/ (plist-get xyz :z) 100.0)) ;; Constants for D65 illuminant (ref-X 0.95047) (ref-Y 1.00000) (ref-Z 1.08883) (ref-u (/ (* 4 ref-X) (+ ref-X (* 15 ref-Y) (* 3 ref-Z)))) (ref-v (/ (* 9 ref-Y) (+ ref-X (* 15 ref-Y) (* 3 ref-Z)))) (u (/ (* 4 X) (+ X (* 15 Y) (* 3 Z)))) (v (/ (* 9 Y) (+ X (* 15 Y) (* 3 Z)))) (L (if (> Y 0.008856) (- (* 116 (expt Y (/ 1.0 3))) 16) (* 903.3 Y))) (u-prime (* 13 L (- u ref-u))) (v-prime (* 13 L (- v ref-v)))) (list :l L :u u-prime :v v-prime))) (defun hasliberg-theme--xyz-to-luv (xyz) "Convert a colour from XYZ to Luv. XYZ is a plist with properties :x, :y, and :z." (let* ((X (/ (plist-get xyz :x) 100.0)) (Y (/ (plist-get xyz :y) 100.0)) (Z (/ (plist-get xyz :z) 100.0)) ;; Constants for D65 illuminant (ref-X 0.95047) (ref-Y 1.00000) (ref-Z 1.08883) (ref-u (/ (* 4 ref-X) (+ ref-X (* 15 ref-Y) (* 3 ref-Z)))) (ref-v (/ (* 9 ref-Y) (+ ref-X (* 15 ref-Y) (* 3 ref-Z)))) (u (/ (* 4 X) (+ X (* 15 Y) (* 3 Z)))) (v (/ (* 9 Y) (+ X (* 15 Y) (* 3 Z)))) (L (if (> Y 0.008856) (- (* 116 (expt Y (/ 1.0 3))) 16) (* 903.3 Y))) (u-prime (* 13 L (- u ref-u))) (v-prime (* 13 L (- v ref-v)))) (list :l L :u u-prime :v v-prime))) (defun hasliberg-theme--luv-to-lch (luv) "Convert a colour from Luv to LCH. LUV is a plist with properties :l, :u, and :v." (let* ((L (plist-get luv :l)) (u (plist-get luv :u)) (v (plist-get luv :v)) (C (sqrt (+ (* u u) (* v v)))) (H (atan v u)) ;; Convert radians to degrees and ensure the hue is positive (H-degree (mod (/ (* H 180.0) pi) 360.0)) (format-3dp (lambda (num) (string-to-number (format "%.3f" num))))) (list :luminance (funcall format-3dp L) :chroma (funcall format-3dp C) :hue (funcall format-3dp H-degree)))) (defun hasliberg-theme--rgb-to-lch (hex) "Convert a colour from RGB Hex to LCH in Luv space. HEX is a string in the form \"#RRGGBB\"." (let* ((rgb (hasliberg-theme--hex-to-rgb hex)) (xyz (hasliberg-theme--rgb-to-xyz rgb)) (luv (hasliberg-theme--xyz-to-luv xyz))) (hasliberg-theme--luv-to-lch luv))) (defun hasliberg-theme-convert-rgb-selection-to-lch (start end) "Convert RGB Hex selection to LCH in Luv space, and insert after the selection. This can be helpful for setting up the new base colours, but not strictly necessary." (interactive "r") (let* ((hex (buffer-substring-no-properties start end)) (lch (hasliberg-theme--rgb-to-lch hex))) (goto-char end) (insert " " (format "'(:luminance %.3f :chroma %.3f :hue %.3f)" (plist-get lch :luminance) (plist-get lch :chroma) (plist-get lch :hue))))) (defun hasliberg-theme-convert-lch-selection-to-rgb (start end) "Convert LCH value selection to RGB Hex, and insert after the selection. This can be helpful for setting up the new base colours, but not strictly necessary." (interactive "r") (let* ((lch-selection (buffer-substring-no-properties start end)) (lch-string (if (string-prefix-p "'" lch-selection) (substring lch-selection 1) lch-selection)) (lch (read lch-string)) (hex (hasliberg-theme--lch-to-rgb lch))) (goto-char end) (insert " " hex))) (defvar hasliberg-theme-shades nil "All shades for the Hasliberg theme colours based on LuvLCh input. The values here are not meant to be updated manually.") (defvar hasliberg-theme-shades-hash (make-hash-table :test 'equal) "Hash table of all Hasliberg theme shades for fast lookup. The values here are not meant to be updated manually.") (defun hasliberg-theme--generate-lch-shades (base-lch) "Generate a list of shades for a given LCH base colour. This takes in the dark / light theme variable into account, and changes the way it generates the shades. The higher values (e.g. 600, 700, so on) are meant to provide more contrast and appear brighter based on the background. In case of dark background, the higher values would result in brighter, more white colours. In case of light background, they would result in darker, more black colours." (let* ((l (plist-get base-lch :luminance)) (c (plist-get base-lch :chroma)) (h (plist-get base-lch :hue)) (shades '(50 100 200 300 400 500 600 700 800 900 950)) (dark-or-light hasliberg-theme-dark-or-light) (luminance-steps (mapcar (lambda (step) ;; Based on dark or light selection, flip the shades. (if (eq dark-or-light 'dark) (+ l (* (- step 500) 0.1)) (- l (* (- step 500) 0.1)))) shades))) (mapcar (lambda (lum) (hasliberg-theme--lch-to-rgb (list :luminance lum :chroma c :hue h))) luminance-steps))) ;; This is a bit manual but working. (defun hasliberg-theme--generate-all-shades () "Generate all shades for the colours defined with customization with the prefix of `hasliberg-theme-colour-'." (let* ((prefix "hasliberg-theme-colour-") (pflen (length prefix)) (customs '(hasliberg-theme-colour-background hasliberg-theme-colour-background-variant hasliberg-theme-colour-neutral hasliberg-theme-colour-primary hasliberg-theme-colour-secondary hasliberg-theme-colour-accent hasliberg-theme-colour-accent-variant hasliberg-theme-colour-subtle hasliberg-theme-colour-subtle-variant hasliberg-theme-colour-info hasliberg-theme-colour-warning)) (colours (cl-loop for c in customs collect (cons (intern (substring (symbol-name c) pflen)) (symbol-value c))))) (mapcar (lambda (colour) (let* ((name (car colour)) (base-lch (cdr colour)) (shades (hasliberg-theme--generate-lch-shades base-lch))) `(,name . ,(cl-pairlis '(50 100 200 300 400 500 600 700 800 900 950) shades)))) colours))) (defun hasliberg-theme--update-shades () "Update the shades and hash table based on the colour variables." (setq hasliberg-theme-shades (hasliberg-theme--generate-all-shades)) (clrhash hasliberg-theme-shades-hash) (cl-loop for (name . shades) in hasliberg-theme-shades do (cl-loop for (shade-name . shade-value) in shades do (puthash (format "%s-%s" name shade-name) shade-value hasliberg-theme-shades-hash)))) (defun hasliberg-theme-hex-for (key) "Retrieve the Hex colour using a hashed KEY." (gethash (symbol-name key) hasliberg-theme-shades-hash)) ;;;;---------------------------------------- ;;; Faces (Fonts) ;;------------------------------------------ (defface oblique-only '((t :inherit default :slant oblique)) "A custom face only to give oblique look") (defun hasliberg-theme--update-standard-faces () (custom-theme-set-faces 'hasliberg ;;;;---------------------------------------- ;;; Basic Faces ;;------------------------------------------ `(default ((t :background ,(hasliberg-theme-hex-for 'background-500) :foreground ,(hasliberg-theme-hex-for 'neutral-500)))) `(bold ((t :weight bold))) `(italic ((t :inherit (default oblique-only)))) `(bold-italic ((t :inherit (bold oblique-only)))) `(link ((t :inherit (default oblique-only) :foreground ,(hasliberg-theme-hex-for 'neutral-400)))) `(highlight ((t :background ,(hasliberg-theme-hex-for 'background-variant-700)))) `(cursor ((t :background ,(hasliberg-theme-hex-for 'accent-700)))) `(region ((t :background ,(hasliberg-theme-hex-for 'background-700) :foreground ,(hasliberg-theme-hex-for 'subtle-600)))) `(secondary-selection ((t :background ,(hasliberg-theme-hex-for 'background-variant-600) :foreground ,(hasliberg-theme-hex-for 'subtle-600)))) `(whitespace-space ((t :foreground ,(hasliberg-theme-hex-for 'background-600)))) `(whitespace-tab ((t :foreground ,(hasliberg-theme-hex-for 'background-600)))) `(isearch ((t :background ,(hasliberg-theme-hex-for 'primary-300)))) `(success ((t :foreground ,(hasliberg-theme-hex-for 'info-700) ))) `(warning ((t :foreground ,(hasliberg-theme-hex-for 'warning-500) ))) `(minibuffer-prompt ((t :foreground ,(hasliberg-theme-hex-for 'primary-500) :weight semibold))) ;;;;---------------------------------------- ;;; Visual Elements ;;------------------------------------------ ;; Configurations around some standard visual elements such as mode lines. ;; These tend to have more complicated configuration, and thus I'm making them ;; multiline and make it easier to edit later. `(fringe ((t :background ,(hasliberg-theme-hex-for 'background-500) :foreground ,(hasliberg-theme-hex-for 'accent-100)))) `(menu ((t :background ,(hasliberg-theme-hex-for 'background-300) :foreground ,(hasliberg-theme-hex-for 'neutral-500)))) `(scroll-bar ((t :background ,(hasliberg-theme-hex-for 'background-300) :foreground ,(hasliberg-theme-hex-for 'neutral-500)))) `(tool-bar ((t :background ,(hasliberg-theme-hex-for 'background-300) :foreground ,(hasliberg-theme-hex-for 'neutral-500)))) `(vertical-border ((t :foreground ,(hasliberg-theme-hex-for 'background-300)))) `(header-line ((t :inherit oblique-only :background ,(hasliberg-theme-hex-for 'background-500) :foreground ,(hasliberg-theme-hex-for 'neutral-300)) )) `(tab-bar ((t :foreground ,(hasliberg-theme-hex-for 'neutral-500)))) `(tab-bar-tab ((t :foreground ,(hasliberg-theme-hex-for 'neutral-500)))) `(tab-bar-inactive ((t :foreground ,(hasliberg-theme-hex-for 'neutral-700)))) `(tab-bar-tab-group-current ((t :foreground ,(hasliberg-theme-hex-for 'neutral-500) :underline t))) `(tab-bar-tab-group-inactive ((t :foreground ,(hasliberg-theme-hex-for 'secondary-400)))) ;; Border used for posframe. `(child-frame-border ((t :background ,(hasliberg-theme-hex-for 'primary-300)))) ;; Mode Line `(mode-line ((t :background ,(hasliberg-theme-hex-for 'background-500) :foreground ,(hasliberg-theme-hex-for 'neutral-300) ;; :overline ,(hasliberg-theme-hex-for 'accent-500) ;; :underline (:color ,(hasliberg-theme-hex-for 'accent-500) :position 0) ;; :box nil ))) `(mode-line-active ((t :background ,(hasliberg-theme-hex-for 'background-500) :foreground ,(hasliberg-theme-hex-for 'neutral-300) :overline ,(hasliberg-theme-hex-for 'neutral-500) ;; :underline (:color ,(hasliberg-theme-hex-for 'accent-500) :position 0) ;; :box nil ))) `(mode-line-inactive ((t :background ,(hasliberg-theme-hex-for 'background-500) :foreground ,(hasliberg-theme-hex-for 'neutral-200) :underline nil :overline nil ;; :box nil ))) ;;;;---------------------------------------- ;;; Line Numbers ;;------------------------------------------ ;; Includes display-line-numbers-mode and global variant. ;; This needs to inherit `default' in order to scale when using `text-scale-adjust'. `(line-number ((t :inherit default :height 0.85 :foreground ,(hasliberg-theme-hex-for 'neutral-50)))) `(line-number-current-line ((t :inherit line-number :foreground ,(hasliberg-theme-hex-for 'accent-500) :weight semibold))) `(line-number-major-tick ((t :inherit line-number :foreground ,(hasliberg-theme-hex-for 'neutral-600)))) `(line-number-minor-tick ((t :inherit line-number))) ;;;;---------------------------------------- ;;; Font lock ;;------------------------------------------ ;; Accent colour only used sparingly. `(font-lock-string-face ((t :foreground ,(hasliberg-theme-hex-for 'accent-500) :weight regular))) ;; Any user defined fields are based on one variant. `(font-lock-function-name-face ((t :foreground ,(hasliberg-theme-hex-for 'primary-400) :weight regular))) `(font-lock-variable-name-face ((t :foreground ,(hasliberg-theme-hex-for 'primary-400)))) `(font-lock-constant-face ((t :foreground ,(hasliberg-theme-hex-for 'primary-700) :weight semibold))) `(font-lock-type-face ((t :foreground ,(hasliberg-theme-hex-for 'primary-600) :weight regular))) ;; Any basic fields are based on another variant. `(font-lock-keyword-face ((t :foreground ,(hasliberg-theme-hex-for 'neutral-700) :weight semibold))) `(font-lock-builtin-face ((t :foreground ,(hasliberg-theme-hex-for 'neutral-400)))) `(font-lock-property-name-face ((t :foreground ,(hasliberg-theme-hex-for 'secondary-700)))) `(font-lock-negation-char-face ((t :inherit bold :foreground ,(hasliberg-theme-hex-for 'secondary-500)))) `(font-lock-preprocessor-face ((t :foreground ,(hasliberg-theme-hex-for 'neutral-300)))) `(font-lock-comment-face ((t :inherit (fixed-pitch oblique-only) :foreground ,(hasliberg-theme-hex-for 'neutral-200) :weight light))) `(font-lock-comment-delimiter-face ((t :inherit (fixed-pitch oblique-only) :foreground ,(hasliberg-theme-hex-for 'primary-200) :weight light))) `(font-lock-doc-face ((t :inherit (fixed-pitch oblique-only) :foreground ,(hasliberg-theme-hex-for 'info-700)))) `(font-lock-property-use-face ((t :foreground ,(hasliberg-theme-hex-for 'primary-600)))) `(font-lock-regexp-grouping-backslash ((t :inherit bold :foreground ,(hasliberg-theme-hex-for 'subtle-variant-500)))) `(font-lock-regexp-grouping-construct ((t :inherit bold :foreground ,(hasliberg-theme-hex-for 'subtle-variant-500)))) `(font-lock-warning-face ((t :foreground ,(hasliberg-theme-hex-for 'warning-500)))) ;;;;---------------------------------------- ;;; Org Mode ;;------------------------------------------ `(org-document-title ((t :foreground ,(hasliberg-theme-hex-for 'neutral-700) :height 3.0))) `(org-level-1 ((t :inherit default :foreground ,(hasliberg-theme-hex-for 'accent-500) :height 1.5))) `(org-level-2 ((t :inherit default :foreground ,(hasliberg-theme-hex-for 'accent-600) :height 1.25))) `(org-level-3 ((t :inherit default :foreground ,(hasliberg-theme-hex-for 'accent-700) :height 1.125))) `(org-level-4 ((t :inherit default :foreground ,(hasliberg-theme-hex-for 'accent-variant-800) :height 1.0625))) `(org-level-5 ((t :inherit default :foreground ,(hasliberg-theme-hex-for 'accent-variant-900) :height 1.0625))) `(org-level-6 ((t :inherit default :foreground ,(hasliberg-theme-hex-for 'accent-variant-900)))) `(org-special-keyword ((t :inherit fixed-pitch :weight thin :foreground ,(hasliberg-theme-hex-for 'neutral-200)))) `(org-code ((t :inherit fixed-pitch :foreground ,(hasliberg-theme-hex-for 'primary-500)))) `(org-verbatim ((t :inherit fixed-pitch :background ,(hasliberg-theme-hex-for 'background-400) :foreground ,(hasliberg-theme-hex-for 'primary-400)))) `(org-ellipsis ((t :inherit fixed-pitch :foreground ,(hasliberg-theme-hex-for 'accent-700) :underline nil :height 0.7))) ;; This sets the line height without affecting font size. `(org-hide ((t :height 1.2))) ;; For code block, keep the background untouched as it would render strangely ;; for line wrap. Instead, use overline and underline to make a clear block. `(org-block ((t :inherit (default) :foreground ,(hasliberg-theme-hex-for 'subtle-variant-500)))) `(org-block-begin-line ((t :inherit (fixed-pitch oblique-only) :background ,(hasliberg-theme-hex-for 'background-500) :foreground ,(hasliberg-theme-hex-for 'primary-400) :overline ,(hasliberg-theme-hex-for 'background-700) :height 0.80))) `(org-block-end-line ((t :inherit (fixed-pitch oblique-only) :background ,(hasliberg-theme-hex-for 'background-500) :foreground ,(hasliberg-theme-hex-for 'primary-400) :underline (:color ,(hasliberg-theme-hex-for 'background-700) :position 0) :height 0.75))) `(org-quote ((t :foreground ,(hasliberg-theme-hex-for 'accent-900) :slant oblique :height 1.1))) `(org-verse ((t :foreground ,(hasliberg-theme-hex-for 'neutral-600)))) `(org-table ((t :inherit fixed-pitch :background ,(hasliberg-theme-hex-for 'background-600) :foreground ,(hasliberg-theme-hex-for 'neutral-600)))) `(org-drawer ((t :inherit (fixed-pitch font-lock-comment-face)))) `(org-property-value ((t :inherit (fixed-pitch font-lock-comment-face)))) `(org-tag ((t :inherit fixed-pitch :height 0.7))) `(org-document-info-keyword ((t :inherit fixed-pitch :foreground ,(hasliberg-theme-hex-for 'neutral-300) :height 0.8))) `(org-meta-line ((t :inherit org-document-info-keyword))) `(org-checkbox ((t :inherit fixed-pitch :box nil))) ;; Todo related handling `(org-headline-done ((t :foreground ,(hasliberg-theme-hex-for 'neutral-200)))) ;; Agenda `(org-agenda-structure ((t :foreground ,(hasliberg-theme-hex-for 'primary-500)))) `(org-agenda-done ((t :foreground ,(hasliberg-theme-hex-for 'neutral-100)))) `(org-upcoming-deadline ((t :foreground ,(hasliberg-theme-hex-for 'warning-800)))) `(org-scheduled-today ((t :foreground ,(hasliberg-theme-hex-for 'neutral-500)))) `(org-scheduled-previously ((t :foreground ,(hasliberg-theme-hex-for 'warning-400)))) `(org-agenda-structure ((t :foreground ,(hasliberg-theme-hex-for 'primary-200)))) `(org-agenda-current-time ((t :foreground ,(hasliberg-theme-hex-for 'accent-variant-700)))) `(org-time-grid ((t :foreground ,(hasliberg-theme-hex-for 'neutral-100)))) `(org-habit-clear-face ((t :background ,(hasliberg-theme-hex-for 'background-variant-500)))) `(org-habit-clear-future-face ((t :background ,(hasliberg-theme-hex-for 'background-variant-600)))) `(org-habit-alert-face ((t :background ,(hasliberg-theme-hex-for 'accent-variant-400)))) `(org-habit-alert-future-face ((t :background ,(hasliberg-theme-hex-for 'primary-200)))) `(org-habit-overdue-face ((t :background ,(hasliberg-theme-hex-for 'background-600)))) `(org-habit-overdue-future-face ((t :background ,(hasliberg-theme-hex-for 'background-600)))) ) ) (defun hasliberg-theme--update-echo-buffer () (dolist (buffer (list " *Minibuf-0*" " *Echo Area 0*" " *Minibuf-1*" " *Echo Area 1*")) (when (get-buffer buffer) (with-current-buffer buffer ;; TODO: I should use colour definitions instead. (face-remap-add-relative 'default 'font-lock-preprocessor-face)))) ) ;; Third party faces (defun hasliberg-theme--update-mode-line-faces () (custom-theme-set-faces 'hasliberg ;;;;---------------------------------------- ;;; Mode Line ;;------------------------------------------ ;; Doom Mode Line related `(doom-modeline-buffer-modified ((t :inherit (doom-modeline) :foreground ,(hasliberg-theme-hex-for 'accent-600) :weight semibold))) `(doom-modeline-project-parent-dir ((t :inherit (doom-modeline) ;; I'm not using 'oblique-only face inheritance, as I don't need ;; default face based resize handling. :slant oblique ))) ) ) (defun hasliberg-theme--update-dashboard-faces () (custom-theme-set-faces 'hasliberg ;;;;---------------------------------------- ;;; Dashboard ;;------------------------------------------ `(dashboard-banner-logo-title ((t :foreground ,(hasliberg-theme-hex-for 'primary-500) :weight semibold))) `(dashboard-heading ((t :foreground ,(hasliberg-theme-hex-for 'primary-600) :weight semibold))) `(dashboard-footer-face ((t :inherit oblique-only :foreground ,(hasliberg-theme-hex-for 'primary-800)))) ) ) (defun hasliberg-theme--update-org-related-faces () (custom-theme-set-faces 'hasliberg ;;;;---------------------------------------- ;;; Org Mode ;;------------------------------------------ ;; Other Org Mode related handling (third party) `(org-modern-todo ((t :inherit fixed-pitch :background ,(hasliberg-theme-hex-for 'primary-600)))) `(org-modern-symbol ((t :inherit fixed-pitch))) ) ) (defun hasliberg-theme--update-markdown-faces () (custom-theme-set-faces 'hasliberg ;;;;---------------------------------------- ;;; Markdown ;;------------------------------------------ ;; I work with markdown only when I have to. Org Mode is my preference, ;; and thus all the configurations here are only to inherit from Org Mode. `(markdown-header-face-1 ((t :inherit org-level-1))) `(markdown-header-face-2 ((t :inherit org-level-2))) `(markdown-header-face-3 ((t :inherit org-level-3))) `(markdown-header-face-4 ((t :inherit org-level-4))) `(markdown-header-face-5 ((t :inherit org-level-5))) `(markdown-header-delimiter-face ((t :inherit fixed-pitch :foreground ,(hasliberg-theme-hex-for 'accent-200) :height 1.1))) `(markdown-language-keyword-face ((t :inherit org-block))) `(markdown-table-face ((t :inherit org-table))) `(markdown-pre-face ((t :inherit org-block))) `(markdown-html-attr-name-face ((t :inherit org-block :foreground ,(hasliberg-theme-hex-for 'primary-600)))) `(markdown-html-attr-value-face ((t :inherit org-block :foreground ,(hasliberg-theme-hex-for 'primary-800)))) `(markdown-html-entity-face ((t :inherit org-block :foreground ,(hasliberg-theme-hex-for 'primary-500)))) `(markdown-html-tag-delimiter-face ((t :inherit org-block :foreground ,(hasliberg-theme-hex-for 'primary-300)))) `(markdown-html-tag-name-face ((t :inherit org-block :foreground ,(hasliberg-theme-hex-for 'primary-700)))) ) ) (defun hasliberg-theme--update-lsp-faces () (custom-theme-set-faces 'hasliberg ;;;;---------------------------------------- ;;; LSP ;;------------------------------------------ `(lsp-face-highlight-textual ((t :background ,(hasliberg-theme-hex-for 'background-variant-700)))) `(lsp-headerline-breadcrumb-path-face ((t :inherit fixed-pitch :foreground ,(hasliberg-theme-hex-for 'primary-500)))) `(lsp-headerline-breadcrumb-symbols-face ((t :inherit fixed-pitch :foreground ,(hasliberg-theme-hex-for 'primary-700) :weight semibold))) ) ) (defun hasliberg-theme--update-language-specific-faces () (custom-theme-set-faces 'hasliberg ;;;;---------------------------------------- ;;; Language Specific Ones ;;------------------------------------------ `(sh-quoted-exec ((t :foreground ,(hasliberg-theme-hex-for 'accent-600)))) `(sh-heredoc ((t :foreground ,(hasliberg-theme-hex-for 'accent-700)))) ) ) (defun hasliberg-theme--update-all-faces () "Update all faces." (hasliberg-theme--update-standard-faces) (hasliberg-theme--update-echo-buffer) (hasliberg-theme--update-mode-line-faces) (hasliberg-theme--update-dashboard-faces) (hasliberg-theme--update-org-related-faces) ;; (hasliberg-theme--update-markdown-faces) (hasliberg-theme--update-lsp-faces) (hasliberg-theme--update-language-specific-faces) ) ;;;;---------------------------------------- ;;; Finalise ;;------------------------------------------ (hasliberg-theme--update-shades) (hasliberg-theme--update-all-faces) (when load-file-name (add-to-list 'custom-theme-load-path (file-name-as-directory (file-name-directory load-file-name)))) (provide-theme 'hasliberg)