packages feed

ghcjs-vdom-0.2.0.0: examples/Table.hs

{-# LANGUAGE QuasiQuotes, OverloadedStrings, BangPatterns #-}

{-
  virtual-dom bindings demo, rendering a large pixel grid with a bouncing red
  square. the step and patch are calculated asynchronously, the update is
  batched in an animation frame
 -}

module Main where

import           Control.Monad

import           Data.IntMap (IntMap)
import qualified Data.IntMap as IM
import qualified Data.JSString as JSS

import           GHCJS.VDOM
import           GHCJS.VDOM.QQ
import qualified GHCJS.VDOM.Element as E
import qualified GHCJS.VDOM.Attribute as A

import           GHCJS.Foreign.Callback
import           GHCJS.Foreign.QQ
import           GHCJS.Types

import           JavaScript.Web.AnimationFrame (inAnimationFrame)

red :: JSString
red = "pixel-red"

white :: JSString
white = "pixel-white"

type Pixels = IntMap (IntMap JSString)

setPixel :: Int -> Int -> JSString -> Pixels -> Pixels
setPixel x y c p =
  let r  = p IM.! y
      r' = IM.insert x c r
  in  r' `seq` IM.insert y r' p

data State = State { x  :: !Int, y  :: !Int
                   , dx :: !Int, dy :: !Int
                   , w  :: !Int, h  :: !Int
                   , pixels :: !Pixels
                   }

mkState :: Int -> Int -> Int -> Int -> State
mkState w h x y = State x y 1 1 w h pix
  where
    pix     = IM.fromList $ map row [0..h-1]
    row n   = (n, IM.fromList (map (col n) [0..w-1]))
    col n m = (m, if (m,n)==(x,y) then red else white)

step :: State -> State
step (State x y dx dy w h p) =
  let dx' = if x==0 then 1 else if x==(w-1) then -1 else dx
      dy' = if y==0 then 1 else if y==(h-1) then -1 else dy
      x'  = x+dx'
      y'  = y+dy'
      p'  = setPixel x' y' red (setPixel x y white p)
   in State x' y' dx' dy' w h p'

cls :: JSString -> Attributes'
cls name = [att| className: name |]

render :: State -> VNode
render s = E.div (cls "state") [ch|pixelDiv,numDiv|]
    where
      xd       = textDiv (y s)
      yd       = textDiv (x s)
      numDiv   = E.div (cls "numeric") [ch|xd,yd|]
      pixelDiv = E.div (cls "pixels")
          (map (renderRowM (w s) . (pixels s IM.!)) [0..h s-1])

textDiv :: Show a => a -> VNode
textDiv x = E.div () [ch|c|]
  where
    c = E.text . JSS.pack . show $ x

renderRowM !w !r = memo renderRow w r

renderRow :: Int -> IntMap JSString -> VNode
renderRow w r =
  E.div (A.class_ "row", A.lang "EN") (map (renderPixelM r) [0..w-1])

renderPixelM !r !c = memo renderPixel r c

renderPixel :: IntMap JSString -> Int -> VNode
renderPixel r c = E.div (cls (r IM.! c)) ()

animate :: VMount -> State -> IO ()
animate m s =
  let s' = step s
      r' = render s'
  in do p <- diff m r'
        void $ inAnimationFrame ContinueAsync (\_ -> patch m p >> animate m s')

main :: IO ()
main = do
  root <- [js| document.createElement('div') |]
  [js_| document.body.appendChild(`root); |]
  let s = mkState 167 101 10 20
  m <- mount root (E.div () ())
  animate m s