miso

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 a

Embedding 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 diff

canvas_ 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

Orbits: rAFSub driving the Canvas monadlive
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 ()