#lang racket/base (require framework racket/gui/base racket/class racket/math racket/runtime-path racket/serialize data/interval-map setup/dirs images/icons/misc "../rectangle-intersect.rkt" string-constants framework/private/logging-timer) (provide docs-text-mixin docs-editor-canvas-mixin syncheck:add-docs-range syncheck:reset-docs-im syncheck:update-blue-boxes) (define sc-f2-to-lock (string-constant sc-f2-to-un/lock)) (define sc-read-more... (string-constant sc-read-more...)) (preferences:set-default 'drracket:syncheck:contracts-locked? #f boolean?) (define corner-radius 48) (define blue-box-color (make-object color% #xE8 #xE8 #xFF)) (define var-color (make-object color% #x26 #x26 #x80)) (define blue-box-margin 5) ;; files->tag->offset : (listof (list file-path int hash[tag -o> (cons int int)])) (define (fetch-files->tag->offset) (filter values (for*/list ([doc-search-dir (in-list (get-doc-search-dirs))] [doc-dir-name (in-list (if (directory-exists? doc-search-dir) (directory-list doc-search-dir) '()))]) (define x (build-path doc-search-dir doc-dir-name "blueboxes.rktd")) (and (file-exists? x) (call-with-input-file x (λ (port) (port-count-lines! port) (define first-line (read-line port)) (define pos (file-position port)) (list x (+ (string->number first-line) pos) (deserialize (read port))))))))) (define files->tag->offset #f) (define (fetch-strs tag) (define plain-strs (fetch-strs-for-single-tag tag)) (cond [(and plain-strs (pair? tag) (eq? (car tag) 'def)) (define constructor-strs (fetch-strs-for-single-tag (cons 'construtor (cdr tag)))) (if constructor-strs (append plain-strs '("") ;; cdr drops the "white label" line (constructor, presumably) (cdr constructor-strs)) plain-strs)] [else plain-strs])) (define (fetch-strs-for-single-tag tag) (unless files->tag->offset (set! files->tag->offset (fetch-files->tag->offset))) (for/or ([ent (in-list files->tag->offset)]) (define offset+lens (hash-ref (list-ref ent 2) tag #f)) (cond [offset+lens (apply append (for/list ([offset+len (in-list offset+lens)]) (define fn (list-ref ent 0)) (define offset (list-ref ent 1)) (call-with-input-file fn (λ (port) (port-count-lines! port) (file-position port (+ (car offset+len) offset)) (for/list ([i (in-range (cdr offset+len))]) (read-line port))))))] [else #f]))) ;; get-label-font : style-list -> number ;; the multiplication by 1.5 is suspicious, but it makes things ;; look right under mac os x (with fairly standard font settings) (define (get-label-font sl) (define style (send sl find-named-style "Standard")) (define font-size (if style (round (* #e1.5 (send (send style get-font) get-point-size))) 18)) (send the-font-list find-or-create-font font-size 'swiss 'normal 'bold)) (define (get-read-more-font sl) (define style (send sl find-named-style "Standard")) (define font-size (if style (send (send style get-font) get-point-size) 12)) (send the-font-list find-or-create-font font-size 'roman 'italic 'normal)) (define (get-read-more-underline-font sl) (define style (send sl find-named-style "Standard")) (define font-size (if style (send (send style get-font) get-point-size) 12)) (send the-font-list find-or-create-font font-size 'roman 'italic 'normal #t)) (define-local-member-name get-show-docs? get-current-strs syncheck:reset-docs-im syncheck:add-docs-range syncheck:update-blue-boxes) (define docs-ec-clipping-region #f) (define docs-ec-last-cw #f) (define docs-ec-last-ch #f) (define docs-ec-last-hi #f) (define docs-ec-last-vi #f) (define (docs-editor-canvas-mixin %) (class % (inherit get-dc get-client-size get-editor horizontal-inset vertical-inset) (define/override (on-paint) (define e (get-editor)) (when e (define dc (get-dc)) (define-values (cw ch) (get-client-size)) (define the-strs (send e get-current-strs)) (cond [(and (send e get-show-docs?) the-strs) (define hi (horizontal-inset)) (define vi (vertical-inset)) (define font (send dc get-font)) (define pen (send dc get-pen)) (define brush (send dc get-brush)) (define smoothing (send dc get-smoothing)) (define std (send (send e get-style-list) find-named-style "Standard")) (when std (send dc set-font (send std get-font))) (define-values (box-width box-height label-overlap?) (get-blue-box-size dc (send e get-style-list) the-strs)) (send dc set-brush blue-box-color 'solid) (send dc set-pen "black" 1 'transparent) (send dc draw-rectangle (- cw box-width) 0 box-width box-height) (send dc set-smoothing 'aligned) ;; most of the time (unless the user is ;; resizing the window) we don't really ;; need a new clipping region, so just ;; make a cache of size 1, keyed by the ;; client width, height, and vertical and ;; horizontal insets. (unless (and (equal? cw docs-ec-last-cw) (equal? ch docs-ec-last-ch) (equal? hi docs-ec-last-hi) (equal? vi docs-ec-last-vi)) (set! docs-ec-last-cw cw) (set! docs-ec-last-ch ch) (set! docs-ec-last-hi hi) (set! docs-ec-last-vi vi) (define rgn1 (new region%)) (define rgn2 (new region%)) (define rgn3 (new region%)) (define rgn4 (new region%)) (send rgn1 set-rectangle 0 0 cw vi) (send rgn2 set-rectangle (- cw hi) 0 hi ch) (send rgn3 set-rectangle 0 (- ch vi) cw vi) (send rgn4 set-rectangle 0 0 hi ch) (send rgn1 union rgn2) (send rgn1 union rgn3) (send rgn1 union rgn4) (set! docs-ec-clipping-region rgn1)) (define old-region (send dc get-clipping-region)) (send dc set-clipping-region docs-ec-clipping-region) (draw-blue-box-shadow dc (- cw box-width) 0 box-width box-height) (send dc set-clipping-region old-region) (send dc set-pen pen) (send dc set-brush brush) (send dc set-font font) (send dc set-smoothing smoothing)] [the-strs (draw-closed dc cw 0)])) (super on-paint)) (super-new))) (define (docs-text-mixin %) (class % (inherit get-canvas get-admin get-style-list dc-location-to-editor-location get-dc get-start-position get-end-position begin-edit-sequence end-edit-sequence invalidate-bitmap-cache) (define locked? (preferences:get 'drracket:syncheck:contracts-locked?)) (define mouse-in-blue-box? #f) (define mouse-in-lock-icon? #f) (define mouse-in-read-more? #f) (define the-strs #f) (define the-strs-id-start #f) (define the-strs-id-end #f) (define/public (get-current-strs) the-strs) (define visit-docs-url void) (define/public (get-show-docs?) (and the-strs (or locked? mouse-in-blue-box?))) (define/public (toggle-syncheck-docs) (begin-edit-sequence) (invalidate-blue-box-region) (cond [locked? (set! mouse-in-blue-box? #f)] [else (update-the-strs)]) (update-locked (not locked?)) (when last-evt-seen (update-mouse-in-blue-box (in-blue-box? last-evt-seen)) (define-values (is-in-lock? is-in-read-more?) (in-lock/in-read-more? last-evt-seen)) (update-mouse-in-lock-icon/read-more? is-in-lock? is-in-read-more?)) (invalidate-blue-box-region) (end-edit-sequence)) (define/public (update-mouse-in-blue-box b) (unless (equal? b mouse-in-blue-box?) (begin-edit-sequence) (invalidate-blue-box-region) (set! mouse-in-blue-box? b) (invalidate-blue-box-region) (end-edit-sequence))) (define/public (update-locked b) (preferences:set 'drracket:syncheck:contracts-locked? b) (unless (equal? b locked?) (begin-edit-sequence) (invalidate-blue-box-region) (set! locked? b) (invalidate-blue-box-region) (end-edit-sequence))) (define/public (update-mouse-in-lock-icon/read-more? lk? rm?) (unless (and (equal? lk? mouse-in-lock-icon?) (equal? rm? mouse-in-read-more?)) (begin-edit-sequence) (invalidate-blue-box-region) (set! mouse-in-lock-icon? lk?) (set! mouse-in-read-more? rm?) (invalidate-blue-box-region) (end-edit-sequence))) (define/private (invalidate-blue-box-region) (define c (get-canvas)) (when c (send c refresh)) ;; the code below is what I'd like to do here, ;; but this doesn't redraw the margin (the part ;; of the editor-canvas that is always outside ;; of th editor) and it doesn't seem possible to ;; trigger a redraw of that part without also ;; triggering a redraw of the entire editor ;; so we just do that instead (above) #; (begin (define-values (br bt _1 _2) (get-box-upper-right-and-lock-coordinates)) (when (and bt br) (cond [(get-show-docs?) (define-values (box-width box-height label-overlap?) (get-blue-box-size (get-dc) (get-style-list) the-strs)) (define x (- br box-width shadow-size)) (invalidate-bitmap-cache (max x 0) (max bt 0) (+ box-width shadow-size) (+ box-height shadow-size))] [the-strs (define size (+ corner-radius shadow-size)) (invalidate-bitmap-cache (max 0 (- br size)) (max 0 bt) size size)])))) (define bx (box 0)) (define by (box 0)) (define bw (box 0)) (define bh (box 0)) (define docs-im #f) (define/public (syncheck:reset-docs-im) (set! docs-im #f)) (define (get/start-docs-im) (cond [docs-im docs-im] [else (set! docs-im (make-interval-map)) docs-im])) (define/public (syncheck:add-docs-range start end tag visit-docs-url) ;; the +1 to end is effectively assuming that there ;; are no abutting identifiers with documentation (define rng (list start (+ end 1) tag visit-docs-url)) (interval-map-set! (get/start-docs-im) start (+ end 1) rng)) (define/override (on-paint before? dc left top right bottom dx dy draw-caret) (super on-paint before? dc left top right bottom dx dy draw-caret) (define-values (br bt bmp-x bmp-y) (get-box-upper-right-and-lock-coordinates)) (when (and (not before?) br bt) (define canvas (get-canvas)) (define hi (send canvas horizontal-inset)) (define vi (send canvas vertical-inset)) (cond [(get-show-docs?) (draw-open dc dx dy (get-style-list) the-strs br bt bmp-x bmp-y mouse-in-blue-box? mouse-in-lock-icon? mouse-in-read-more? locked? left top right bottom)] [the-strs (draw-closed dc (+ dx br) (+ dy bt))]))) ;; get-box-upper-right-and-lock-coordinates ;; : (or/c (-> (values #f #f #f #f) ;; (-> (values num num #f #f)) ;; (-> (values num num num num))) (define/private (get-box-upper-right-and-lock-coordinates) (define admin (get-admin)) (define canvas (get-canvas)) (cond [(and admin canvas) (send admin get-view bx by bw bh) (define hi (send canvas horizontal-inset)) (define vi (send canvas vertical-inset)) (define view-left (unbox bx)) (define view-top (unbox by)) (define br (+ view-left (unbox bw) hi)) (define bt (+ view-top (- vi))) (cond [the-strs (define-values (box-width box-height label-overlap?) (get-blue-box-size (get-dc) (get-style-list) the-strs)) (define bmp-bluebox-x (+ br (- blue-box-margin box-width))) (define bmp-bluebox-y (+ bt (- box-height blue-box-margin lock-height))) (define view-bottom (+ view-top (unbox bh))) (define bmp-view-x (+ view-left blue-box-margin)) (define bmp-view-y (- view-bottom blue-box-margin lock-height)) (cond [(and (bmp-bluebox-x . <= . bmp-view-x) (bmp-bluebox-y . >= . bmp-view-y)) (values br bt bmp-view-x bmp-view-y)] [(bmp-bluebox-y . >= . bmp-view-y) (values br bt bmp-bluebox-x bmp-view-y)] [(bmp-bluebox-x . <= . bmp-view-x) (values br bt bmp-view-x bmp-bluebox-y)] [else (values br bt bmp-bluebox-x bmp-bluebox-y)])] [else (values br bt #f #f)])] [else (values #f #f #f #f)])) (define/override (adjust-cursor evt) (cond [(in-blue-box? evt) arrow-cursor] [else (super adjust-cursor evt)])) (define last-evt-seen #f) (define/override (on-event evt) (set! last-evt-seen evt) (update-the-strs/maybe-invalidate void void) (update-mouse-in-blue-box (in-blue-box? evt)) (define-values (is-in-lock? is-in-read-more?) (in-lock/in-read-more? last-evt-seen)) (update-mouse-in-lock-icon/read-more? is-in-lock? is-in-read-more?) (cond [mouse-in-blue-box? (cond [(and mouse-in-lock-icon? (send evt button-up? 'left)) (update-locked (not locked?))] [(and mouse-in-read-more? (send evt button-up? 'left)) (visit-docs-url)])] [else (super on-event evt)])) (define timer (new logging-timer% [notify-callback (λ () (set! timer-running? #f) (update-the-strs))])) (define timer-running? #f) (define/augment (after-set-position) (inner (void) after-set-position) (when (or locked? mouse-in-blue-box? (not the-strs)) (unless timer-running? (set! timer-running? #t) (send timer start 300 #t)))) (define/public (syncheck:update-blue-boxes) (update-the-strs)) (define/private (update-the-strs) (update-the-strs/maybe-invalidate (λ () (begin-edit-sequence) (invalidate-blue-box-region)) (λ () (when last-evt-seen (update-mouse-in-blue-box (in-blue-box? last-evt-seen)) (define-values (is-in-lock? is-in-read-more?) (in-lock/in-read-more? last-evt-seen)) (update-mouse-in-lock-icon/read-more? is-in-lock? is-in-read-more?)) (invalidate-blue-box-region) (end-edit-sequence)))) ;; update-the-strs/maybe-invalidate : (-> void) (-> void) -> boolean ;; returns #t if something changed (and thus invalidation should happen) (define/private (update-the-strs/maybe-invalidate before after) (define sp (get-start-position)) (when (= sp (get-end-position)) (define tag+rng (interval-map-ref (get/start-docs-im) sp #f)) (when tag+rng (define ir-start (list-ref tag+rng 0)) (define ir-end (list-ref tag+rng 1)) (define tag (list-ref tag+rng 2)) (define new-visit-docs-url (list-ref tag+rng 3)) (define new-strs (fetch-strs tag)) (when new-strs (before) (set! the-strs new-strs) (set! the-strs-id-start ir-start) (set! the-strs-id-end ir-end) (set! visit-docs-url new-visit-docs-url) (after))))) (define/augment (on-insert where len) (when docs-im (clear-im-range where len) (interval-map-expand! docs-im where (+ where len)) (possibly-clobber-strs where len #f) (when the-strs-id-start (when (<= where the-strs-id-start) (set! the-strs-id-start (+ the-strs-id-start len)) (set! the-strs-id-end (+ the-strs-id-end len))))) (inner (void) on-insert where len)) (define/augment (on-delete where len) (when docs-im (clear-im-range where len) (interval-map-contract! docs-im where (+ where len)) (possibly-clobber-strs where len #t) (when the-strs-id-start (when (<= where the-strs-id-start) (set! the-strs-id-start (- the-strs-id-start len)) (set! the-strs-id-end (- the-strs-id-end len))))) (inner (void) on-delete where len)) (define/private (possibly-clobber-strs where len delete?) (when (or (not the-strs-id-start) (not the-strs-id-end) (and (<= the-strs-id-start where) (< where the-strs-id-end)) (and delete? (<= the-strs-id-start (+ where len) the-strs-id-end))) (when the-strs (begin-edit-sequence) (invalidate-blue-box-region) (set! the-strs #f) (set! the-strs-id-start #f) (set! the-strs-id-end #f) (invalidate-blue-box-region) (end-edit-sequence)))) (define/private (clear-im-range where len) (when docs-im (for ([x (in-range len)]) (define tag+rng (interval-map-ref docs-im (+ where x) #f)) (when tag+rng (interval-map-remove! docs-im (list-ref tag+rng 0) (list-ref tag+rng 1)))))) (define/private (in-blue-box? evt) (cond [(send evt leaving?) #f] [else (define dc-x (send evt get-x)) (define dc-y (send evt get-y)) (define-values (br bt bmp-x bmp-y) (get-box-upper-right-and-lock-coordinates)) (cond [(and br bt the-strs) (define-values (ex ey) (dc-location-to-editor-location dc-x dc-y)) (define-values (bw bh _) (if (get-show-docs?) (get-blue-box-size (get-dc) (get-style-list) the-strs) (values corner-radius corner-radius #f))) (and (<= (- br bw) ex br) (<= bt ey (+ bt bh)))] [else #f])])) (define/private (in-lock/in-read-more? evt) (cond [(send evt leaving?) (values #f #f)] [(and (get-show-docs?) (get-dc)) (define dc-x (send evt get-x)) (define dc-y (send evt get-y)) (define-values (br bt bmp-x bmp-y) (get-box-upper-right-and-lock-coordinates)) (define-values (read-more-w read-more-h read-more-d read-more-a) (send (get-dc) get-text-extent sc-read-more... (get-read-more-underline-font (get-style-list)))) (cond [(and br bt bmp-x bmp-y) (define-values (ex ey) (dc-location-to-editor-location dc-x dc-y)) (values (and (<= bmp-x ex (+ bmp-x lock-width)) (<= bmp-y ey (+ bmp-y lock-height))) (and (<= (- br read-more-w read-more-gap) ex (- br read-more-gap)) (<= (+ bmp-y lock-height (- read-more-h)) ey (+ bmp-y lock-height))))] [else (values #f #f)])] [else (values #f #f)])) (super-new))) (define arrow-cursor (make-object cursor% 'arrow)) (define (make-arrow-path init-angle) (struct turtle (x y θ)) (define (move-then-turn p dist α) (define x (turtle-x p)) (define y (turtle-y p)) (define θ (turtle-θ p)) (turtle (+ x (* dist (cos θ))) (+ y (* dist (sin θ))) (+ α θ))) (define arrow-tail-length 12) (define arrow-tail-width 8) (define arrow-head-side-len 24) ;; start at the center of the end of the arrow (define p1 (turtle 0 0 init-angle)) (define p2 (move-then-turn p1 (/ arrow-tail-width 2) (/ pi 2))) (define p3 (move-then-turn p2 arrow-tail-length (- (/ pi 2)))) (define p4 (move-then-turn p3 (- (/ arrow-head-side-len 2) (/ arrow-tail-width 2)) (* pi 2/3))) (define p5 (move-then-turn p4 arrow-head-side-len (* pi 2/3))) (define p6 (move-then-turn p5 arrow-head-side-len (* pi 2/3))) (define p7 (move-then-turn p6 (- (/ arrow-head-side-len 2) (/ arrow-tail-width 2)) (- (/ pi 2)))) (define p8 (move-then-turn p7 arrow-tail-length (/ pi 2))) (define p9 (move-then-turn p8 (/ arrow-tail-width 2) 0)) (define dc-path (new dc-path%)) (send dc-path move-to (turtle-x p1) (turtle-y p1)) (for ([p (in-list (list p2 p3 p4 p5 p6 p7 p8 p9))]) (send dc-path line-to (turtle-x p) (turtle-y p))) (send dc-path close) dc-path) (define open-arrow-path (make-arrow-path (* pi 1/4))) (define lock-closed (lock-icon #f)) (define lock-open (lock-icon #t)) (define lock-width (max (send lock-closed get-width) (send lock-open get-width))) (define lock-height (max (send lock-closed get-height) (send lock-open get-height))) (define (draw-closed dc x y) (define smoothing (send dc get-smoothing)) (define pen (send dc get-pen)) (define brush (send dc get-brush)) (send dc set-pen "black" 1 'transparent) (send dc set-smoothing 'aligned) (define-values (ox oy) (send dc get-origin)) (send dc set-origin (+ ox x (- corner-radius) (- shadow-size)) (+ oy y (- corner-radius) (- shadow-size))) (send dc set-brush closed-radial-gradient-brush) (send dc draw-arc 0 0 (* (+ corner-radius shadow-size) 2) (* (+ corner-radius shadow-size) 2) pi (* pi #e1.5)) (send dc set-origin ox oy) (send dc set-brush blue-box-color 'solid) (send dc draw-arc (- x corner-radius 1) (- y corner-radius 1) (+ (* corner-radius 2) 2) (+ (* corner-radius 2) 2) pi (* pi #e1.5)) (send dc set-brush "white" 'solid) (send dc draw-path open-arrow-path (- x 6) (+ y 6)) (send dc set-smoothing smoothing) (send dc set-brush brush) (send dc set-pen pen)) (define fade-out-color (make-object color% 255 255 255 .8)) (define (draw-open dc dx dy sl strs br bt bmp-x bmp-y show-lock? mouse-in-lock-icon? mouse-in-read-more? locked? left top right bottom) (define-values (box-width box-height label-overlap?) (get-blue-box-size dc sl strs)) (when (let* ([wd-left (- br box-width shadow-size)] [wd-top bt] [wd-right (+ wd-left box-width shadow-size)] [wd-bottom (+ wd-top box-height shadow-size)]) ;; wd for "will draw" (rectangles-intersect? wd-left wd-top wd-right wd-bottom left top right bottom)) (define dx+br (+ dx br)) (define dy+bt (+ dy bt)) (define pen (send dc get-pen)) (define brush (send dc get-brush)) (define smoothing (send dc get-smoothing)) (define text-foreground (send dc get-text-foreground)) (define font (send dc get-font)) (define std (send sl find-named-style "Standard")) (when std (send dc set-font (send std get-font))) (send dc set-smoothing 'aligned) (send dc set-brush blue-box-color 'solid) (send dc set-pen "black" 1 'transparent) (send dc draw-rectangle (+ dx (- br box-width)) (+ dy bt) box-width box-height) (send dc set-font (if mouse-in-read-more? (get-read-more-underline-font sl) (get-read-more-font sl))) (define-values (read-more-w read-more-h read-more-d read-more-a) (send dc get-text-extent sc-read-more... (get-read-more-underline-font sl))) (send dc draw-text sc-read-more... (+ dx (- br read-more-w read-more-gap)) (+ dy (- (+ bmp-y lock-height) read-more-h))) (when show-lock? (define icon (if locked? lock-closed lock-open)) (send dc draw-bitmap icon (+ dx bmp-x) (+ dy bmp-y)) (when mouse-in-lock-icon? (send dc set-font normal-control-font) (send dc set-text-foreground "black") (define-values (tw th _1 _2) (send dc get-text-extent sc-f2-to-lock)) (send dc set-brush fade-out-color 'solid) (define txt-x (- dx+br box-width)) (define txt-y (+ dy+bt box-height)) (send dc draw-rectangle txt-x txt-y (+ tw 8) (+ th 4)) (send dc draw-text sc-f2-to-lock (+ txt-x 4) (+ txt-y 2)))) (when std (send dc set-font (send std get-font))) (define label-font (get-label-font sl)) (define-values (label-w label-h label-d label-a) (send dc get-text-extent (list-ref strs 0) label-font)) (define-values (first-line-w first-line-h _1 _2) (send dc get-text-extent (if (null? (cdr strs)) "" (list-ref strs 1)))) (send dc set-text-foreground var-color) (for/fold ([y (if label-overlap? (+ blue-box-margin (extra-first-line-space dc sl strs)) (+ blue-box-margin label-h))]) ([str (in-list (cdr strs))]) (define-values (w h d a) (send dc get-text-extent str)) (send dc draw-text str (+ (- dx+br box-width) blue-box-margin) (+ dy+bt y)) (+ y h)) (draw-blue-box-shadow dc (- dx+br box-width) dy+bt box-width box-height) (send dc set-text-foreground "white") (send dc set-font label-font) (send dc draw-text (list-ref strs 0) (- dx+br blue-box-margin label-w) (+ dy+bt blue-box-margin)) (send dc set-text-foreground text-foreground) (send dc set-smoothing smoothing) (send dc set-brush brush) (send dc set-pen pen) (send dc set-font font))) ;; EFFECT: updates the pen & brush of the given dc (but restores origin) (define (draw-blue-box-shadow dc box-x box-y box-width box-height) (define-values (ox oy) (send dc get-origin)) (send dc set-pen "black" 1 'transparent) (send dc set-brush horizontal-gradient-brush) (send dc set-origin (+ ox (- box-x shadow-size)) (+ oy box-y)) (send dc draw-rectangle 0 0 shadow-size box-height) (send dc set-brush vertical-gradient-brush) (send dc set-origin (+ ox box-x) (+ oy (+ box-y box-height))) (send dc draw-rectangle 0 0 box-width shadow-size) (send dc set-brush radial-gradient-brush) (send dc set-origin (+ ox (- box-x shadow-size)) (+ oy (- (+ box-y box-height) shadow-size))) (send dc draw-arc 0 0 (* 2 shadow-size) (* 2 shadow-size) pi (* pi 3/2)) (send dc set-origin ox oy)) (define shadow-start-color (make-object color% 0 0 0 .5)) (define shadow-end-color (make-object color% 0 0 0 0)) (define shadow-size 10) (define horizontal-gradient-brush (new brush% [gradient (new linear-gradient% [x0 0] [y0 0] [x1 shadow-size] [y1 0] [stops (list (list 0 shadow-end-color) (list 1 shadow-start-color))])])) (define vertical-gradient-brush (new brush% [gradient (new linear-gradient% [x0 0] [y0 0] [x1 0] [y1 shadow-size] [stops (list (list 0 shadow-start-color) (list 1 shadow-end-color))])])) (define radial-gradient-brush (new brush% [gradient (new radial-gradient% [x0 shadow-size] [y0 shadow-size] [r0 0] [x1 shadow-size] [y1 shadow-size] [r1 shadow-size] [stops (list (list 0 shadow-start-color) (list 1 shadow-end-color))])])) (define closed-radial-gradient-brush (new brush% [gradient (new radial-gradient% [x0 (+ corner-radius shadow-size)] [y0 (+ corner-radius shadow-size)] [r0 corner-radius] [x1 (+ corner-radius shadow-size)] [y1 (+ corner-radius shadow-size)] [r1 (+ shadow-size corner-radius)] [stops (list (list 0 (make-object color% 0 0 0 0)) (list 0 shadow-start-color) (list 1 shadow-end-color))])])) (define (get-blue-box-size dc sl strs) (define std-font (and (send sl find-named-style "Standard") (send (send sl find-named-style "Standard") get-font))) (define-values (main-w main-h) (for/fold ([tot-w (+ blue-box-margin blue-box-margin)] [tot-h (+ blue-box-margin blue-box-margin)]) ([str (in-list (cdr strs))]) (define-values (w h d a) (send dc get-text-extent str std-font)) (values (max tot-w (+ w blue-box-margin blue-box-margin)) (+ tot-h h)))) (define-values (label-w label-h _2 _3) (send dc get-text-extent (list-ref strs 0) (get-label-font sl))) (define-values (first-line-w first-line-h _5 _6) (send dc get-text-extent (if (null? (cdr strs)) "" (list-ref strs 1)) std-font)) (define-values (read-more-w read-more-h _7 _8) (send dc get-text-extent sc-read-more... (get-read-more-underline-font sl))) (cond [(<= (+ label-w first-line-w 10) main-w) ;; in this case, there is no overlap on the first line, so ;; we draw both on the first line (like they are drawn in the html) (values main-w (+ main-h (extra-first-line-space dc sl strs) read-more-h read-more-gap) #t)] [else ;; otherwise we make an extra line at the top for the first line (values (max main-w (+ label-w blue-box-margin blue-box-margin) (+ read-more-w blue-box-margin blue-box-margin)) (+ main-h label-h read-more-h read-more-gap) #f)])) (define read-more-gap 4) ;; returns the extra gap to leave above the first line of the contract ;; so that the baseline of the label and the baseline of that first line ;; are at the same place (useful in the case where the label overlaps ;; with the first line of the contract) (define (extra-first-line-space dc sl strs) (define-values (_1 label-h label-d _2) (send dc get-text-extent (list-ref strs 0) (get-label-font sl))) (define-values (_3 first-line-h first-line-d _4) (send dc get-text-extent (if (null? (cdr strs)) "" (list-ref strs 1)))) (max 0 (- (- label-h label-d) (- first-line-h first-line-d))))