Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make modus-themes-list-colors sort by colour name #60

Open
bestlem opened this issue Jan 6, 2023 · 13 comments
Open

make modus-themes-list-colors sort by colour name #60

bestlem opened this issue Jan 6, 2023 · 13 comments
Labels

Comments

@bestlem
Copy link

bestlem commented Jan 6, 2023

I was just comparing the colours in versions 3 and 4 and I found it difficult to find all shades of say magenta. This is because they are not in a common place in the output.

Could modus-themes-list-colors be sorted by colour name so that for example all magenta shades would be close together,

Is the current order significant in any way?

@protesilaos
Copy link
Owner

Hello @bestlem!

I was just comparing the colours in versions 3 and 4 and I found it difficult to find all shades of say magenta. This is because they are not in a common place in the output.

Note that several colours from the old version do not exist anymore. I removed all those that were introduced ad-hoc or, generally, were not used considerately.

Could modus-themes-list-colors be sorted by colour name so that for example all magenta shades would be close together,

Is the current order significant in any way?

The palette is organised in subsets. For example, modus-operandi starts with:

 #ffffff       #ffffff       bg-main
 #f0f0f0       #f0f0f0       bg-dim
 #000000       #000000       fg-main
 #595959       #595959       fg-dim
 #193668       #193668       fg-alt
 #c4c4c4       #c4c4c4       bg-active
 #e0e0e0       #e0e0e0       bg-inactive
 #9f9f9f       #9f9f9f       border

Those are the basic/common "base" colours.

Then we have the common accent colours for regular text:

#a60000       #a60000       red
#972500       #972500       red-warmer
#a0132f       #a0132f       red-cooler
#7f0000       #7f0000       red-faint
#d00000       #d00000       red-intense
#006800       #006800       green
#316500       #316500       green-warmer
#00663f       #00663f       green-cooler
#2a5045       #2a5045       green-faint
#008900       #008900       green-intense
#6f5500       #6f5500       yellow
#884900       #884900       yellow-warmer
#7a4f2f       #7a4f2f       yellow-cooler
#624416       #624416       yellow-faint
#808000       #808000       yellow-intense
#0031a9       #0031a9       blue
#354fcf       #354fcf       blue-warmer
#0000b0       #0000b0       blue-cooler
#003497       #003497       blue-faint
#0000ff       #0000ff       blue-intense
#721045       #721045       magenta
#8f0075       #8f0075       magenta-warmer
#531ab6       #531ab6       magenta-cooler
#7c318f       #7c318f       magenta-faint
#dd22dd       #dd22dd       magenta-intense
#005e8b       #005e8b       cyan
#3f578f       #3f578f       cyan-warmer
#005f5f       #005f5f       cyan-cooler
#005077       #005077       cyan-faint
#008899       #008899       cyan-intense

In the above, you will notice that the grouping is on a per-hue basis.

Further down there are background values and purpose-specific groups like:

#c1f2d1       #c1f2d1       bg-added
#d8f8e1       #d8f8e1       bg-added-faint
#aee5be       #aee5be       bg-added-refine
#8cca8c       #8cca8c       bg-added-intense
#005000       #005000       fg-added

Sorting by hue will not always yield the desired results. For example, the "added" colours are easier to compare in their context with their "removed" counterparts than with the common green hues.

Perhaps it is easier if I help you identify a colour from the old version or, at least, a proxy for it?

@bestlem
Copy link
Author

bestlem commented Jan 7, 2023

It is not by hue I was after.

More alphabetical so for example I can see all magenta-* together.

The reason for this is you have changed the background colours in org-blocks soi that I find some text difficult to read due to low contrast and I wanted to see all magenta to choose what it should be.

protesilaos added a commit that referenced this issue Jan 7, 2023
This fixes a mistake of mine where I amplified the background
colouration of Org blocks.  This concerns the case where the user
option 'modus-themes-org-blocks' has a 'tinted-background' value.

Thanks to Mark Bestley for informing me about this in issue 60 on the
GitHub mirror:
<#60 (comment)>.
@protesilaos
Copy link
Owner

I understand you are using modus-themes-org-blocks with the tinted-background value, right? I just made a change that fixes the mistake I did before: the increased intensity of the background was not intentional. The blocks should now have the same background as before.

@bestlem
Copy link
Author

bestlem commented Jan 8, 2023

Thanks that fixes my immediate issue.

However I think that having list-colors be sortable perhaps by clicking on the column headers would be useful

@protesilaos
Copy link
Owner

having list-colors be sortable perhaps by clicking on the column headers would be useful

Yes, this would be useful. I believe we can do it with tabulated-list-mode, though I must learn how to set it up.

@edgar-vincent
Copy link

FWIW, I think the ability to sort the list would be nice, but I like the default order for the reason Prot has mentioned.

@protesilaos
Copy link
Owner

@edgar-vincent I will need to experiment with the tabulated list to better understand what our options are.

@protesilaos
Copy link
Owner

I have been experimenting with this. The tabulated-list-mode might cause us problems. I abandonned that effort and tried various sorting techniques using this method:

(defun my-modus-themes--sorted-palette (theme &optional overrides)
  (let ((bg (face-background 'default)))
    (sort (seq-filter (lambda (entry)
                        (stringp (cadr entry)))
                      (append (symbol-value (modus-themes--palette-symbol theme :overrides))
                              modus-themes-common-palette-overrides
                              (symbol-value (modus-themes--palette-symbol theme))))
          (lambda (x y) ; comment this lambda out and enable the one below
            (string-lessp (car x) (car y)))
          ;; (lambda (x y)
          ;;   (< (modus-themes-contrast bg (cadr x))
          ;;      (modus-themes-contrast bg (cadr y))))
          )))

(defun my-modus-themes-list-colors-current-sorted ()
  (interactive)
  (cl-letf (((symbol-function 'modus-themes--palette-value) #'my-modus-themes--sorted-palette))
    (modus-themes-list-colors-current)))

Do M-x my-modus-themes-list-colors-current-sorted to see the results.

I think this is not good enough because it places unrelated colours next to each other, such as "border" close to "blue". The method that sorts by contrast is a bit better as it helps put all the backgrounds together, but it still groups them in awkward ways.

There probably is a more sophisticated algorithm we can use, though it needs more thought.

@edgar-vincent
Copy link

I'm working on a better sorting solution. I hope to be able to finish it today.

@edgar-vincent
Copy link

This is my attempt at sorting the colours. I loosely followed the method described there: https://tomekdev.com/posts/sorting-colors-in-js

What this does is that it clusters colours together around a series of main colours (red, orange, yellow, etc.), and then each cluster is sorted by luminosity (for colours which are close to black, white and grey) or saturation (all the others).

The result is not perfect, but it is an improvement over the previous solutions, and it is reasonably fast, at least on my computer. It is a lot of code for a minor feature, though, so it might need to be simplified.

(require 'color)

(defun my-modus-themes--hex-to-rgb (hex-color &optional range)
  "Convert HEX-COLOR to a list of the form (RED GREEN BLUE).
HEX-COLOR must be a string of the form '#AABBCC'. Optional parameter RANGE
specifies the maximum value of RED, GREEN and BLUE (by default, 255)."
  (let* ((hex (string-to-number (string-remove-prefix "#" hex-color) 16))
         (rgb-raw (reverse (mapcar (lambda (d)
                                     (% (/ hex (expt 16 (- d 1))) 16))
                                   (number-sequence 1 6))))
         (rgb)
         (i 1))

    (mapc (lambda (e)
            (when (= 1 (% i 2))
              (push (+ (* e 16) (nth i rgb-raw)) rgb))
            (setq i (1+ i)))
          rgb-raw)
    (if range
        (reverse (mapcar (lambda (d)
                           (/ (* d range) 255))
                         rgb))
      (reverse rgb))))

(defun my-modus-themes--color-distance (color1 color2)
  "Return the distance between COLOR1 and COLOR2.
Both arguments should be expressed in hexadecimal notation"
  ;; Euclidian distance.
  (let ((rgb1 (my-modus-themes--hex-to-rgb color1))
        (rgb2 (my-modus-themes--hex-to-rgb color2))
        (result 0))

    (dotimes (i 3)
      (setq result (+ result
                      (expt (- (nth i rgb1)
                               (nth i rgb2)) 2))))
    (sqrt result)))
;; From core Emacs' color.el. This doesn't give good results so far.
;; (color-distance (my-modus-themes--hex-to-rgb color1 65536) (my-modus-themes--hex-to-rgb color2 65536)))
;; From ct.el
;; (require 'ct)
;; (ct-distance color1 color2))

(defun my-modus-themes--sorted-palette (theme &optional overrides)
  (apply #'append ;; Flatten the color list.
         (mapcar (lambda (cluster)   ;; Sort each cluster.
                   (let ((cluster-main (car cluster))      ;; We need to know the main color of each cluster
                         (cluster-stripped (cdr cluster))) ;; because black, white and grey are sorted by lightness
                                                           ;; while the others are sorted by saturation.
                     (sort cluster-stripped  ;; We want to sort the palette colors only, not the main clusters' color.
                           (lambda (x y)
                             (let* ((color1-rgb (my-modus-themes--hex-to-rgb (cadr x) 1.0))
                                    (color2-rgb (my-modus-themes--hex-to-rgb (cadr y) 1.0))
                                    (color1-hsl (apply #'color-rgb-to-hsl color1-rgb))
                                    (color2-hsl (apply #'color-rgb-to-hsl color2-rgb)))

                               (if (member cluster-main '("black" "grey" "white"))
                                   (< (nth 2 color1-hsl) (nth 2 color2-hsl))
                                 (< (nth 1 color1-hsl) (nth 1 color2-hsl))))))))

                 ;; Put each color of the palette into a cluster.
                 (let ((colors (seq-filter (lambda (entry) ;; List of Modus palette colors.
                                             (stringp (cadr entry)))
                                           (append (symbol-value (modus-themes--palette-symbol theme :overrides))
                                                   modus-themes-common-palette-overrides
                                                   (symbol-value (modus-themes--palette-symbol theme)))))
                       (clusters '(((red "#ff0000"))
                                   ((orange "#ff8000"))
                                   ((yellow "#ffff00"))
                                   ((chartreuse "#80ff00"))
                                   ((green "#00ff00"))
                                   ((spring-green "#00ff80"))
                                   ((cyan "#00ffff"))
                                   ((azure "#007fff"))
                                   ((blue "#0000ff"))
                                   ((violet "#7f00ff"))
                                   ((magenta "#ff00ff"))
                                   ((rose "#ff0080"))
                                   ((black "#000000"))
                                   ((grey "#ebebeb"))
                                   ((white "#ffffff")))))

                   (dolist (color colors)
                     (let* ((cluster-key
                             (let ((closest-distance)
                                   (closest-cluster))

                               (dolist (cluster clusters closest-cluster)
                                 (let* ((cur-cluster (car cluster))
                                        (cur-distance (my-modus-themes--color-distance (cadr color) (cadr cur-cluster))))
                                   (when (null closest-distance)
                                     (setq closest-distance cur-distance
                                           closest-cluster cur-cluster))
                                   (when (< cur-distance closest-distance)
                                     (setq closest-distance cur-distance
                                           closest-cluster cur-cluster))))))
                            (cluster-entry  (assoc cluster-key clusters)))
                       (setf (cdr cluster-entry) (push color (cdr cluster-entry)))))
                   clusters))))

(defun my-modus-themes-list-colors-current-sorted ()
  (interactive)
  (cl-letf (((symbol-function 'modus-themes--palette-value) #'my-modus-themes--sorted-palette))
    (modus-themes-list-colors-current)))

@protesilaos
Copy link
Owner

Thank you @edgar-vincent! Sorting those is not easy and we see how the code has to expand considerably. Each method has its advantages, though we ultimately lose the original grouping of the palette. For example, with your approach I get this somewhere in the middle:

 #006700       #006700       fg-added-intense
 #005000       #005000       fg-added
 #7f0000       #7f0000       modeline-err
 #005077       #005077       cyan-faint
 #005f5f       #005f5f       cyan-cooler

These values are close to each other based on the criteria you use, but they are unrelated in terms of how they are applied in the themes. The "added" colours go together with the "removed" and "changed" entries, as they all concern diffs. The modeline-err should be in the same context as modeline-warning and modeline-info. They are all close to the other modeline colours.

Again, this is not to say that your approach is wrong: this is a matter of preference and I still find the default to be the best (or least bad) for the kind of semantics that are involved.

@edgar-vincent
Copy link

I agree with you. I think the (default) semantic sorting method is the best. Anyway, my algorithm is there for those who want to use it.

@protesilaos
Copy link
Owner

I know it has been a while, but I just pushed a change to make the preview commands use tabulated-list-mode. If you have the time, please take a look.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants