Files
cl-tty/org/tabbar.org
Hermes Agent a061d60898 split: scrollbox-tabbar.org into scrollbox.org, tabbar.org, container-package.org
- Create org/scrollbox.org (tangles scrollbox.lisp)
- Create org/tabbar.org (tangles tabbar.lisp)
- Create org/container-package.org (tangles container-package.lisp)
- Disable :tangle in old scrollbox-tabbar.org (kept for prose docs)
- Fix missing paren in render method (was depth=1 at EOF)
- All 483 tests pass, 14 suites, 100%
2026-05-12 18:00:06 +00:00

2.6 KiB

TabBar

Overview

TabBar handles horizontal tab navigation with keyboard support. Tabs are rendered as labeled items; the active tab is highlighted.

Implementation

(in-package #:cl-tty.container)

(defclass tab-bar (dirty-mixin)
  ((tabs :initform nil :initarg :tabs :accessor tab-bar-tabs :type list)
   (active :initform nil :initarg :active :accessor tab-bar-active)
   (layout-node :initform (make-layout-node) :accessor tab-bar-layout-node)
   (focusable :initform t :accessor tab-bar-focusable)))

(defun make-tab-bar (&key tabs active)
  (make-instance 'tab-bar :tabs (or tabs nil) :active active))

(defun tab-bar-add (tb id title)
  (setf (tab-bar-tabs tb) (nconc (tab-bar-tabs tb) (list (list :id id :title title))))
  (unless (tab-bar-active tb) (setf (tab-bar-active tb) id)) id)

(defmethod component-layout-node ((tb tab-bar)) (tab-bar-layout-node tb))

(defun tab-bar-next (tb)
  (let* ((tabs (tab-bar-tabs tb)) (current (tab-bar-active tb))
         (ids (mapcar (lambda (tab) (getf tab :id)) tabs)) (pos (position current ids)))
    (when pos (let ((next (nth (mod (1+ pos) (length ids)) ids)))
                (setf (tab-bar-active tb) next) (mark-dirty tb)))))

(defun tab-bar-prev (tb)
  (let* ((tabs (tab-bar-tabs tb)) (current (tab-bar-active tb))
         (ids (mapcar (lambda (tab) (getf tab :id)) tabs)) (pos (position current ids)))
    (when pos (let ((prev (nth (mod (1- pos) (length ids)) ids)))
                (setf (tab-bar-active tb) prev) (mark-dirty tb)))))

(defun tab-bar-select (tb id) (setf (tab-bar-active tb) id) (mark-dirty tb))

(defun tab-bar-handle-key (tb event)
  (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)) (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 x))
    (dolist (tab tabs)
      (let* ((id (getf tab :id)) (title (getf tab :title))
             (label (format nil " ~A " title)) (label-len (length label))
             (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) (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)))))
  (values))