display.lisp 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. ;;;; Copyright © 2023, Jaidyn Ann <jadedctrl@posteo.at>
  2. ;;;;
  3. ;;;; This program is free software: you can redistribute it and/or
  4. ;;;; modify it under the terms of the GNU General Public License as
  5. ;;;; published by the Free Software Foundation, either version 3 of
  6. ;;;; the License, or (at your option) any later version.
  7. ;;;;
  8. ;;;; This program is distributed in the hope that it will be useful,
  9. ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. ;;;; GNU General Public License for more details.
  12. ;;;;
  13. ;;;; You should have received a copy of the GNU General Public License
  14. ;;;; along with this program. If not, see <https://www.gnu.org/licenses/>.
  15. ;;;; FLORA-SEARCH-AURORA.DISPLAY ✎
  16. ;;;; All display-related curses go here.
  17. (in-package :flora-search-aurora.display)
  18. (defmacro do-for-cell (matrix &body body)
  19. "Given a 2d-array (matrix), execute the body for every cell.
  20. The body has access to 4 variables:
  21. * i/j — The current row/column.
  22. * dimensions — Dimensions of the given matrix.
  23. * cell — The value of the current cell."
  24. `(let* ((dimensions (array-dimensions ,matrix))
  25. (max-i (car dimensions))
  26. (max-j (cadr dimensions))
  27. (i 0) (j 0))
  28. (loop
  29. (let ((cell (ignore-errors (aref ,matrix i j))))
  30. (cond
  31. ((< i max-i)
  32. (cond
  33. ((< j max-j)
  34. ,@body
  35. (incf j))
  36. ((eq j max-j)
  37. (setf j 0)
  38. (incf i))))
  39. ((eq i max-i)
  40. (return)))))))
  41. (defun matrix-delta (a b)
  42. "Given two 2D matrices, return a matrix containing only the cells
  43. that change between A→B (favouring those in B) — all others are nil."
  44. (let ((delta (make-array (array-dimensions a))))
  45. (do-for-cell a
  46. (when (not (eq cell
  47. (aref b i j)))
  48. (setf (aref delta i j)
  49. (aref b i j))))
  50. delta))
  51. (defun print-screen-matrix (matrix)
  52. "Given a matrix of characters, print each element to standard output."
  53. (do-for-cell matrix
  54. (when (characterp cell)
  55. (move-cursor (+ i 1) (+ j 1))
  56. (write-char cell))))
  57. (defun make-screen-matrix ()
  58. "Create a “screen matrix” — that is, a 2D array representing the
  59. 72x20 grid of characters we can print to the terminal."
  60. (make-array '(20 72) :initial-element #\space))
  61. ;;; ———————————————————————————————————
  62. ;;; “Rendering” strings to matrix
  63. ;;; ———————————————————————————————————
  64. (defun render-line (matrix text coords)
  65. "Apply a one-line string to the matrix at the given coordinates."
  66. (let ((dims (array-dimensions matrix))
  67. (x (getf coords :x))
  68. (y (getf coords :y)))
  69. (if (and (stringp text)
  70. (> (length text) 0))
  71. (progn
  72. (ignore-errors (setf (aref matrix y x) (char text 0)))
  73. (render-line matrix (subseq text 1)
  74. (list :x (+ x 1) :y y)))
  75. matrix)))
  76. (defun render-string-verbatim (matrix string coords)
  77. "Apply a STRING to a MATRIX at the precise COORDS, preserving newlines.
  78. No word-wrapping is done, even if the line exceeds the MATRIX’es size!"
  79. (let ((y (- (getf coords :y) 1))
  80. (x (getf coords :x)))
  81. (mapcar (lambda (line) (✎:render-line matrix line (list :x x :y (incf y))))
  82. (str:lines string))))
  83. (defun render-string (matrix text coords &key (char-count (length text)) (width 35))
  84. (let* ((x (getf coords :x))
  85. (y (getf coords :y)))
  86. (render-string-verbatim
  87. matrix
  88. (subseq (…:linewrap-string text width) 0 char-count)
  89. coords)))
  90. (defun render-fill-rectangle (matrix char coords width height)
  91. (render-string-verbatim
  92. matrix
  93. (str:unlines
  94. (loop for i to height
  95. collect (make-string width :initial-element char)))
  96. coords)
  97. matrix)
  98. ;;; ———————————————————————————————————
  99. ;;; Misc. utils
  100. ;;; ———————————————————————————————————
  101. (defun hide-cursor ()
  102. (cl-charms/low-level:curs-set 0))
  103. (defun show-cursor ()
  104. (cl-charms/low-level:curs-set 1))
  105. (defun move-cursor (row column &key (stream *standard-output*))
  106. "Moves cursor to desired position.
  107. Borrowed from https://github.com/gorozhin/chlorophyll/
  108. Copyright © 2022 Mikhail Gorozhin — MIT license"
  109. (format stream "~C[~A;~AH" #\Esc row column))
  110. (defun clear-screen (&key (stream *standard-output*))
  111. "Completely clear the terminal screen."
  112. (move-cursor 0 0 :stream stream)
  113. (format stream "~C[J" #\Esc))