Platform
Canvas
miso has full 2D and 3D canvas support via Miso.Canvas. See also the canvas2d example and three-miso for Three.js integration.
The Canvas monad
Drawing commands run in the Canvas monad, a ReaderT over the raw CanvasContext2D:
type Canvas a = ReaderT CanvasContext2D IO aEmbedding a canvas in the view
Use the canvas smart constructor. It takes an init callback (runs once on mount, returns state) and a draw callback (runs on every render with the current state). Capture the current model in the draw closure:
canvas
[ HP.width_ "800", HP.height_ "480" ]
(\_ -> pure ()) -- init: called once on canvas initialisation
(\() -> drawScene myModel) -- draw: called on each diffcanvas_ is the variant that threads no init state at all.
Drawing commands
drawScene :: Model -> Canvas ()
drawScene model = do
clearRect (0, 0, 800, 480)
fillStyle (color (RGB 30 144 255))
beginPath ()
arc (400, 240, 50, 0, 2 * pi)
fill ()
font "24px sans-serif"
fillText ("Score: " <> ms (score model), 10, 30)Available primitives include clearRect, fillRect, strokeRect, beginPath, closePath, moveTo, lineTo, arc, arcTo, fill, stroke, fillText, drawImage. Style setters: fillStyle, strokeStyle, lineWidth, font.
Animation loop
For smooth 60 FPS canvas animations, use rAFSub from Miso.Subscription.RAF instead of a manual threadDelay loop. It hooks into requestAnimationFrame and delivers a DOMHighResTimeStamp each frame:
data Action = Tick Double
main :: IO ()
main = startApp defaultEvents comp { subs = [ rAFSub Tick ] }Try it
data OrbitAction = Frame Double
-- Three planets orbit on a 2D canvas at 60 FPS. rAFSub delivers a
-- requestAnimationFrame timestamp; the draw callback closes over the model.
orbits :: Component ctx () Double OrbitAction
orbits = (component 0 update view)
{ subs = [ rAFSub Frame ] }
where
update (Frame ms') = this .= ms' / 1000
view _ () t =
Canvas.canvas [ HP.width_ "320", HP.height_ "220" ]
(\_ -> pure ()) -- init: runs once, no state needed
(\() -> scene t) -- draw: runs after every diff
scene :: Double -> Canvas.Canvas ()
scene t = do
-- a translucent wash instead of clearRect leaves motion trails
Canvas.fillStyle (Canvas.color (RGBA 14 13 11 0.24))
Canvas.fillRect (0, 0, 320, 220)
forM_ (zip [0 ..] [ RGB 255 184 74, RGB 240 138 36, RGB 226 83 31 ]) $
\(i, planet) -> do
let phase = t * (1.6 - 0.4 * i) + i * 2.1
x = 160 + (34 + 30 * i) * cos phase
y = 110 + (22 + 19 * i) * sin phase
Canvas.beginPath ()
Canvas.arc (x, y, 7 - 1.5 * i, 0, 2 * pi)
Canvas.fillStyle (Canvas.color planet)
Canvas.fill ()