raart/draw.rkt

453 lines
15 KiB
Racket
Raw Normal View History

2018-01-02 00:23:32 +00:00
#lang racket/base
(require racket/match
racket/list
2018-12-12 11:51:17 +00:00
racket/string
2018-01-02 00:23:32 +00:00
racket/contract/base
(for-syntax racket/base
syntax/parse)
2018-08-21 20:28:29 +00:00
"buffer.rkt"
(submod "buffer.rkt" internal))
2018-01-02 00:23:32 +00:00
2018-01-07 12:16:36 +00:00
;; XXX ensure that argument order is ALWAYS w h r c
2018-01-02 16:18:09 +00:00
(define (strict-or a b) (or a b))
2018-01-02 00:23:32 +00:00
(define current-style (make-parameter 'normal))
2018-01-03 03:05:06 +00:00
(define current-fg (make-parameter #f))
(define current-bg (make-parameter #f))
2018-01-03 18:44:57 +00:00
(define-syntax (with-maybe-parameterize stx)
(syntax-parse stx
[(_ () . body) #'(let () . body)]
[(_ ([p:id v:id] . m) . body)
#'(let ([t (λ () (with-maybe-parameterize m . body))])
(if v (parameterize ([p v]) (t)) (t)))]))
2018-01-02 00:23:32 +00:00
2018-01-03 20:50:00 +00:00
(define (rectangle-intersect a-x1 a-y1
a-x2 a-y2
b-x1 b-y1
b-x2 b-y2)
(and (< a-x1 b-x2)
(> a-x2 b-x1)
(< a-y1 b-y2)
(> a-y2 b-y1)))
2018-01-02 00:23:32 +00:00
;; w : exact-nonnegative-integer?
;; h : exact-nonnegative-integer?
2018-01-03 20:50:00 +00:00
;; ! : okay? (row col char -> void) row col -> bool
2018-01-02 00:23:32 +00:00
(struct raart (w h !))
2018-01-02 02:19:28 +00:00
2018-08-17 17:15:07 +00:00
(struct meta-raart raart (m))
(define (without-cursor x)
(match-define (raart w h !) x)
(meta-raart w h ! '(without-cursor)))
2018-01-03 03:05:06 +00:00
(define (draw buf x)
2018-01-02 02:19:28 +00:00
(match-define (raart w h !) x)
2018-01-03 20:50:00 +00:00
(define-values
(max-rows max-cols draw-char!)
(buffer-start! buf h w))
(define (draw-with-params r c ch)
(unless (or (negative? r) (negative? c))
(draw-char! (current-style) (current-fg) (current-bg)
r c ch)))
2018-01-03 20:50:00 +00:00
(define (on-screen? w h r c)
(rectangle-intersect 0 0
max-cols max-rows
c r
(+ c w) (+ r h)))
(! on-screen? draw-with-params 0 0)
2018-08-17 17:15:07 +00:00
(define without-cursor?
(memq 'without-cursor (if (meta-raart? x) (meta-raart-m x) '())))
(buffer-commit! #:cursor? (not without-cursor) buf))
2018-01-02 02:19:28 +00:00
2018-01-03 20:50:00 +00:00
(define (raart* w h !)
(raart w h
(λ (okay? d r c)
(and (okay? w h r c)
(! okay? d r c)))))
2018-01-03 18:44:57 +00:00
;; Core forms
2018-01-02 00:23:32 +00:00
(define (with-drawing s f b x)
(match-define (raart w h !) x)
2018-01-03 20:50:00 +00:00
(raart* w h (λ (okay? d r c)
(with-maybe-parameterize ([current-style s]
[current-fg f]
[current-bg b])
(! okay? d r c)))))
2018-01-02 00:23:32 +00:00
(define (blank [w 0] [h 1])
2018-01-03 20:50:00 +00:00
(raart* w h (λ (okay? d r c) #f)))
2018-01-02 00:23:32 +00:00
(define (char ch)
(when (char-iso-control? ch)
(error 'char "Illegal character: ~v" ch))
2018-01-03 20:50:00 +00:00
(raart* 1 1 (λ (okay? d r c) (d r c ch))))
2018-01-02 00:23:32 +00:00
2018-01-09 18:20:08 +00:00
(define (text s)
(for ([ch (in-string s)]
#:when (char-iso-control? ch))
(error 'text "Illegal character in string: ~v" ch))
(raart* (string-length s) 1
(λ (okay? d r c)
(for ([ch (in-string s)]
[dc (in-naturals)])
(d r (+ c dc) ch)))))
2018-01-02 00:23:32 +00:00
(define (place-at back dr dc front)
(match-define (raart bw bh b!) back)
(match-define (raart fw fh f!) front)
(unless (and (<= fw bw) (<= fh bh))
(error 'place-at "Foreground must fit inside background"))
2018-01-03 20:50:00 +00:00
(raart* bw bh
(λ (okay? d r c)
(strict-or
(b! okay? d r c)
(f! okay? d (+ r dr) (+ c dc))))))
2018-01-03 18:44:57 +00:00
(define (mask mc mw mr mh x)
(match-define (raart xw xh x!) x)
2018-01-03 20:50:00 +00:00
(raart* xw xh
(λ (okay? d r c)
(x!
(λ (w h r c)
(and (okay? w h r c)
(rectangle-intersect mc mr
(+ mc mw) (+ mr mh)
c r
(+ c w) (+ r h))))
d r c))))
2018-01-03 18:44:57 +00:00
(define (crop cc cw cr ch x)
2018-01-03 20:50:00 +00:00
(define mx (mask cc cw cr ch x))
(match-define (raart mw mh m!) mx)
(raart* cw ch
(λ (okay? d dr dc)
2018-01-03 20:50:00 +00:00
(m! (λ (w h r c)
(okay? w h (- r cr) (- c cc)))
(λ (r c ch)
(d (+ dr (- r cr)) (+ dc (- c cc)) ch))
0 0))))
2018-01-03 18:44:57 +00:00
(define (if-drawn f x)
(match-define (raart w h !) x)
2018-01-03 20:50:00 +00:00
(raart* w h (λ (okay? d r c)
(define ? (! okay? d r c))
2018-01-09 18:20:08 +00:00
(when ? (f r c w h))
2018-01-03 20:50:00 +00:00
?)))
2018-01-03 18:44:57 +00:00
(define (place-cursor-after x cr cc)
(match-define (raart w h !) x)
2018-01-03 20:50:00 +00:00
(raart* w h (λ (okay? d r c)
(strict-or (! okay? d r c)
(d cr cc #f)))))
2018-01-03 18:44:57 +00:00
2018-01-11 00:45:46 +00:00
(define (*vappend2 #:reverse? [reverse? #f] y x)
2018-01-03 18:44:57 +00:00
(match-define (raart xw xh x!) x)
(match-define (raart yw yh y!) y)
(unless (= xw yw)
(error '*vappend2 "Widths must be equal: ~e vs ~e" xw yw))
2018-01-03 20:50:00 +00:00
(raart* xw (+ xh yh)
(λ (okay? d r c)
2018-01-11 00:45:46 +00:00
(define (dx) (x! okay? d (+ r 0) c))
(define (dy) (y! okay? d (+ r xh) c))
(if reverse?
(strict-or (dy) (dx))
(strict-or (dx) (dy))))))
2018-01-03 18:44:57 +00:00
2018-01-11 00:48:06 +00:00
(define (*happend2 #:reverse? [reverse? #f] y x)
2018-01-03 18:44:57 +00:00
(match-define (raart xw xh x!) x)
(match-define (raart yw yh y!) y)
(unless (= xh yh)
(error '*happend2 "Heights must be equal: ~e vs ~e" xh yh))
2018-01-03 20:50:00 +00:00
(raart* (+ xw yw) xh
(λ (okay? d r c)
2018-01-11 00:48:06 +00:00
(define (dx) (x! okay? d r (+ c 0)))
(define (dy) (y! okay? d r (+ c xw)))
(if reverse?
(strict-or (dy) (dx))
(strict-or (dx) (dy))))))
2018-01-03 18:44:57 +00:00
;; Library
(define (style s x) (with-drawing s #f #f x))
(define (fg f x) (with-drawing #f f #f x))
(define (bg b x) (with-drawing #f #f b x))
2018-01-02 00:23:32 +00:00
(define-syntax (place-at* stx)
(syntax-parse stx
[(_ b:expr) #'b]
[(_ b:expr [dr:expr dc:expr f:expr] . more:expr)
#'(place-at* (place-at b dr dc f) . more)]))
(define (matte-at mw mh @c @r x)
(match-define (raart xw xh x!) x)
(unless (and (<= (+ xw @c) mw)
(<= (+ xh @r) mh))
2018-01-02 02:19:28 +00:00
(error 'matte-at "Original (~ax~a@~a,~a) must fit inside matte (~ax~a)"
xw xh @c @r mw mh))
2018-01-02 00:23:32 +00:00
(place-at (blank mw mh) @r @c x))
(define (translate dr dc x)
(match-define (raart xw xh x!) x)
(matte-at (+ xw dc) (+ xh dr) dc dr x))
2018-01-18 18:56:18 +00:00
(define (matte aw ah
2018-01-02 00:23:32 +00:00
#:halign [ws 'center]
#:valign [hs 'center]
x)
2018-01-18 18:56:18 +00:00
(define w (or aw (raart-w x)))
(define h (or ah (raart-h x)))
2018-01-02 00:23:32 +00:00
(match-define (raart xw xh x!) x)
(unless (and (<= xw w) (<= xh h))
(error 'matte "Original (~ax~a) must fit inside matte (~ax~a)"
xw xh w h))
(matte-at w h
2018-01-02 02:19:28 +00:00
(match ws
['left 0]
['center (floor (/ (- w xw) 2))]
['right (- w xw)])
(match hs
['top 0]
['center (floor (/ (- h xh) 2))]
['bottom (- h xh)])
2018-01-02 00:23:32 +00:00
x))
(define (inset dw dh x)
(match-define (raart w h !) x)
(matte (+ dw w dw) (+ dh h dh)
#:halign 'center #:valign 'center
x))
2018-01-11 00:45:46 +00:00
(define (vappend2 y x #:halign [halign #f] #:reverse? [reverse? #f])
2018-01-02 16:18:09 +00:00
(cond
2018-01-11 00:45:46 +00:00
[(not halign) (*vappend2 #:reverse? reverse? y x)]
2018-01-02 16:18:09 +00:00
[else
(match-define (raart xw xh x!) x)
(match-define (raart yw yh y!) y)
(define nw (max xw yw))
(define xp (matte nw xh #:halign halign x))
(define yp (matte nw yh #:halign halign y))
2018-01-11 00:45:46 +00:00
(*vappend2 #:reverse? reverse? yp xp)]))
(define (vappend #:halign [halign #f] #:reverse? [reverse? #f] r1 . rs)
(foldl (λ (a d) (vappend2 #:halign halign #:reverse? reverse? a d)) r1 rs))
(define (vappend* #:halign [halign #f] #:reverse? [reverse? #f] rs)
(apply vappend rs #:halign halign #:reverse? reverse?))
2018-01-02 16:18:09 +00:00
2018-01-11 00:48:06 +00:00
(define (happend2 y x #:valign [valign #f] #:reverse? [reverse? #f])
2018-01-02 16:18:09 +00:00
(cond
2018-01-11 00:48:06 +00:00
[(not valign) (*happend2 #:reverse? reverse? y x)]
2018-01-02 16:18:09 +00:00
[else
(match-define (raart xw xh x!) x)
(match-define (raart yw yh y!) y)
(define nh (max xh yh))
(define xp (matte xw nh #:valign valign x))
(define yp (matte yw nh #:valign valign y))
2018-01-11 00:48:06 +00:00
(*happend2 #:reverse? reverse? yp xp)]))
(define (happend #:valign [valign #f] #:reverse? [reverse? #f] r1 . rs)
(foldl (λ (a d) (happend2 #:valign valign #:reverse? reverse? a d)) r1 rs))
(define (happend* #:valign [valign #f] #:reverse? [reverse? #f] rs)
(apply happend rs #:valign valign #:reverse? reverse?))
2018-01-02 16:18:09 +00:00
(define (hline w)
2018-01-09 18:20:08 +00:00
(text (make-string w #\─)))
2018-01-02 16:18:09 +00:00
(define (vline h)
(vappend* (make-list h (char #\│))))
(define (frame #:style [s #f] #:fg [f #f] #:bg [b #f] x)
(match-define (raart w h _) x)
(place-at
(with-drawing s f b
(vappend
(happend (char #\┌) (hline w ) (char #\┐))
(happend (vline h) (blank w h) (vline h))
(happend (char #\└) (hline w ) (char #\┘))))
1 1 x))
2018-01-02 00:23:32 +00:00
(define (table rows
;; XXX add more options to frames
#:frames? [frames? #t]
#:style [s #f] #:fg [f #f] #:bg [b #f]
#:inset-dw [dw 0]
#:inset-dh [dh 0]
#:valign [row-valign 'top]
#:halign [halign 'left])
(define (list-ref* i l)
2018-01-07 12:16:36 +00:00
(list-ref l (min i (sub1 (length l)))))
2018-01-02 00:23:32 +00:00
(define (col-halign-sel i halign)
(match halign
[(? symbol?) halign]
[(? list?) (list-ref* i halign)]))
(define (col-halign col-i)
(col-halign-sel col-i halign))
(define col-ws
(for/list ([i (in-range (length (first rows)))])
(define col (map (λ (r) (list-ref r i)) rows))
(apply max (map raart-w col))))
(define last-col (sub1 (length col-ws)))
(define (make-bar left middle right)
(happend*
(cons
(char left)
(for/list ([col-w (in-list col-ws)]
[col-i (in-naturals)])
(happend (hline (+ dw col-w dw))
(if (= last-col col-i)
(char right)
(char middle)))))))
(define header (make-bar #\┌ #\┬ #\┐))
(define inbetween (make-bar #\├ #\┼ #\┤))
(define footer (make-bar #\└ #\┴ #\┘))
(define last-row (sub1 (length rows)))
(vappend*
(for/list ([row (in-list rows)]
[row-i (in-naturals)])
(define row-h (apply max (map raart-h row)))
(define cell-h (+ dh row-h dh))
(define cell-wall (vline cell-h))
(define the-row
(happend*
(for/list ([col (in-list row)]
[col-w (in-list col-ws)]
[col-i (in-naturals)])
(define cell-w (+ dw col-w dw))
(define the-cell
(matte cell-w #:halign (col-halign col-i)
cell-h #:valign row-valign
(inset dw dh col)))
(define cell+left
2019-03-02 23:44:26 +00:00
(if frames? (happend cell-wall the-cell) the-cell))
(if (and frames? (= col-i last-col))
2018-01-02 00:23:32 +00:00
(happend cell+left cell-wall)
cell+left))))
(define include-header? (zero? row-i))
(define row-and-above
2019-03-02 23:44:26 +00:00
(if (and frames? include-header?) (vappend header the-row) the-row))
2018-01-02 00:23:32 +00:00
(define include-footer? (= row-i last-row))
(define row-and-below
2019-03-02 23:44:26 +00:00
(if frames?
(vappend row-and-above
(if include-footer?
footer
inbetween))
row-and-above))
2018-01-02 00:23:32 +00:00
row-and-below)))
(define (text-rows rows)
(local-require racket/format)
(for/list ([row (in-list rows)])
(for/list ([col (in-list row)])
(if (raart? col) col (text (~a col))))))
2018-12-12 11:51:17 +00:00
(define (para mw s #:halign [halign 'left])
2019-01-20 21:25:15 +00:00
(para* mw (map text (string-split s " ")) #:halign halign #:gap (text " ")))
(define (para* mw rs #:halign [halign 'left] #:gap [gap (blank)])
2018-12-12 11:51:17 +00:00
(for/fold ([all-rows (list (blank))]
#:result
(vappend* #:halign halign (reverse all-rows)))
([r (in-list rs)])
(match-define (cons last-row rows) all-rows)
(if (< (+ (raart-w last-row) (raart-w r)) mw)
2019-01-20 21:25:15 +00:00
(cons (happend last-row gap r) rows)
2018-12-12 11:51:17 +00:00
(cons r all-rows))))
2018-01-16 00:48:30 +00:00
(define (draw-here r)
(draw (make-output-buffer) r))
2018-01-02 00:23:32 +00:00
(define valign/c (or/c 'top 'center 'bottom))
(define halign/c (or/c 'left 'center 'right))
(provide
(contract-out
[raart? (-> any/c boolean?)]
[raart-w (-> raart? integer?)]
[raart-h (-> raart? integer?)]
2018-01-02 00:23:32 +00:00
[draw
2018-01-03 03:05:06 +00:00
(-> buffer? raart?
void?)]
2018-01-16 00:48:30 +00:00
[draw-here (-> raart? void?)]
2018-01-02 00:23:32 +00:00
[style (-> style/c raart? raart?)]
[fg (-> color/c raart? raart?)]
[bg (-> color/c raart? raart?)]
[with-drawing
(-> (or/c style/c #f)
(or/c color/c #f)
(or/c color/c #f)
raart? raart?)]
[blank (->* () (exact-nonnegative-integer? exact-nonnegative-integer?) raart?)]
[char (-> (and/c char? (not/c char-iso-control?)) raart?)]
[text (-> string? raart?)]
[hline (-> exact-nonnegative-integer? raart?)]
[vline (-> exact-nonnegative-integer? raart?)]
2018-01-11 00:45:46 +00:00
[vappend2 (->* (raart? raart?)
(#:halign (or/c halign/c #f) #:reverse? boolean?)
raart?)]
[vappend (->* (raart?)
(#:halign (or/c halign/c #f) #:reverse? boolean?)
#:rest (listof raart?)
raart?)]
[vappend* (->* ((non-empty-listof raart?))
(#:halign (or/c halign/c #f) #:reverse? boolean?)
raart?)]
2018-01-11 00:48:06 +00:00
[happend2 (->* (raart? raart?)
(#:valign (or/c valign/c #f) #:reverse? boolean?)
raart?)]
[happend (->* (raart?)
(#:valign (or/c valign/c #f) #:reverse? boolean?)
#:rest (listof raart?)
raart?)]
[happend* (->* ((non-empty-listof raart?))
(#:valign (or/c valign/c #f) #:reverse? boolean?)
raart?)]
2018-01-02 00:23:32 +00:00
[place-at (-> raart? exact-nonnegative-integer? exact-nonnegative-integer? raart?
raart?)]
[frame (->* (raart?)
(#:style (or/c style/c #f) #:fg (or/c color/c #f) #:bg (or/c color/c #f))
raart?)]
[matte-at (-> exact-nonnegative-integer? exact-nonnegative-integer?
exact-nonnegative-integer? exact-nonnegative-integer?
raart?
raart?)]
[translate (-> exact-nonnegative-integer? exact-nonnegative-integer?
raart? raart?)]
[halign/c contract?]
[valign/c contract?]
2018-01-18 18:56:18 +00:00
[matte (->* ((or/c exact-nonnegative-integer? false/c)
(or/c exact-nonnegative-integer? false/c)
raart?)
2018-01-02 00:23:32 +00:00
(#:halign halign/c #:valign valign/c)
raart?)]
[inset (-> exact-nonnegative-integer? exact-nonnegative-integer? raart? raart?)]
[mask (-> exact-nonnegative-integer? exact-nonnegative-integer?
exact-nonnegative-integer? exact-nonnegative-integer?
raart? raart?)]
[crop (-> exact-nonnegative-integer? exact-nonnegative-integer?
exact-nonnegative-integer? exact-nonnegative-integer?
raart? raart?)]
[table (->* ((listof (listof raart?)))
(#:frames? boolean?
#:style (or/c style/c #f)
#:fg (or/c color/c #f)
#:bg (or/c color/c #f)
#:inset-dw exact-nonnegative-integer?
#:inset-dh exact-nonnegative-integer?
#:valign valign/c
#:halign (or/c halign/c (list*of halign/c (or/c halign/c '()))))
raart?)]
[text-rows (-> (listof (listof any/c))
2018-01-02 16:18:09 +00:00
(listof (listof raart?)))]
2018-12-12 11:51:17 +00:00
[para (->* (exact-nonnegative-integer? string?)
(#:halign halign/c)
raart?)]
[para* (->* (exact-nonnegative-integer? (listof raart?))
2019-01-20 21:25:15 +00:00
(#:halign halign/c #:gap raart?)
2018-12-12 11:51:17 +00:00
raart?)]
2018-01-09 18:20:08 +00:00
[if-drawn (-> (-> exact-nonnegative-integer? exact-nonnegative-integer?
exact-nonnegative-integer? exact-nonnegative-integer?
any)
raart? raart?)]
2018-01-02 20:45:52 +00:00
[place-cursor-after
2018-01-03 03:33:30 +00:00
(-> raart? exact-nonnegative-integer? exact-nonnegative-integer?
2018-08-17 17:15:07 +00:00
raart?)]
[without-cursor
(-> raart? raart?)])
2018-01-02 00:23:32 +00:00
place-at*)