v1.0.0: merge mouse → input — eliminate cl-tty.mouse package
The mouse-event struct was already in cl-tty.input. All mouse handling logic (mouse-mixin, hit-test, selection, clipboard, link detection) was in a separate cl-tty.mouse package. Moved everything into the input package where the struct lives, eliminating one package boundary. Changes: - absorb mouse-mixin, handle-mouse-event, hit-test, selection struct, selection variables/functions, cell-link-at, open-link-at into text-input.org (tangled to input.lisp) - update cl-tty.input defpackage with mouse exports - mouse tests merged into INPUT-SUITE (appended to input-tests.lisp) - delete mouse.org, mouse-package.lisp, mouse.lisp, mouse-tests.lisp - update ASDF, run-all-tests.lisp, scripts to drop mouse references All test suites pass at 100% (INPUT-SUITE: 102 tests, +6 from mouse)
This commit is contained in:
@@ -220,7 +220,18 @@ via ~sb-posix~ directly.
|
||||
#:keymap #:make-keymap #:keymap-name #:keymap-bindings #:keymap-parent
|
||||
#:*keymaps* #:*chord-timeout*
|
||||
#:defkeymap #:dispatch-key-event #:key-match-p
|
||||
#:component-keymap))
|
||||
#:component-keymap
|
||||
;; Mouse (merged from cl-tty.mouse)
|
||||
#:mouse-mixin
|
||||
#:on-mouse-down #:on-mouse-up #:on-mouse-move #:on-mouse-scroll
|
||||
#:handle-mouse-event
|
||||
#:hit-test
|
||||
#:selection #:get-selection #:copy-to-clipboard
|
||||
#:make-selection #:selection-p
|
||||
#:start-selection #:update-selection #:finalize-selection
|
||||
#:selection-active-p
|
||||
#:*selection* #:*selection-active* #:*selection-start* #:*selection-end*
|
||||
#:cell-link-at #:open-link-at))
|
||||
#+END_SRC
|
||||
|
||||
* Input Reader Core
|
||||
@@ -1769,6 +1780,153 @@ requiring components to inherit from a specific base class.
|
||||
(:method ((c t)) nil))
|
||||
#+END_SRC
|
||||
|
||||
* Mouse support (merged from cl-tty.mouse)
|
||||
|
||||
Mouse event propagation through the component tree. The input system
|
||||
already parses SGR mouse sequences into ~mouse-event~ structs. This
|
||||
section adds:
|
||||
|
||||
1. A ~mouse-mixin~ class with event handler slots
|
||||
2. Hit-testing: given (x,y), find the deepest component owning that cell
|
||||
3. Event dispatch: route ~mouse-event~ → component handlers, bubble up
|
||||
4. Text selection: drag highlight + clipboard copy
|
||||
|
||||
** mouse-mixin — mixin class for mouse event handler slots
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ~/.local/share/cl-tty/src/components/input.lisp
|
||||
(defclass mouse-mixin ()
|
||||
((on-mouse-down :initarg :on-mouse-down :initform nil :accessor on-mouse-down)
|
||||
(on-mouse-up :initarg :on-mouse-up :initform nil :accessor on-mouse-up)
|
||||
(on-mouse-move :initarg :on-mouse-move :initform nil :accessor on-mouse-move)
|
||||
(on-mouse-scroll :initarg :on-mouse-scroll :initform nil :accessor on-mouse-scroll)))
|
||||
#+END_SRC
|
||||
|
||||
** handle-mouse-event — dispatch mouse events to the right slot handler
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ~/.local/share/cl-tty/src/components/input.lisp
|
||||
(defun handle-mouse-event (component event)
|
||||
(let* ((type (mouse-event-type event))
|
||||
(handler (case type
|
||||
(:press (on-mouse-down component))
|
||||
(:release (on-mouse-up component))
|
||||
(:drag (on-mouse-move component))
|
||||
(t nil))))
|
||||
(when handler (funcall handler event))))
|
||||
#+END_SRC
|
||||
|
||||
** hit-test — find the deepest component at a given (x, y)
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ~/.local/share/cl-tty/src/components/input.lisp
|
||||
(defun hit-test (root x y)
|
||||
"Find the deepest component at (X, Y) by testing layout-node bounds."
|
||||
(labels ((recurse (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)))
|
||||
(dolist (child (ignore-errors (component-children node)))
|
||||
(let ((child-hit (recurse child)))
|
||||
(when child-hit (setf best child-hit))))
|
||||
(or best
|
||||
(when (and (>= x nx) (< x (+ nx nw))
|
||||
(>= y ny) (< y (+ ny nh)))
|
||||
node)))))))
|
||||
(recurse root)))
|
||||
#+END_SRC
|
||||
|
||||
** Selection state
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ~/.local/share/cl-tty/src/components/input.lisp
|
||||
(defvar *selection* nil)
|
||||
(defvar *selection-active* nil "T when a drag selection is in progress.")
|
||||
(defvar *selection-start* nil "Cons (X . Y) of mouse-down position during drag.")
|
||||
(defvar *selection-end* nil "Cons (X . Y) of current mouse position during drag.")
|
||||
#+END_SRC
|
||||
|
||||
** selection struct
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ~/.local/share/cl-tty/src/components/input.lisp
|
||||
(defstruct (selection (:conc-name sel-))
|
||||
(start-x 0) (start-y 0) (end-x 0) (end-y 0) (text ""))
|
||||
#+END_SRC
|
||||
|
||||
** get-selection / copy-to-clipboard
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ~/.local/share/cl-tty/src/components/input.lisp
|
||||
(defun get-selection ()
|
||||
(when *selection* (sel-text *selection*)))
|
||||
|
||||
(defun copy-to-clipboard (text)
|
||||
#+linux
|
||||
(cond
|
||||
((sb-ext:posix-getenv "WAYLAND_DISPLAY")
|
||||
(sb-ext:run-program "wl-copy" nil :input text :wait nil))
|
||||
(t
|
||||
(sb-ext:run-program "xclip" (list "-selection" "clipboard")
|
||||
:input text :wait nil)))
|
||||
#+darwin (sb-ext:run-program "pbcopy" nil :input text :wait nil))
|
||||
#+END_SRC
|
||||
|
||||
** start-selection / update-selection
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ~/.local/share/cl-tty/src/components/input.lisp
|
||||
(defun start-selection (x y)
|
||||
"Begin a drag selection at (X Y)."
|
||||
(setf *selection-start* (cons x y)
|
||||
*selection-end* (cons x y)
|
||||
*selection-active* t))
|
||||
|
||||
(defun update-selection (x y)
|
||||
"Update the drag selection end position to (X Y)."
|
||||
(setf *selection-end* (cons x y)))
|
||||
#+END_SRC
|
||||
|
||||
** selection-active-p
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ~/.local/share/cl-tty/src/components/input.lisp
|
||||
(defun selection-active-p ()
|
||||
"Return T if a drag selection is in progress."
|
||||
*selection-active*)
|
||||
#+END_SRC
|
||||
|
||||
** finalize-selection
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ~/.local/share/cl-tty/src/components/input.lisp
|
||||
(defun finalize-selection (fb)
|
||||
"End the drag selection and extract text from the framebuffer."
|
||||
(setf *selection-active* nil)
|
||||
(when (and *selection-start* *selection-end* fb)
|
||||
(let* ((x1 (car *selection-start*))
|
||||
(y1 (cdr *selection-start*))
|
||||
(x2 (car *selection-end*))
|
||||
(y2 (cdr *selection-end*))
|
||||
(text (cl-tty.rendering:extract-text fb x1 y1 x2 y2)))
|
||||
(setf *selection* (make-selection :start-x x1 :start-y y1
|
||||
:end-x x2 :end-y y2
|
||||
:text text))
|
||||
(setf *selection-start* nil *selection-end* nil)
|
||||
text)))
|
||||
#+END_SRC
|
||||
|
||||
** cell-link-at / open-link-at
|
||||
|
||||
#+BEGIN_SRC lisp :tangle ~/.local/share/cl-tty/src/components/input.lisp
|
||||
(defun cell-link-at (fb x y)
|
||||
"Return the link URL at (X Y) in framebuffer FB, or nil."
|
||||
(cl-tty.rendering:fb-cell-link-url fb x y))
|
||||
|
||||
(defun open-link-at (fb x y)
|
||||
"If there is a link URL at (X Y) in FB, open it via xdg-open."
|
||||
(let ((url (cell-link-at fb x y)))
|
||||
(when url
|
||||
#+linux (sb-ext:run-program "xdg-open" (list url) :wait nil)
|
||||
#+darwin (sb-ext:run-program "open" (list url) :wait nil))
|
||||
url))
|
||||
#+END_SRC
|
||||
|
||||
* Tests
|
||||
|
||||
The test suite is tangled to ~../tests/input-tests.lisp~ and covers:
|
||||
@@ -2195,3 +2353,44 @@ The test suite is tangled to ~../tests/input-tests.lisp~ and covers:
|
||||
(print be)))))
|
||||
(is (listp expanded))))
|
||||
#+END_SRC
|
||||
|
||||
;; ─── Mouse tests (merged from cl-tty.mouse) ───────────────────
|
||||
#+BEGIN_SRC lisp :tangle ~/.local/share/cl-tty/tests/input-tests.lisp
|
||||
(def-test mouse-mixin-create ()
|
||||
(let ((m (make-instance 'mouse-mixin)))
|
||||
(is-true (typep m 'mouse-mixin))))
|
||||
|
||||
(def-test mouse-hit-test-point ()
|
||||
"hit-test returns nil when no component has position slots bound"
|
||||
(let ((obj (make-instance 'mouse-mixin)))
|
||||
(is-false (hit-test obj 0 0))
|
||||
(is-false (hit-test obj 100 100))))
|
||||
|
||||
(def-test selection-set-and-get ()
|
||||
(setf *selection* (make-selection :text "hello"))
|
||||
(is (equal "hello" (get-selection))))
|
||||
|
||||
(def-test start-selection-initializes-state ()
|
||||
(start-selection 5 10)
|
||||
(is-true (selection-active-p))
|
||||
(is (equal '(5 . 10) *selection-start*))
|
||||
(is (equal '(5 . 10) *selection-end*))
|
||||
(setf *selection-active* nil *selection-start* nil *selection-end* nil))
|
||||
|
||||
(def-test update-selection-moves-end ()
|
||||
(start-selection 0 0)
|
||||
(update-selection 3 7)
|
||||
(is (equal '(3 . 7) *selection-end*))
|
||||
(setf *selection-active* nil *selection-start* nil *selection-end* nil))
|
||||
|
||||
(def-test finalize-selection-extracts-text ()
|
||||
(let* ((fb-be (cl-tty.rendering:make-framebuffer-backend))
|
||||
(fb (cl-tty.rendering:fb-framebuffer fb-be)))
|
||||
(cl-tty.backend:draw-text fb-be 0 0 "hello" nil nil)
|
||||
(cl-tty.backend:draw-text fb-be 0 1 "world" nil nil)
|
||||
(start-selection 0 0)
|
||||
(update-selection 4 1)
|
||||
(let ((text (finalize-selection fb)))
|
||||
(is (equal "hello
|
||||
world" text)))))
|
||||
#+END_SRC
|
||||
|
||||
Reference in New Issue
Block a user