aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-07-20 23:43:11 -0500
committerCraig Jennings <c@cjennings.net>2026-07-20 23:43:11 -0500
commit552736ec479903edbc4576182f52469c6dc222c5 (patch)
treeba32983863b6627d31cc94fd32fbe296fb24d449
parentc4fae62bf541b13429c93e7422a568041c9d57bd (diff)
downloadarchsetup-552736ec479903edbc4576182f52469c6dc222c5.tar.gz
archsetup-552736ec479903edbc4576182f52469c6dc222c5.zip
fix(design): clamp the gauge once so needle and readout agree
The needle angle clamped to 0-100 while the readout rendered the raw value, so a gauge fed 150 pinned the needle at +60 degrees under a "150%" label. A shared gallery-widget--clamp-value now feeds both halves. Two hygiene fixes ride along. The module requires cl-lib explicitly instead of leaning on svg.el's transitive require. The token-file load resolves its directory through gallery-widget--source-dir, which falls back to default-directory when the form is re-evaluated outside a load and outside a file buffer (both location variables are nil there and file-name-directory errored).
-rw-r--r--docs/prototypes/gallery-widget.el25
-rw-r--r--tests/gallery-widgets/test-gallery-widget.el35
2 files changed, 55 insertions, 5 deletions
diff --git a/docs/prototypes/gallery-widget.el b/docs/prototypes/gallery-widget.el
index 5d69148..5a80e59 100644
--- a/docs/prototypes/gallery-widget.el
+++ b/docs/prototypes/gallery-widget.el
@@ -24,9 +24,16 @@
(require 'svg)
(require 'dom)
+(require 'cl-lib)
-(load (expand-file-name "gallery-tokens.el"
- (file-name-directory (or load-file-name buffer-file-name)))
+(defun gallery-widget--source-dir ()
+ "Directory holding this module's source (and gallery-tokens.el).
+Falls back to `default-directory' when evaluated outside a load and
+outside a file buffer, where both location variables are nil."
+ (let ((src (or load-file-name buffer-file-name)))
+ (if src (file-name-directory src) default-directory)))
+
+(load (expand-file-name "gallery-tokens.el" (gallery-widget--source-dir))
nil t)
(defun gallery-widget-token (name)
@@ -43,11 +50,17 @@ a typo'd token must fail loudly, not render black."
(defconst gallery-widget--gauge-max-deg 60.0
"Needle angle at value 100.")
-(defun gallery-widget--needle-angle (value)
- "Map VALUE (0-100, clamped) onto the gauge's needle angle in degrees."
+(defun gallery-widget--clamp-value (value)
+ "Validate VALUE is a number and clamp it to the gauge's 0-100 range.
+One clamp shared by the needle angle and the readout, so the two halves
+of the widget can never disagree at an out-of-range input."
(unless (numberp value)
(error "Gauge value must be a number, got %S" value))
- (let ((v (min 100.0 (max 0.0 (float value)))))
+ (min 100.0 (max 0.0 (float value))))
+
+(defun gallery-widget--needle-angle (value)
+ "Map VALUE (0-100, clamped) onto the gauge's needle angle in degrees."
+ (let ((v (gallery-widget--clamp-value value)))
(+ gallery-widget--gauge-min-deg
(* (/ v 100.0)
(- gallery-widget--gauge-max-deg gallery-widget--gauge-min-deg)))))
@@ -77,6 +90,8 @@ Positive angles swing right, matching the gauge's needle travel."
Geometry mirrors the web card: 96px-wide semicircular dial, pivot at its
bottom center, 40px needle sweeping -60..+60 degrees, readout below."
(let* ((w 96) (h 64) (cx 48.0) (cy 48.0)
+ ;; clamp once; the angle and the readout render the same value
+ (value (gallery-widget--clamp-value value))
(angle (gallery-widget--needle-angle value))
(tip (gallery-widget--polar cx cy 40.0 angle))
(svg (svg-create w h :viewBox (format "0 0 %d %d" w h))))
diff --git a/tests/gallery-widgets/test-gallery-widget.el b/tests/gallery-widgets/test-gallery-widget.el
index 166cd59..de0c165 100644
--- a/tests/gallery-widgets/test-gallery-widget.el
+++ b/tests/gallery-widgets/test-gallery-widget.el
@@ -110,5 +110,40 @@
"The readout shows a rounded integer percent, matching the web card."
(should (string-match-p ">67%<" (test-gallery-widget--svg-string 66.6))))
+(ert-deftest gallery-widget-gauge-out-of-range-readout-clamps ()
+ "Readout and needle agree at out-of-range values: both clamp.
+Regression: the needle clamped to the dial ends while the readout rendered
+the raw value, so a gauge fed 150 pinned the needle at +60 degrees under a
+\"150%\" label."
+ (let ((over (test-gallery-widget--svg-string 150)))
+ (should (string-match-p ">100%<" over))
+ (should-not (string-match-p ">150%<" over)))
+ (let ((under (test-gallery-widget--svg-string -5)))
+ (should (string-match-p ">0%<" under))
+ (should-not (string-match-p ">-5%<" under))))
+
+(ert-deftest gallery-widget-source-dir-load-and-fallback ()
+ "The token-file directory resolves from load context, else default-directory.
+Regression: `file-name-directory' received nil when the load form was re-evaluated
+outside a load and outside a file buffer."
+ (let ((load-file-name "/tmp/proto/gallery-widget.el"))
+ (should (equal (gallery-widget--source-dir) "/tmp/proto/")))
+ (with-temp-buffer
+ (let ((load-file-name nil)
+ (default-directory "/tmp/elsewhere/"))
+ (should (equal (gallery-widget--source-dir) "/tmp/elsewhere/")))))
+
+(ert-deftest gallery-widget-requires-cl-lib-at-load-time ()
+ "Loading the module alone brings in cl-lib, not just an autoload cookie.
+A cold byte-compile or a changed autoload would otherwise break
+`gallery-widget--node' with a void-function error on first call."
+ (with-temp-buffer
+ (let ((emacs (expand-file-name invocation-name invocation-directory))
+ (widget (expand-file-name "docs/prototypes/gallery-widget.el"
+ test-gallery-widget--root)))
+ (call-process emacs nil t nil "--batch" "-l" widget
+ "--eval" "(princ (if (featurep 'cl-lib) \"cl-lib:yes\" \"cl-lib:no\"))")
+ (should (string-match-p "cl-lib:yes" (buffer-string))))))
+
(provide 'test-gallery-widget)
;;; test-gallery-widget.el ends here