v0.15.0: Critical input/rendering fixes, subagent-reviewed #7

Merged
amr merged 36 commits from feature/v0.11.0-slots into main 2026-05-11 22:03:18 -04:00
6 changed files with 40 additions and 29 deletions
Showing only changes of commit abf8e5cdeb - Show all commits

View File

@@ -59,18 +59,27 @@ module adds:
(when handler (funcall handler event))))
(defun hit-test (root x y)
"Find the deepest component at (X, Y) by testing layout-node bounds.
Recurses into component-children to find the innermost match.
Components without a layout-node or position return nil."
(labels ((recurse (node)
(when (and (slot-exists-p node 'x) (slot-boundp node 'x)
(slot-exists-p node 'y) (slot-boundp node 'y)
(slot-exists-p node 'width) (slot-boundp node 'width)
(slot-exists-p node 'height) (slot-boundp node 'height))
(let ((nx (slot-value node 'x))
(ny (slot-value node 'y))
(nw (slot-value node 'width))
(nh (slot-value node 'height)))
(when (and (>= x nx) (< x (+ nx nw))
(>= y ny) (< y (+ ny nh)))
node)))))
(let ((ln (ignore-errors (component-layout-node node)))
(best nil))
(when ln
(let ((nx (layout-node-x ln))
(ny (layout-node-y ln))
(nw (layout-node-width ln))
(nh (layout-node-height ln)))
;; Check children first for deeper match
(dolist (child (ignore-errors (component-children node)))
(let ((child-hit (recurse child)))
(when child-hit
(setf best child-hit))))
;; If no child matched, check self
(or best
(when (and (>= x nx) (< x (+ nx nw))
(>= y ny) (< y (+ ny nh)))
node)))))))
(recurse root)))
;; Selection

View File

@@ -504,7 +504,8 @@ they are truncated with an ellipsis.
#+BEGIN_SRC lisp
(defmethod render ((tb tab-bar) backend)
(let* ((ln (tab-bar-layout-node tb))
(x 0) (y 0)
(x (if ln (layout-node-x ln) 0))
(y (if ln (layout-node-y ln) 0))
(w (if ln (layout-node-width ln) 80))
(active-id (tab-bar-active tb))
(tabs (tab-bar-tabs tb))
@@ -664,9 +665,11 @@ Children outside the viewport are skipped."
(case (key-event-key event) (:left (tab-bar-prev tb) t) (:right (tab-bar-next tb) t) (t nil)))
(defmethod render ((tb tab-bar) backend)
(let* ((ln (tab-bar-layout-node tb)) (y 0)
(let* ((ln (tab-bar-layout-node tb))
(x (if ln (layout-node-x ln) 0))
(y (if ln (layout-node-y ln) 0))
(w (if ln (layout-node-width ln) 80))
(active-id (tab-bar-active tb)) (tabs (tab-bar-tabs tb)) (x-pos 0))
(active-id (tab-bar-active tb)) (tabs (tab-bar-tabs tb)) (x-pos x))
(dolist (tab tabs)
(let* ((id (getf tab :id)) (title (getf tab :title))
(label (format nil " ~A " title)) (label-len (length label))

View File

@@ -402,7 +402,8 @@ not selectable (visually distinct).
#+BEGIN_SRC lisp
(defmethod render ((sel select) backend)
(let* ((ln (select-layout-node sel))
(x 0) (y 0)
(x (if ln (layout-node-x ln) 0))
(y (if ln (layout-node-y ln) 0))
(w (if ln (layout-node-width ln) 80))
(visible (select-visible-options sel))
(sel-idx (select-selected-index sel)))
@@ -508,7 +509,9 @@ not selectable (visually distinct).
(subseq filtered start end)))
(defmethod render ((sel select) backend)
(let* ((ln (select-layout-node sel)) (x 0) (y 0)
(let* ((ln (select-layout-node sel))
(x (if ln (layout-node-x ln) 0))
(y (if ln (layout-node-y ln) 0))
(w (if ln (layout-node-width ln) 80))
(visible (select-visible-options sel)) (sel-idx (select-selected-index sel)))
(dolist (item visible)

View File

@@ -603,9 +603,9 @@ debugging argument mismatches — avoid that trap.
(cursor (text-input-cursor in))
(display (if (plusp (length value))
value
(or (text-input-placeholder in) ""))))
(declare (ignore w cursor))
(draw-text backend x y display nil nil)))
(or (text-input-placeholder in) "")))
(truncated (subseq display 0 (min (length display) w))))
(draw-text backend x y truncated nil nil)))
#+END_SRC
@@ -861,7 +861,6 @@ debugging argument mismatches — avoid that trap.
(h (if ln (layout-node-height ln) 24))
(lines (textarea-lines ta))
(max-lines (min (length lines) h)))
(declare (ignore w))
(loop for i from 0 below max-lines
for line in lines
do (draw-text backend x (+ y i)

View File

@@ -72,21 +72,18 @@ Children outside the viewport are skipped."
(if (> content-size viewport-size) (/ (float scroll-pos) (- content-size viewport-size)) 0.0))
(defun draw-scrollbars (sb backend viewport-w viewport-h)
(let* ((ln (scroll-box-layout-node sb))
(vx (if ln (layout-node-x ln) 0))
(vy (if ln (layout-node-y ln) 0))
(content-h (scroll-box-content-height sb)) (content-w (scroll-box-content-width sb))
(let* ((content-h (scroll-box-content-height sb)) (content-w (scroll-box-content-width sb))
(sy (scroll-box-scroll-y sb)) (sx (scroll-box-scroll-x sb)))
(when (> content-h viewport-h)
(let* ((thumb (scrollbar-thumb sy viewport-h content-h))
(thumb-pos (round (* thumb viewport-h))))
(draw-rect backend (+ vx (1- viewport-w)) vy 1 viewport-h :bg :bright-black)
(draw-text backend (+ vx (1- viewport-w)) (+ vy thumb-pos) "█" nil nil)))
(draw-rect backend (1- viewport-w) 0 1 viewport-h :bg :bright-black)
(draw-text backend (1- viewport-w) thumb-pos "█" nil nil)))
(when (> content-w viewport-w)
(let* ((thumb (scrollbar-thumb sx viewport-w content-w))
(thumb-pos (round (* thumb viewport-w))))
(draw-rect backend vx (+ vy (1- viewport-h)) viewport-w 1 :bg :bright-black)
(draw-text backend (+ vx thumb-pos) (+ vy (1- viewport-h)) "█" nil nil)))))
(draw-rect backend 0 (1- viewport-h) viewport-w 1 :bg :bright-black)
(draw-text backend thumb-pos (1- viewport-h) "█" nil nil)))))
(defun update-sticky-scroll (sb)
(when (sticky-scroll-p sb)

View File

@@ -46,7 +46,7 @@
(is-active (eql id active-id))
(fg (if is-active :accent :text-muted))
(bg (if is-active :background-element nil)))
(when (> (+ x-pos label-len 2) w)
(when (>= (+ x-pos label-len 2) w)
(draw-text backend x-pos y "..." :text-muted nil) (return))
(draw-text backend x-pos y label fg bg)
(incf x-pos (+ label-len 2)))))