#+TITLE: TabBar #+STARTUP: content #+FILETAGS: :cl-tty:container: * Overview TabBar handles horizontal tab navigation with keyboard support. Tabs are rendered as labeled items; the active tab is highlighted. * Implementation #+BEGIN_SRC lisp :tangle ../src/components/tabbar.lisp (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)) #+END_SRC