From 058bcab2a7025bc77e425becdf396605c39eb987 Mon Sep 17 00:00:00 2001 From: Kinneyzhang Date: Sat, 22 Aug 2026 10:19:16 +0800 Subject: [PATCH] Keep Button state presentation themeable --- etaf-ui.el | 26 +++++++++++++++++++------- tests/etaf-ui-tests.el | 25 +++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/etaf-ui.el b/etaf-ui.el index 33b0d40..333ba16 100644 --- a/etaf-ui.el +++ b/etaf-ui.el @@ -254,6 +254,9 @@ SELECTED-KEY, and ROW-SELECTED-P provide interaction state." (variant (and (not disabled) (etaf-current-prop :variant))) (variant-values (cond + (disabled + '(:color "#687386" :bgcolor "#E5E7EB" + :border ((1) solid "#9CA3AF") :face normal)) ((and (eq variant 'secondary) state-pressed) '(:color "#142235" :bgcolor "#B9DED7" :border ((1) solid "#24736C") :face bold)) @@ -265,7 +268,16 @@ SELECTED-KEY, and ROW-SELECTED-P provide interaction state." :border ((1) solid "#A79F93") :face normal)) ((eq variant 'ghost) '(:color "#142235" :bgcolor "#FFFDF8" - :border ((1) solid "#C8C1B6") :face normal))))) + :border ((1) solid "#C8C1B6") :face normal)) + ;; The enabled/pressed catalog defaults used to come from + ;; state selectors alone. Resolve them as props too so an + ;; explicit theme can override the same state boundary. + (state-pressed + '(:color "#FFFFFF" :bgcolor "#1E5A56" + :border ((1) solid "#174A47") :face bold)) + (t + '(:color "#FFFFFF" :bgcolor "#2F6B43" + :border ((1) solid "#2F6B43") :face bold))))) (etaf-ui--button-view label (and press-p press) disabled (etaf-current-prop :ref) (let ((custom-class (etaf-current-prop :class))) @@ -295,12 +307,12 @@ contract; callers can still override presentation with the ordinary props." :styles (styles ("&" :width max-content) - ("&.disabled" :color "#687386" :bgcolor "#E5E7EB" - :border ((1) solid "#9CA3AF") :padding (0 1) :face normal) - ("&.enabled" :color "#FFFFFF" :bgcolor "#2F6B43" - :border ((1) solid "#2F6B43") :padding (0 1) :face bold) - ("&.pressed" :color "#FFFFFF" :bgcolor "#1E5A56" - :border ((1) solid "#174A47") :padding (0 1) :face bold)) + ;; State classes carry semantic state only. Resolved presentation props + ;; above remain authoritative, so a themed disabled Button cannot inherit + ;; the catalog's light default surface. + ("&.disabled" :padding (0 1) :face normal) + ("&.enabled" :padding (0 1) :face bold) + ("&.pressed" :padding (0 1) :face bold)) :setup (etaf-ui--button-setup)) diff --git a/tests/etaf-ui-tests.el b/tests/etaf-ui-tests.el index df0963c..6a79f39 100644 --- a/tests/etaf-ui-tests.el +++ b/tests/etaf-ui-tests.el @@ -185,6 +185,31 @@ (when-let ((buffer (get-buffer buffer-name))) (kill-buffer buffer))))) +(ert-deftest etaf-ui-button-disabled-preserves-explicit-theme-surface () + "A disabled Button keeps explicit dark presentation props in its surface." + (let ((buffer-name " *etaf-ui-themed-disabled-button-test*")) + (unwind-protect + (progn + (etaf-mount + buffer-name + (etaf-view + (button :label "Unavailable" :ref 'themed-disabled + :disabled t :color "#F4F7FF" :bgcolor "#202C42" + :border "#34435A"))) + (let ((position + (with-current-buffer buffer-name + (goto-char (point-min)) + (search-forward "Unavailable") + (1- (point))))) + (with-current-buffer buffer-name + (let ((face (get-text-property position 'face))) + (should (string-match-p "#202C42" (format "%S" face))) + (should (string-match-p "#F4F7FF" (format "%S" face)))))) + (when-let ((runtime (etaf-runtime-for-buffer buffer-name))) + (etaf-unmount runtime)) + (when-let ((buffer (get-buffer buffer-name))) + (kill-buffer buffer)))))) + (ert-deftest etaf-ui-button-owns-pointer-hover-and-pressed-feedback () "Buttons expose native pointer/hover affordances and a pressed state." (let ((buffer-name " *etaf-ui-button-surface-test*")