438 lines
20 KiB
Text
438 lines
20 KiB
Text
|
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||
|
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||
|
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||
|
|
|
||
|
|
;; bjogui/src/draw.bjo — the module (bjogui draw).
|
||
|
|
;;
|
||
|
|
;; Drawing with a pen: what a component made by `component` in (bjogui view)
|
||
|
|
;; draws itself with. Each function draws at once, on top of what is drawn
|
||
|
|
;; before it, in the component's own coordinates: 0, 0 is its top left
|
||
|
|
;; corner, x grows to the right and y down, in pixels.
|
||
|
|
;;
|
||
|
|
;; A colour is a string, as everywhere in bjogui: a name such as "red", or
|
||
|
|
;; "#rgb", "#rrggbb" or "#aarrggbb". The empty string, and a colour that
|
||
|
|
;; cannot be read, draw nothing. An angle is in radians, as `sin` and `cos`
|
||
|
|
;; of (std maths) take it, and grows clockwise from the right, because y
|
||
|
|
;; grows down.
|
||
|
|
;;
|
||
|
|
;; The pen is Avalonia's DrawingContext, so a drawing is vectors: it is sharp
|
||
|
|
;; at any scale of the screen.
|
||
|
|
|
||
|
|
(export
|
||
|
|
Pen
|
||
|
|
;; Shapes.
|
||
|
|
fill-rect stroke-rect fill-circle stroke-circle fill-ellipse stroke-ellipse
|
||
|
|
stroke-line stroke-polyline fill-polygon stroke-arc fill-path stroke-path
|
||
|
|
;; Text and pictures.
|
||
|
|
draw-text text-size draw-image
|
||
|
|
;; What is drawn inside a function, moved, turned, cut or faded.
|
||
|
|
translated scaled rotated clipped faded
|
||
|
|
;; For (bjogui app): a drawing, as the picture an Image shows.
|
||
|
|
drawing-image)
|
||
|
|
|
||
|
|
(import (std prelude))
|
||
|
|
(import (std maths))
|
||
|
|
(import (std interop))
|
||
|
|
(import (std mutable map))
|
||
|
|
|
||
|
|
(import/class
|
||
|
|
(Object (: System.Object))
|
||
|
|
(Disposable (: System.IDisposable))
|
||
|
|
(Culture (: System.Globalization.CultureInfo))
|
||
|
|
(IBrush (: Avalonia.Media.IBrush))
|
||
|
|
(IPen (: Avalonia.Media.IPen))
|
||
|
|
(AvPen (: Avalonia.Media.Pen (-> IBrush double IDashStyle LineCap LineJoin double AvPen)))
|
||
|
|
(IDashStyle (: Avalonia.Media.IDashStyle))
|
||
|
|
(DashStyle (: Avalonia.Media.DashStyle (-> (Seq double) double DashStyle)))
|
||
|
|
(LineCap (: Avalonia.Media.PenLineCap))
|
||
|
|
(LineJoin (: Avalonia.Media.PenLineJoin))
|
||
|
|
(BoxShadows (: Avalonia.Media.BoxShadows))
|
||
|
|
(Rect (: Avalonia.Rect (-> double double double double Rect)))
|
||
|
|
(Point (: Avalonia.Point (-> double double Point)))
|
||
|
|
(Size (: Avalonia.Size (-> double double Size)))
|
||
|
|
(Matrix (: Avalonia.Matrix))
|
||
|
|
(Geometry (: Avalonia.Media.Geometry))
|
||
|
|
(RectGeometry (: Avalonia.Media.RectangleGeometry (-> Rect double double RectGeometry)))
|
||
|
|
(StreamGeometry (: Avalonia.Media.StreamGeometry (-> StreamGeometry)))
|
||
|
|
(GeometryContext (: Avalonia.Media.StreamGeometryContext))
|
||
|
|
(Sweep (: Avalonia.Media.SweepDirection))
|
||
|
|
(DrawingGroup (: Avalonia.Media.DrawingGroup (-> DrawingGroup)))
|
||
|
|
(DrawingImage (: Avalonia.Media.DrawingImage (-> Drawing DrawingImage)))
|
||
|
|
(Drawing (: Avalonia.Media.Drawing))
|
||
|
|
(DrawingContext (: Avalonia.Media.DrawingContext))
|
||
|
|
(PushedState (: Avalonia.Media.DrawingContext.PushedState))
|
||
|
|
(IImage (: Avalonia.Media.IImage))
|
||
|
|
(Bitmap (: Avalonia.Media.Imaging.Bitmap (-> string Bitmap)))
|
||
|
|
(FontFamily (: Avalonia.Media.FontFamily (-> string FontFamily)))
|
||
|
|
(FontStyle (: Avalonia.Media.FontStyle))
|
||
|
|
(FontWeight (: Avalonia.Media.FontWeight))
|
||
|
|
(FontStretch (: Avalonia.Media.FontStretch))
|
||
|
|
(Typeface (: Avalonia.Media.Typeface (-> FontFamily FontStyle FontWeight FontStretch Typeface)))
|
||
|
|
(FlowDirection (: Avalonia.Media.FlowDirection))
|
||
|
|
(FormattedText (: Avalonia.Media.FormattedText
|
||
|
|
(-> string Culture FlowDirection Typeface double IBrush FormattedText))))
|
||
|
|
|
||
|
|
(import/extern
|
||
|
|
(parse-brush (: Avalonia.Media.Brush.Parse (-> string IBrush)))
|
||
|
|
(parse-geometry (: Avalonia.Media.Geometry.Parse (-> string Geometry)))
|
||
|
|
(open-group (: Avalonia.Media.DrawingGroup.Open (-> DrawingGroup DrawingContext)))
|
||
|
|
(bounds-of (: Avalonia.Media.Drawing.GetBounds (-> Drawing Rect)))
|
||
|
|
(rect-x (: Avalonia.Rect.X (-> Rect double) #:get))
|
||
|
|
(rect-y (: Avalonia.Rect.Y (-> Rect double) #:get))
|
||
|
|
(open-geometry (: Avalonia.Media.StreamGeometry.Open (-> StreamGeometry GeometryContext)))
|
||
|
|
(begin-figure! (: Avalonia.Media.StreamGeometryContext.BeginFigure (-> GeometryContext Point bool void)))
|
||
|
|
(line-to! (: Avalonia.Media.StreamGeometryContext.LineTo (-> GeometryContext Point void)))
|
||
|
|
(arc-to! (: Avalonia.Media.StreamGeometryContext.ArcTo
|
||
|
|
(-> GeometryContext Point Size double bool Sweep void)))
|
||
|
|
(end-figure! (: Avalonia.Media.StreamGeometryContext.EndFigure (-> GeometryContext bool void)))
|
||
|
|
(dc-rect (: Avalonia.Media.DrawingContext.DrawRectangle
|
||
|
|
(-> DrawingContext IBrush IPen Rect double double BoxShadows void)))
|
||
|
|
(dc-ellipse (: Avalonia.Media.DrawingContext.DrawEllipse
|
||
|
|
(-> DrawingContext IBrush IPen Point double double void)))
|
||
|
|
(dc-line (: Avalonia.Media.DrawingContext.DrawLine (-> DrawingContext IPen Point Point void)))
|
||
|
|
(dc-geometry (: Avalonia.Media.DrawingContext.DrawGeometry
|
||
|
|
(-> DrawingContext IBrush IPen Geometry void)))
|
||
|
|
(dc-text (: Avalonia.Media.DrawingContext.DrawText (-> DrawingContext FormattedText Point void)))
|
||
|
|
(dc-image (: Avalonia.Media.DrawingContext.DrawImage (-> DrawingContext IImage Rect void)))
|
||
|
|
(push-transform (: Avalonia.Media.DrawingContext.PushTransform (-> DrawingContext Matrix PushedState)))
|
||
|
|
(push-clip (: Avalonia.Media.DrawingContext.PushClip (-> DrawingContext Rect PushedState)))
|
||
|
|
(push-opacity (: Avalonia.Media.DrawingContext.PushOpacity (-> DrawingContext double PushedState)))
|
||
|
|
(pop! (: Avalonia.Media.DrawingContext.PushedState.Dispose (-> PushedState void)))
|
||
|
|
(translation (: Avalonia.Matrix.CreateTranslation (-> double double Matrix)))
|
||
|
|
(scaling (: Avalonia.Matrix.CreateScale (-> double double Matrix)))
|
||
|
|
(rotation (: Avalonia.Matrix.CreateRotation (-> double Point Matrix)))
|
||
|
|
(text-width (: Avalonia.Media.FormattedText.Width (-> FormattedText double) #:get))
|
||
|
|
(text-height (: Avalonia.Media.FormattedText.Height (-> FormattedText double) #:get))
|
||
|
|
(max-text-width! (: Avalonia.Media.FormattedText.MaxTextWidth (-> FormattedText double void) #:set))
|
||
|
|
(default-family (: Avalonia.Media.FontFamily.Default #:get))
|
||
|
|
(invariant (: System.Globalization.CultureInfo.InvariantCulture #:get))
|
||
|
|
(dispose! (: System.IDisposable.Dispose (-> Disposable void))))
|
||
|
|
|
||
|
|
;; ---------------------------------------------------------------------------
|
||
|
|
;; The pen
|
||
|
|
;; ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
;; What a component draws with, for as long as its `#:draw` function runs.
|
||
|
|
(type (: Pen #:opaque (Record (: dc DrawingContext))))
|
||
|
|
|
||
|
|
;; ---------------------------------------------------------------------------
|
||
|
|
;; What is read once
|
||
|
|
;; ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
;; Colours, paths, pictures and fonts are read once each and kept, because a
|
||
|
|
;; component draws them again on each change. A table that grows past its
|
||
|
|
;; limit is emptied, so a program that makes new paths all the time does not
|
||
|
|
;; keep them all.
|
||
|
|
(def cache-limit 2000)
|
||
|
|
|
||
|
|
(: brushes (MutableMap string (Option IBrush)))
|
||
|
|
(def brushes (mutablemap))
|
||
|
|
|
||
|
|
(: geometries (MutableMap string (Option Geometry)))
|
||
|
|
(def geometries (mutablemap))
|
||
|
|
|
||
|
|
(: bitmaps (MutableMap string (Option Bitmap)))
|
||
|
|
(def bitmaps (mutablemap))
|
||
|
|
|
||
|
|
(: typefaces (MutableMap string Typeface))
|
||
|
|
(def typefaces (mutablemap))
|
||
|
|
|
||
|
|
(: (-> (MutableMap string %a) string (-> %a) %a))
|
||
|
|
(defun (cached table key make)
|
||
|
|
(match (mutablemap-try-ref table key)
|
||
|
|
((Some v) v)
|
||
|
|
(None
|
||
|
|
(when (>= (mutablemap-length table) cache-limit) (mutablemap-clear! table))
|
||
|
|
(def v (make))
|
||
|
|
(mutablemap-set! table key v)
|
||
|
|
v)))
|
||
|
|
|
||
|
|
(: (-> string (Option IBrush)))
|
||
|
|
(defun (brush colour)
|
||
|
|
(if (string-empty? colour)
|
||
|
|
None
|
||
|
|
(cached brushes colour
|
||
|
|
(fun ()
|
||
|
|
(match (try (parse-brush colour) #:catch (System.Exception))
|
||
|
|
((Ok b) (Some b))
|
||
|
|
((Err _) None))))))
|
||
|
|
|
||
|
|
(: (-> string (Option Geometry)))
|
||
|
|
(defun (geometry data)
|
||
|
|
(cached geometries data
|
||
|
|
(fun ()
|
||
|
|
(match (try (parse-geometry data) #:catch (System.Exception))
|
||
|
|
((Ok g) (Some g))
|
||
|
|
((Err _) None)))))
|
||
|
|
|
||
|
|
(: (-> string (Option Bitmap)))
|
||
|
|
(defun (bitmap path)
|
||
|
|
(cached bitmaps path
|
||
|
|
(fun ()
|
||
|
|
(match (try (Bitmap. path) #:catch (System.Exception))
|
||
|
|
((Ok b) (Some b))
|
||
|
|
((Err _) None)))))
|
||
|
|
|
||
|
|
(: (-> string bool Typeface))
|
||
|
|
(defun (typeface font bold?)
|
||
|
|
(cached typefaces #"${font}/${bold?}"
|
||
|
|
(fun ()
|
||
|
|
(Typeface. (if (string-empty? font) default-family (FontFamily. font))
|
||
|
|
FontStyle.Normal (if bold? FontWeight.Bold FontWeight.Normal) FontStretch.Normal))))
|
||
|
|
|
||
|
|
;; A line of `width`, in the brush. `cap` is "flat", "round" or "square".
|
||
|
|
;; `dash` is the lengths of the dashes and the gaps between them, in widths
|
||
|
|
;; of the line, in turn; empty for a solid line.
|
||
|
|
(: (-> IBrush double string (Vec double) IPen))
|
||
|
|
(defun (line-pen b width cap dash)
|
||
|
|
(def c (match cap ("round" LineCap.Round) ("square" LineCap.Square) (_ LineCap.Flat)))
|
||
|
|
(def d (if (vec-empty? dash)
|
||
|
|
(: (clr-default) IDashStyle)
|
||
|
|
(cast IDashStyle (DashStyle. (cast (Seq double) (: (vec->array dash) (Array double))) 0.0))))
|
||
|
|
(cast IPen (AvPen. b width d c LineJoin.Round 10.0)))
|
||
|
|
|
||
|
|
(: (-> double double Point))
|
||
|
|
(defun (pt x y) (Point. x y))
|
||
|
|
|
||
|
|
(def no-brush (: (clr-default) IBrush))
|
||
|
|
(def no-pen (: (clr-default) IPen))
|
||
|
|
|
||
|
|
;; ---------------------------------------------------------------------------
|
||
|
|
;; Shapes
|
||
|
|
;; ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
;; The rectangle at `x`, `y`, `w` by `h`, filled, with corners as round as
|
||
|
|
;; `#:radius`.
|
||
|
|
;; A rectangle with round corners is drawn as a geometry, because a drawing
|
||
|
|
;; that is kept, as a component's is, drops the corners of DrawRectangle.
|
||
|
|
(: (-> Pen IBrush IPen double double double double double void))
|
||
|
|
(defun (rect! pen b p x y w h radius)
|
||
|
|
(if (> radius 0.0)
|
||
|
|
(dc-geometry (.dc pen) b p (cast Geometry (RectGeometry. (Rect. x y w h) radius radius)))
|
||
|
|
(dc-rect (.dc pen) b p (Rect. x y w h) 0.0 0.0 (clr-default))))
|
||
|
|
|
||
|
|
(: fill-rect (-> Pen double double double double string (#:radius double) void))
|
||
|
|
(defun (fill-rect pen x y w h colour #:radius 0.0)
|
||
|
|
(match (brush colour)
|
||
|
|
((Some b) (rect! pen b no-pen x y w h radius))
|
||
|
|
(None unit)))
|
||
|
|
|
||
|
|
;; The line around a rectangle. The line is centred on the rectangle's edge.
|
||
|
|
(: stroke-rect (-> Pen double double double double string (#:width double) (#:radius double)
|
||
|
|
(#:dash (Vec double))
|
||
|
|
void))
|
||
|
|
(defun (stroke-rect pen x y w h colour #:width 1.0 #:radius 0.0 #:dash [])
|
||
|
|
(match (brush colour)
|
||
|
|
((Some b) (rect! pen no-brush (line-pen b width "flat" dash) x y w h radius))
|
||
|
|
(None unit)))
|
||
|
|
|
||
|
|
;; The circle around `cx`, `cy`, of radius `r`, filled.
|
||
|
|
(: fill-circle (-> Pen double double double string void))
|
||
|
|
(defun (fill-circle pen cx cy r colour)
|
||
|
|
(fill-ellipse pen (- cx r) (- cy r) (* 2.0 r) (* 2.0 r) colour))
|
||
|
|
|
||
|
|
;; The line around a circle.
|
||
|
|
(: stroke-circle (-> Pen double double double string (#:width double) (#:dash (Vec double)) void))
|
||
|
|
(defun (stroke-circle pen cx cy r colour #:width 1.0 #:dash [])
|
||
|
|
(stroke-ellipse pen (- cx r) (- cy r) (* 2.0 r) (* 2.0 r) colour #:width width #:dash dash))
|
||
|
|
|
||
|
|
;; The ellipse in the rectangle at `x`, `y`, `w` by `h`, filled.
|
||
|
|
(: fill-ellipse (-> Pen double double double double string void))
|
||
|
|
(defun (fill-ellipse pen x y w h colour)
|
||
|
|
(match (brush colour)
|
||
|
|
((Some b) (dc-ellipse (.dc pen) b no-pen (pt (+ x (/ w 2.0)) (+ y (/ h 2.0))) (/ w 2.0) (/ h 2.0)))
|
||
|
|
(None unit)))
|
||
|
|
|
||
|
|
;; The line around an ellipse.
|
||
|
|
(: stroke-ellipse (-> Pen double double double double string (#:width double) (#:dash (Vec double))
|
||
|
|
void))
|
||
|
|
(defun (stroke-ellipse pen x y w h colour #:width 1.0 #:dash [])
|
||
|
|
(match (brush colour)
|
||
|
|
((Some b) (dc-ellipse (.dc pen) no-brush (line-pen b width "flat" dash)
|
||
|
|
(pt (+ x (/ w 2.0)) (+ y (/ h 2.0))) (/ w 2.0) (/ h 2.0)))
|
||
|
|
(None unit)))
|
||
|
|
|
||
|
|
;; A line from `x1`, `y1` to `x2`, `y2`. `#:cap` is how its ends look:
|
||
|
|
;; "flat", "round" or "square".
|
||
|
|
(: stroke-line (-> Pen double double double double string (#:width double) (#:cap string)
|
||
|
|
(#:dash (Vec double))
|
||
|
|
void))
|
||
|
|
(defun (stroke-line pen x1 y1 x2 y2 colour #:width 1.0 #:cap "flat" #:dash [])
|
||
|
|
(match (brush colour)
|
||
|
|
((Some b) (dc-line (.dc pen) (line-pen b width cap dash) (pt x1 y1) (pt x2 y2)))
|
||
|
|
(None unit)))
|
||
|
|
|
||
|
|
;; The geometry through `points`, closed when `closed?`.
|
||
|
|
(: (-> (Vec (Tuple double double)) bool bool (Option Geometry)))
|
||
|
|
(defun (through points filled? closed?)
|
||
|
|
(if (< (vec-length points) 2)
|
||
|
|
None
|
||
|
|
(begin
|
||
|
|
(def g (StreamGeometry.))
|
||
|
|
(def ctx (open-geometry g))
|
||
|
|
(def (Tuple x0 y0) (vec-ref points 0))
|
||
|
|
(begin-figure! ctx (pt x0 y0) filled?)
|
||
|
|
(loop (:for (Tuple x y) (vec-drop points 1)) (:do (line-to! ctx (pt x y))))
|
||
|
|
(end-figure! ctx closed?)
|
||
|
|
(dispose! (cast Disposable ctx))
|
||
|
|
(Some (cast Geometry g)))))
|
||
|
|
|
||
|
|
;; Lines from each of `points` to the next.
|
||
|
|
(: stroke-polyline (-> Pen (Vec (Tuple double double)) string (#:width double) (#:cap string)
|
||
|
|
(#:dash (Vec double))
|
||
|
|
void))
|
||
|
|
(defun (stroke-polyline pen points colour #:width 1.0 #:cap "flat" #:dash [])
|
||
|
|
(match (Tuple (brush colour) (through points #f #f))
|
||
|
|
((Tuple (Some b) (Some g)) (dc-geometry (.dc pen) no-brush (line-pen b width cap dash) g))
|
||
|
|
(_ unit)))
|
||
|
|
|
||
|
|
;; The polygon with `points` at its corners, filled.
|
||
|
|
(: fill-polygon (-> Pen (Vec (Tuple double double)) string void))
|
||
|
|
(defun (fill-polygon pen points colour)
|
||
|
|
(match (Tuple (brush colour) (through points #t #t))
|
||
|
|
((Tuple (Some b) (Some g)) (dc-geometry (.dc pen) b no-pen g))
|
||
|
|
(_ unit)))
|
||
|
|
|
||
|
|
;; A piece of the circle around `cx`, `cy` of radius `r`: from the angle
|
||
|
|
;; `from`, `sweep` radians on, clockwise when `sweep` is more than 0. A sweep
|
||
|
|
;; of a whole turn or more is the whole circle.
|
||
|
|
(: stroke-arc (-> Pen double double double double double string (#:width double) (#:cap string)
|
||
|
|
(#:dash (Vec double))
|
||
|
|
void))
|
||
|
|
(defun (stroke-arc pen cx cy r from sweep colour #:width 1.0 #:cap "flat" #:dash [])
|
||
|
|
(def turn (* 2.0 3.141592653589793))
|
||
|
|
(defun (at a) (pt (+ cx (* r (cos a))) (+ cy (* r (sin a)))))
|
||
|
|
(match (brush colour)
|
||
|
|
((Some b)
|
||
|
|
(cond ((>= (abs sweep) (- turn 1e-9))
|
||
|
|
(stroke-circle pen cx cy r colour #:width width #:dash dash))
|
||
|
|
((= sweep 0.0) unit)
|
||
|
|
(else
|
||
|
|
(def g (StreamGeometry.))
|
||
|
|
(def ctx (open-geometry g))
|
||
|
|
(begin-figure! ctx (at from) #f)
|
||
|
|
(arc-to! ctx (at (+ from sweep)) (Size. r r) 0.0 (> (abs sweep) (/ turn 2.0))
|
||
|
|
(if (> sweep 0.0) Sweep.Clockwise Sweep.CounterClockwise))
|
||
|
|
(end-figure! ctx #f)
|
||
|
|
(dispose! (cast Disposable ctx))
|
||
|
|
(dc-geometry (.dc pen) no-brush (line-pen b width cap dash) (cast Geometry g)))))
|
||
|
|
(None unit)))
|
||
|
|
|
||
|
|
;; The path `data`, written as SVG writes one: "M 10 10 L 50 10 L 30 40 Z"
|
||
|
|
;; is a triangle. Filled. A path that cannot be read draws nothing.
|
||
|
|
(: fill-path (-> Pen string string void))
|
||
|
|
(defun (fill-path pen data colour)
|
||
|
|
(match (Tuple (brush colour) (geometry data))
|
||
|
|
((Tuple (Some b) (Some g)) (dc-geometry (.dc pen) b no-pen g))
|
||
|
|
(_ unit)))
|
||
|
|
|
||
|
|
;; The line along the path `data`.
|
||
|
|
(: stroke-path (-> Pen string string (#:width double) (#:cap string) (#:dash (Vec double)) void))
|
||
|
|
(defun (stroke-path pen data colour #:width 1.0 #:cap "flat" #:dash [])
|
||
|
|
(match (Tuple (brush colour) (geometry data))
|
||
|
|
((Tuple (Some b) (Some g)) (dc-geometry (.dc pen) no-brush (line-pen b width cap dash) g))
|
||
|
|
(_ unit)))
|
||
|
|
|
||
|
|
;; ---------------------------------------------------------------------------
|
||
|
|
;; Text and pictures
|
||
|
|
;; ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
(: (-> string IBrush double bool string double FormattedText))
|
||
|
|
(defun (formatted s b size bold? font max-width)
|
||
|
|
(def t (FormattedText. s invariant FlowDirection.LeftToRight (typeface font bold?) size b))
|
||
|
|
(when (> max-width 0.0) (max-text-width! t max-width))
|
||
|
|
t)
|
||
|
|
|
||
|
|
;; The text `s` with its top at `y`. `#:align` says where `x` is on it:
|
||
|
|
;; "left", "centre" or "right". `#:font` is the name of a font, "" for the
|
||
|
|
;; theme's. With `#:max-width`, the text breaks its lines at that width.
|
||
|
|
(: draw-text (-> Pen string double double string (#:size double) (#:bold bool) (#:font string)
|
||
|
|
(#:align string) (#:max-width double)
|
||
|
|
void))
|
||
|
|
(defun (draw-text pen s x y colour #:size 14.0 #:bold #f #:font "" #:align "left" #:max-width 0.0)
|
||
|
|
(match (brush colour)
|
||
|
|
((Some b)
|
||
|
|
(def t (formatted s b size bold font max-width))
|
||
|
|
(def left (match align
|
||
|
|
("centre" (- x (/ (text-width t) 2.0)))
|
||
|
|
("right" (- x (text-width t)))
|
||
|
|
(_ x)))
|
||
|
|
(dc-text (.dc pen) t (pt left y)))
|
||
|
|
(None unit)))
|
||
|
|
|
||
|
|
;; The width and the height that `draw-text` gives `s`, with the same
|
||
|
|
;; options. For a component to lay out its text.
|
||
|
|
(: text-size (-> string (#:size double) (#:bold bool) (#:font string) (#:max-width double)
|
||
|
|
(Tuple double double)))
|
||
|
|
(defun (text-size s #:size 14.0 #:bold #f #:font "" #:max-width 0.0)
|
||
|
|
(def t (formatted s (parse-brush "black") size bold font max-width))
|
||
|
|
(Tuple (text-width t) (text-height t)))
|
||
|
|
|
||
|
|
;; The picture in the file at `path`, in the rectangle at `x`, `y`, `w` by
|
||
|
|
;; `h`. A file that cannot be read draws nothing.
|
||
|
|
(: draw-image (-> Pen string double double double double void))
|
||
|
|
(defun (draw-image pen path x y w h)
|
||
|
|
(match (bitmap path)
|
||
|
|
((Some b) (dc-image (.dc pen) (cast IImage b) (Rect. x y w h)))
|
||
|
|
(None unit)))
|
||
|
|
|
||
|
|
;; ---------------------------------------------------------------------------
|
||
|
|
;; Moved, turned, cut and faded
|
||
|
|
;; ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
(: (-> PushedState (-> void) void))
|
||
|
|
(defun (inside state body)
|
||
|
|
(body)
|
||
|
|
(pop! state))
|
||
|
|
|
||
|
|
;; What `body` draws, moved by `dx`, `dy`.
|
||
|
|
(: translated (-> Pen double double (-> void) void))
|
||
|
|
(defun (translated pen dx dy body)
|
||
|
|
(inside (push-transform (.dc pen) (translation dx dy)) body))
|
||
|
|
|
||
|
|
;; What `body` draws, `sx` times as wide and `sy` times as tall, from 0, 0.
|
||
|
|
(: scaled (-> Pen double double (-> void) void))
|
||
|
|
(defun (scaled pen sx sy body)
|
||
|
|
(inside (push-transform (.dc pen) (scaling sx sy)) body))
|
||
|
|
|
||
|
|
;; What `body` draws, turned by `angle` around `cx`, `cy`.
|
||
|
|
(: rotated (-> Pen double double double (-> void) void))
|
||
|
|
(defun (rotated pen angle cx cy body)
|
||
|
|
(inside (push-transform (.dc pen) (rotation angle (pt cx cy))) body))
|
||
|
|
|
||
|
|
;; What `body` draws, only inside the rectangle at `x`, `y`, `w` by `h`.
|
||
|
|
(: clipped (-> Pen double double double double (-> void) void))
|
||
|
|
(defun (clipped pen x y w h body)
|
||
|
|
(inside (push-clip (.dc pen) (Rect. x y w h)) body))
|
||
|
|
|
||
|
|
;; What `body` draws, at `opacity`: 0 is not seen, 1 is as drawn.
|
||
|
|
(: faded (-> Pen double (-> void) void))
|
||
|
|
(defun (faded pen opacity body)
|
||
|
|
(inside (push-opacity (.dc pen) opacity) body))
|
||
|
|
|
||
|
|
;; ---------------------------------------------------------------------------
|
||
|
|
;; For the app
|
||
|
|
;; ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
;; What `draw` draws on `w` by `h`, as a picture that an Image shows, and
|
||
|
|
;; where the Image goes for 0, 0 to be the top left corner. None when there
|
||
|
|
;; is no room.
|
||
|
|
;;
|
||
|
|
;; A DrawingImage moves the top left corner of what is drawn to its own 0, 0,
|
||
|
|
;; so a drawing that starts at 10, 10, or at -5, 0, would move. The place of
|
||
|
|
;; the bounds' corner is given back, and the Image is put there, which moves
|
||
|
|
;; the drawing back. What is drawn is cut to `w` by `h`, so what is outside
|
||
|
|
;; costs nothing to show.
|
||
|
|
(: drawing-image (-> (-> Pen void) double double (Option (Tuple Object double double))))
|
||
|
|
(defun (drawing-image draw w h)
|
||
|
|
(if (or (<= w 0.0) (<= h 0.0))
|
||
|
|
None
|
||
|
|
(begin
|
||
|
|
(def group (DrawingGroup.))
|
||
|
|
(def dc (open-group group))
|
||
|
|
(def pen (Pen (dc dc)))
|
||
|
|
(clipped pen 0.0 0.0 w h (fun () (draw pen)))
|
||
|
|
(dispose! (cast Disposable dc))
|
||
|
|
(def b (bounds-of (cast Drawing group)))
|
||
|
|
(Some (Tuple (cast Object (DrawingImage. (cast Drawing group))) (rect-x b) (rect-y b))))))
|