Fix dialogue linebreaks; surround text with spaces

… finally! It took so dang long to get
linebreaking and positioning working in a
satisfactory way, but it’s done, dammit!
And no-one can break it, never again!

Also, surrounding the text with spaces makes it
a bit more legible. Nice touch, right? =w=
This commit is contained in:
Jaidyn Ann 2023-07-02 11:54:16 -05:00
parent 845c2540a9
commit 1d30f66df4
3 changed files with 64 additions and 45 deletions

View File

@ -220,8 +220,8 @@ Returns the state for use with STATE-LOOP, pay attention!"
return the parameters of a text-box that can optimally fit the given TEXT in the return the parameters of a text-box that can optimally fit the given TEXT in the
direction specified relative to the focal point. If a legible position cant be direction specified relative to the focal point. If a legible position cant be
found, just give up! Return nil. found, just give up! Return nil.
Otherwise, return a list list with the coordinates, max column, and max row for Otherwise, return a list list with the coordinates, textbox width, and textbox
use with RENDER-STRING." height all parameters for use with RENDER-STRING & co."
(let* ((text-x-margin (if rightp (let* ((text-x-margin (if rightp
(+ (getf coords :x) 3) (+ (getf coords :x) 3)
0)) 0))
@ -231,30 +231,35 @@ use with RENDER-STRING."
(- (getf coords :x) 3)))) (- (getf coords :x) 3))))
(lines (ignore-errors (str:lines (:linewrap-string text text-width)))) (lines (ignore-errors (str:lines (:linewrap-string text text-width))))
(text-height (length lines))) (text-height (length lines)))
(format *error-output* "HEIGHT: ~A WIDTH ~A LINES: ~A~%" text-height text-width lines) (format *error-output* "HORIZ COORD: ~A HEIGHT: ~A WIDTH ~A LINES:~%~S~%" coords text-height text-width lines)
;; When this layout is valid… ;; When this layout is valid…
(when (and lines (when (and lines
(>= height text-height) ;; If the textll fit on screen (>= height text-height) ;; If the textll fit on screen
(> text-width 10)) ;; If the text is wide enough to be legible (> text-width 10)) ;; If the text is wide enough to be legible
(let ((y (:at-least 0 (- (getf coords :y) (let* ((y (:at-least 0 (- (getf coords :y)
(if (eq (length lines) 1) ;; Align toward the speakers face (if (eq text-height 1) ;; Align toward the speakers face
1 0) 1 0)
(floor (/ (length lines) 2))))) (floor (/ text-height 2)))))
(x (if (and (not rightp) (x (if (and (not rightp)
(eq (length lines) 1)) (eq (length lines) 1))
(- text-width (length text)) (- text-width (length text))
text-x-margin))) text-x-margin))
(list (list :x x :y y) ;; Coords (y-margin (if (> (+ y (length lines)) height) ;; How many lines are off-screen
text-width ;;(+ x text-width) ;; Max column (- (+ y (length lines)) height)
height))))) ;; Max row 0)))
(list
;; Coords of text-boxes top-left corner
(list :x x :y (- y y-margin))
text-width ;; Width of text-box
text-height))))) ;; Height of text-box
(defun optimal-text-placement-vertically (text coords &key (downp nil) (width 72) (height 20)) (defun optimal-text-placement-vertically (text coords &key (downp nil) (width 72) (height 20))
"Given a vertical direction (DOWNP defined or nil) and a focal point COORDS,) "Given a vertical direction (DOWNP defined or nil) and a focal point COORDS,)
return the parameters of a text-box that can optimally fit the given TEXT in the return the parameters of a text-box that can optimally fit the given TEXT in the
direction specified relative to the focal point. Return nil if no such placement direction specified relative to the focal point. Return nil if no such placement
is found, otherwise return a list of the coordinates, max-column, and max-row is found, otherwise return a list of the coordinates, textbox width, and textbox
(for use with RENDER-STRING)." height (for use as parameters with RENDER-STRING et al.)."
(let* ((text-y-margin (if downp (let* ((text-y-margin (if downp
(+ (getf coords :y) 2) (+ (getf coords :y) 2)
(- (getf coords :y) 2))) (- (getf coords :y) 2)))
@ -263,10 +268,10 @@ is found, otherwise return a list of the coordinates, max-column, and max-row
(- text-y-margin 1))) (- text-y-margin 1)))
(text-width (floor (* width 3/5))) ;; Too wides illegible! So ⅗-screen. (text-width (floor (* width 3/5))) ;; Too wides illegible! So ⅗-screen.
(lines (ignore-errors (str:lines (:linewrap-string text text-width))))) (lines (ignore-errors (str:lines (:linewrap-string text text-width)))))
(format *error-output* "HEIGHT: ~A WIDTH ~A LINES: ~A~%" text-height text-width lines) (format *error-output* "VERT HEIGHT: ~A WIDTH ~A LINES: ~A~%" text-height text-width lines)
;; When the text can be printed with this layout… ;; When the text can be printed with this layout…
(when (and lines (>= text-height (length lines))) (when (and lines (>= text-height (length lines)))
(let ((y (:at-least (let* ((y (:at-least
0 0
(if downp (if downp
text-y-margin text-y-margin
@ -276,10 +281,15 @@ is found, otherwise return a list of the coordinates, max-column, and max-row
(- (getf coords :x) (- (getf coords :x)
(if (eq (length lines) 1) (if (eq (length lines) 1)
(floor (/ (length (car lines)) 2)) (floor (/ (length (car lines)) 2))
(floor (/ text-width 2))))))) (floor (/ text-width 2))))))
(list (list :x x :y y) ;; Coords (x-margin (if (> (+ x text-width) width)
text-width ;;(+ x text-width) ;; Max column (- (+ x text-width) width)
(+ y text-height)))))) ;; Max row 0)))
(list
;; Coords of text-boxes top-left corner
(list :x (- x x-margin) :y y)
text-width ;; Width of the text-box
(length lines)))))) ;; Height of text-box
(defun optimal-speech-layout (map dialogue &key (width 72) (height 20)) (defun optimal-speech-layout (map dialogue &key (width 72) (height 20))
@ -305,22 +315,30 @@ and max-row; for use with RENDER-STRING. Like so:
(optimal-text-placement-vertically text coords :width width :height height (optimal-text-placement-vertically text coords :width width :height height
:downp (not playerp))))) :downp (not playerp)))))
(defun ensure-dialogue-layout (map dialogue-list)
"Given a DIALOGUE-LIST, ensure that the FIRST line of dialogue has a :layout
property that is, a property detailing the optimal width and coordinates for
its display."
(when (and (getf (car dialogue-list) :text)
(not (getf (car dialogue-list) :layout)))
(setf (getf (car dialogue-list) :layout)
(optimal-speech-layout map (car dialogue-list)))))
(defun render-dialogue-block (matrix map dialogue)
(defun render-dialogue-block (matrix dialogue)
"Render a bit of DIALOGUE to the MATRIX, in an intelligent fashion; that is, "Render a bit of DIALOGUE to the MATRIX, in an intelligent fashion; that is,
make it pretty, dang it! >O< make it pretty, dang it! >O<
:..o()o..:" :..o()o..:"
(let* ((progress (getf dialogue :progress)) (let* ((progress (getf dialogue :progress))
(text (getf dialogue :text)) (text (getf dialogue :text))
(optimal-layout (when text (optimal-speech-layout map dialogue))) (optimal-layout (getf dialogue :layout))
(coords (car optimal-layout))) (coords (car optimal-layout)))
(when (and text optimal-layout) (when (and text optimal-layout)
(format *error-output* "~A~%" optimal-layout) (:render-fill-rectangle matrix #\space
;; (✎:render-fill-rectangle matrix #\space (list :x (- (getf coords :x) 1)
;; (list :x (- (getf coords :x) 1) :y (- (getf coords :y) 1))
;; :y (- (getf coords :y) 1) (+ (second optimal-layout) 2) ;; Width
;; (- (second optimal-layout) (getf coords :x) -2) (+ (third optimal-layout) 1)) ;; Height
;; (- (third optimal-layout) (getf coords :y) -2))
(:render-string (:render-string
matrix text (first optimal-layout) matrix text (first optimal-layout)
:width (second optimal-layout) :width (second optimal-layout)
@ -332,7 +350,8 @@ make it pretty, dang it! >O<
Helper function for DIALOGUE-STATE." Helper function for DIALOGUE-STATE."
(when (getf (car dialogue-list) :text) (when (getf (car dialogue-list) :text)
(:show-cursor) (:show-cursor)
(render-dialogue-block matrix map (car dialogue-list)))) (ensure-dialogue-layout map dialogue-list)
(render-dialogue-block matrix (car dialogue-list))))

View File

@ -102,7 +102,7 @@ No word-wrapping is done, even if the line exceeds the MATRIXes size!"
(y (getf coords :y))) (y (getf coords :y)))
(render-string-verbatim (render-string-verbatim
matrix matrix
(:linewrap-string (subseq text 0 char-count) width) (subseq (:linewrap-string text width) 0 char-count)
coords))) coords)))

View File

@ -61,7 +61,8 @@ side (either :CENTER, :LEFT, or :RIGHT)."
(defun linewrap-string (string width) (defun linewrap-string (string width)
"Break a STRING into several lines, each one no larger than WIDTH. Uses "Break a STRING into several lines, each one no larger than WIDTH. Uses
newlines and hypens (to break long words) as necessary." newlines and hypens (to break long words) as necessary."
(let ((spaces (append '(0) (search-all " " string))) (let* ((string (str:replace-all (string #\newline) " " string))
(spaces (append '(0) (search-all " " string)))
(index width)) (index width))
(loop while (< index (length string)) (loop while (< index (length string))
do (let ((closest-space (car (closest-below index spaces))) do (let ((closest-space (car (closest-below index spaces)))
@ -71,8 +72,7 @@ newlines and hypens (to break long words) as necessary."
;; Break up long words with a hyphen ;; Break up long words with a hyphen
(return (return
(linewrap-string (linewrap-string
(str:insert "- " (- index 1) (str:insert "- " (- index 1) string)
(str:replace-all (string #\newline) " " string))
width)) width))
;; Replace eligible spaces with newlines uwu ;; Replace eligible spaces with newlines uwu
(progn (progn