packages feed

monomer-1.0.0.0: examples/tutorial/Tutorial07_CustomWidget.hs

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}

module Tutorial07_CustomWidget where

import Control.Concurrent (threadDelay)
import Control.Lens
import Control.Monad (forM_)
import Data.Default
import Data.Text (Text)
import Data.Typeable (cast)
import Monomer
import Monomer.Widgets.Single

import qualified Data.Text as T
import qualified Monomer.Lens as L

newtype CanvasCfg = CanvasCfg {
  _canvasColors :: [Color]
} deriving (Eq, Show)

instance Default CanvasCfg where
  def = CanvasCfg {
    _canvasColors = []
  }

instance Semigroup CanvasCfg where
  (<>) c1 c2 = CanvasCfg {
    _canvasColors = _canvasColors c1 <> _canvasColors c2
  }

instance Monoid CanvasCfg where
  mempty = def

data CanvasMessage
  = ResetCanvas
  deriving (Eq, Show)

newtype CanvasState = CanvasState {
  _clickedPoints :: [Point]
} deriving (Eq, Show)

makeLenses 'CanvasCfg
makeLenses 'CanvasState

canvasColor :: Color -> CanvasCfg
canvasColor col = def & canvasColors .~ [col]

canvas :: WidgetNode s e
canvas = canvas_ def

canvas_ :: [CanvasCfg] -> WidgetNode s e
canvas_ configs = defaultWidgetNode "canvas" newWidget where
  config = mconcat configs
  state = CanvasState []
  newWidget = makeCanvas config state

makeCanvas :: CanvasCfg -> CanvasState -> Widget s e
makeCanvas cfg state = widget where
  widget = createSingle state def {
    singleMerge = merge,
    singleHandleEvent = handleEvent,
    singleHandleMessage = handleMessage,
    singleGetSizeReq = getSizeReq,
    singleRender = render
  }

  colors
    | null (cfg ^. canvasColors) = [orange, green, steelBlue, deepPink]
    | otherwise = cfg ^. canvasColors
  nextColor idx = colors !! (idx `mod` length colors)

  merge wenv node oldNode oldState = result where
    newNode = node
      & L.widget .~ makeCanvas cfg oldState
    result = resultNode newNode

  handleEvent wenv node target evt = case evt of
    Click point button clicks -> Just result where
      newPoint = subPoint point origin
      newPoints = newPoint : state ^. clickedPoints
      newState = CanvasState newPoints
      newNode = node
        & L.widget .~ makeCanvas cfg newState
      result = resultNode newNode
    Move _ -> Just (resultReqs node [RenderOnce])
    _ -> Nothing
    where
      vp = node ^. L.info . L.viewport
      origin = Point (vp ^. L.x) (vp ^. L.y)

  handleMessage wenv node target msg = case cast msg of
    Just ResetCanvas -> Just result where
      newState = CanvasState []
      newNode = node
        & L.widget .~ makeCanvas cfg newState
      result = resultNode newNode
    _ -> Nothing

  getSizeReq wenv node = (sizeReqW, sizeReqH) where
    sizeReqW = minWidth 100
    sizeReqH = minHeight 100

  render wenv node renderer = do
    drawInTranslation renderer origin $
      forM_ tuples $ \(idx, pointA, pointB) -> do
        setStrokeColor renderer (nextColor idx)
        setStrokeWidth renderer 2
        beginPath renderer
        renderLine renderer pointA pointB
        stroke renderer
    where
      vp = node ^. L.info . L.viewport
      mousePos = wenv ^. L.inputStatus . L.mousePos
      newPoint = subPoint mousePos origin
      origin = Point (vp ^. L.x) (vp ^. L.y)
      clicked = state ^. clickedPoints
      points
        | isPointInNodeVp node mousePos = reverse $ newPoint : clicked
        | otherwise = reverse clicked
      tuples = zip3 [0..] points (drop 1 points)

data AppModel
  = AppModel
  deriving (Eq, Show)

data AppEvent
  = AppResetCanvas
  deriving (Eq, Show)

makeLenses 'AppModel

buildUI
  :: WidgetEnv AppModel AppEvent
  -> AppModel
  -> WidgetNode AppModel AppEvent
buildUI wenv model = widgetTree where
  widgetTree = vstack [
      button "Reset canvas" AppResetCanvas,
      spacer,
      canvas `nodeKey` "mainCanvas" `styleBasic` [border 1 gray]
--      canvas_ [canvasColor pink] `nodeKey` "mainCanvas" `styleBasic` [border 1 gray]
    ] `styleBasic` [padding 10]

handleEvent
  :: WidgetEnv AppModel AppEvent
  -> WidgetNode AppModel AppEvent
  -> AppModel
  -> AppEvent
  -> [AppEventResponse AppModel AppEvent]
handleEvent wenv node model evt = case evt of
  AppResetCanvas -> [Message "mainCanvas" ResetCanvas]

main07 :: IO ()
main07 = do
  startApp model handleEvent buildUI config
  where
    config = [
      appWindowTitle "Tutorial 07 - Custom Widget",
      appTheme darkTheme,
      appFontDef "Regular" "./assets/fonts/Roboto-Regular.ttf"
      ]
    model = AppModel