#lang racket/base (require racket/match racket/list racket/string racket/contract/base (for-syntax racket/base syntax/parse) "buffer.rkt" (submod "buffer.rkt" internal)) ;; XXX ensure that argument order is ALWAYS w h r c (define (strict-or a b) (or a b)) (define current-style (make-parameter 'normal)) (define current-fg (make-parameter #f)) (define current-bg (make-parameter #f)) (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)))])) (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))) ;; w : exact-nonnegative-integer? ;; h : exact-nonnegative-integer? ;; ! : okay? (row col char -> void) row col -> bool (struct raart (w h !)) (struct meta-raart raart (m)) (define (without-cursor x) (match-define (raart w h !) x) (meta-raart w h ! '(without-cursor))) (define (draw buf x) (match-define (raart w h !) x) (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))) (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) (define without-cursor? (memq 'without-cursor (if (meta-raart? x) (meta-raart-m x) '()))) (buffer-commit! #:cursor? (not without-cursor) buf)) (define (raart* w h !) (raart w h (λ (okay? d r c) (and (okay? w h r c) (! okay? d r c))))) ;; Core forms (define (with-drawing s f b x) (match-define (raart w h !) x) (raart* w h (λ (okay? d r c) (with-maybe-parameterize ([current-style s] [current-fg f] [current-bg b]) (! okay? d r c))))) (define (blank [w 0] [h 1]) (raart* w h (λ (okay? d r c) #f))) (define (char ch) (when (char-iso-control? ch) (error 'char "Illegal character: ~v" ch)) (raart* 1 1 (λ (okay? d r c) (d r c ch)))) (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))))) (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")) (raart* bw bh (λ (okay? d r c) (strict-or (b! okay? d r c) (f! okay? d (+ r dr) (+ c dc)))))) (define (mask mc mw mr mh x) (match-define (raart xw xh x!) x) (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)))) (define (crop cc cw cr ch x) (define mx (mask cc cw cr ch x)) (match-define (raart mw mh m!) mx) (raart* cw ch (λ (okay? d dr dc) (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)))) (define (if-drawn f x) (match-define (raart w h !) x) (raart* w h (λ (okay? d r c) (define ? (! okay? d r c)) (when ? (f r c w h)) ?))) (define (place-cursor-after x cr cc) (match-define (raart w h !) x) (raart* w h (λ (okay? d r c) (strict-or (! okay? d r c) (d cr cc #f))))) (define (*vappend2 #:reverse? [reverse? #f] y x) (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)) (raart* xw (+ xh yh) (λ (okay? d r c) (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)))))) (define (*happend2 #:reverse? [reverse? #f] y x) (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)) (raart* (+ xw yw) xh (λ (okay? d r c) (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)))))) ;; 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)) (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)) (error 'matte-at "Original (~ax~a@~a,~a) must fit inside matte (~ax~a)" xw xh @c @r mw mh)) (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)) (define (matte aw ah #:halign [ws 'center] #:valign [hs 'center] x) (define w (or aw (raart-w x))) (define h (or ah (raart-h x))) (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 (match ws ['left 0] ['center (floor (/ (- w xw) 2))] ['right (- w xw)]) (match hs ['top 0] ['center (floor (/ (- h xh) 2))] ['bottom (- h xh)]) x)) (define (inset dw dh x) (match-define (raart w h !) x) (matte (+ dw w dw) (+ dh h dh) #:halign 'center #:valign 'center x)) (define (vappend2 y x #:halign [halign #f] #:reverse? [reverse? #f]) (cond [(not halign) (*vappend2 #:reverse? reverse? y x)] [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)) (*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?)) (define (happend2 y x #:valign [valign #f] #:reverse? [reverse? #f]) (cond [(not valign) (*happend2 #:reverse? reverse? y x)] [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)) (*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?)) (define (hline w) (text (make-string w #\─))) (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)) (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) (list-ref l (min i (sub1 (length l))))) (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 (if frames? (happend cell-wall the-cell) the-cell)) (if (and frames? (= col-i last-col)) (happend cell+left cell-wall) cell+left)))) (define include-header? (zero? row-i)) (define row-and-above (if (and frames? include-header?) (vappend header the-row) the-row)) (define include-footer? (= row-i last-row)) (define row-and-below (if frames? (vappend row-and-above (if include-footer? footer inbetween)) row-and-above)) 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)))))) (define (para mw s #:halign [halign 'left]) (para* mw (map text (string-split s " ")) #:halign halign #:gap (text " "))) (define (para* mw rs #:halign [halign 'left] #:gap [gap (blank)]) (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) (cons (happend last-row gap r) rows) (cons r all-rows)))) (define (draw-here r) (draw (make-output-buffer) r)) (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?)] [draw (-> buffer? raart? void?)] [draw-here (-> raart? void?)] [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?)] [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?)] [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?)] [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?] [matte (->* ((or/c exact-nonnegative-integer? false/c) (or/c exact-nonnegative-integer? false/c) raart?) (#: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)) (listof (listof raart?)))] [para (->* (exact-nonnegative-integer? string?) (#:halign halign/c) raart?)] [para* (->* (exact-nonnegative-integer? (listof raart?)) (#:halign halign/c #:gap raart?) raart?)] [if-drawn (-> (-> exact-nonnegative-integer? exact-nonnegative-integer? exact-nonnegative-integer? exact-nonnegative-integer? any) raart? raart?)] [place-cursor-after (-> raart? exact-nonnegative-integer? exact-nonnegative-integer? raart?)] [without-cursor (-> raart? raart?)]) place-at*)