monomer-1.0.0.0: examples/generative/Widgets/CirclesGrid.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
module Widgets.CirclesGrid (
CirclesGridCfg(..),
HasItemWidth(..),
HasSeed(..),
circlesGrid
) where
import Control.Lens
import Control.Monad (when)
import Data.Default
import Data.Maybe
import System.Random
import Monomer.Graphics.ColorTable
import Monomer.Widgets.Single
-- Imported to avoid issues with duplicate lens names
import Widgets.BoxesPalette
import qualified Monomer.Lens as L
data CirclesGridCfg = CirclesGridCfg {
_cgcItemWidth :: Double,
_cgcSeed :: Maybe Int
} deriving (Eq, Show)
instance Default CirclesGridCfg where
def = CirclesGridCfg {
_cgcItemWidth = 25,
_cgcSeed = Just 42
}
data CirclesGridState = CirclesGridState {
_cgcMouseX :: Double,
_cgcMouseY :: Double
} deriving (Eq, Show)
makeLensesWith abbreviatedFields ''CirclesGridCfg
makeLensesWith abbreviatedFields ''CirclesGridState
circlesGrid :: CirclesGridCfg -> WidgetNode s e
circlesGrid cfg = defaultWidgetNode "circlesGrid" widget where
widget = makeCirclesGrid cfg (CirclesGridState 0 0)
makeCirclesGrid :: CirclesGridCfg -> CirclesGridState -> Widget s e
makeCirclesGrid cfg state = widget where
widget = createSingle state def {
singleUseScissor = True,
singleMerge = merge,
singleHandleEvent = handleEvent,
singleGetSizeReq = getSizeReq,
singleRender = render
}
merge wenv node oldNode oldState = resultNode newNode where
newNode = node
& L.widget .~ makeCirclesGrid cfg oldState
handleEvent wenv node target evt = case evt of
Move (Point x y) -> Just (resultReqs newNode [RenderOnce]) where
newState = CirclesGridState x y
newNode = node
& L.widget .~ makeCirclesGrid cfg newState
_ -> Nothing
getSizeReq wenv node = (expandSize 100 1, expandSize 100 1)
render wenv node renderer = do
when (isJust (cfg ^. seed)) $
setStdGen $ mkStdGen (fromJust $ cfg ^. seed)
mapM_ (drawCircle renderer state vp iw cols) [0..cols * rows - 1]
where
style = currentStyle wenv node
vp = getContentArea node style
iw = cfg ^. itemWidth
cols = round (vp ^. L.w / iw)
rows = round (vp ^. L.h / iw)
drawCircle
:: Renderer -> CirclesGridState -> Rect -> Double -> Int -> Int -> IO ()
drawCircle renderer state vp iw cols idx = do
colorIdx :: Double <- randomIO
offsetX <- randomIO
offsetY <- randomIO
let color = colors !! floor (fromIntegral (length colors) * colorIdx)
let colorFill = color & L.a .~ 0.3
beginPath renderer
setStrokeWidth renderer 2
setStrokeColor renderer color
setFillColor renderer colorFill
renderEllipse renderer (rect offsetX offsetY)
fill renderer
stroke renderer
where
colors = [cyan, deepPink, orange, paleGreen]
sizeFactor = 0.3 + 1.1 * (state ^. mouseY - vp ^. L.y) / vp ^. L.h
randFactor = (state ^. mouseX - vp ^. L.x) / vp ^. L.w
currw = sizeFactor * iw
szDiff = (1 - sizeFactor) * iw
x = vp ^. L.x + iw * fromIntegral (idx `rem` cols)
y = vp ^. L.y + iw * fromIntegral (idx `div` cols)
rect ox oy = Rect rx ry currw currw where
rx = x + randFactor * (ox - 0.5) * iw
ry = y + randFactor * (oy - 0.5) * iw