monomer-1.0.0.0: examples/generative/Widgets/BoxesPalette.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
module Widgets.BoxesPalette (
BoxesPaletteCfg(..),
HasItemWidth(..),
HasPaletteType(..),
HasPaletteSize(..),
HasSeed(..),
boxesPalette
) where
import Control.Lens
import Control.Monad (forM, when)
import Data.Default
import Data.Maybe
import System.Random
import Monomer.Graphics.ColorTable
import Monomer.Widgets.Single
import qualified Monomer.Lens as L
data ColorHSV
= ColorHSV Double Double Double
deriving (Eq, Show)
data BoxesPaletteCfg = BoxesPaletteCfg {
_bpcItemWidth :: Double,
_bpcPaletteType :: Int,
_bpcPaletteSize :: Int,
_bpcSeed :: Maybe Int
} deriving (Eq, Show)
instance Default BoxesPaletteCfg where
def = BoxesPaletteCfg {
_bpcItemWidth = 25,
_bpcPaletteType = 0,
_bpcPaletteSize = 20,
_bpcSeed = Just 42
}
data BoxesPaletteState = BoxesPaletteState {
_cgcMouseX :: Double,
_cgcMouseY :: Double
} deriving (Eq, Show)
makeLensesWith abbreviatedFields ''BoxesPaletteCfg
makeLensesWith abbreviatedFields ''BoxesPaletteState
boxesPalette :: BoxesPaletteCfg -> WidgetNode s e
boxesPalette cfg = defaultWidgetNode "boxesPalette" widget where
widget = makeBoxesPalette cfg (BoxesPaletteState 100 100)
makeBoxesPalette :: BoxesPaletteCfg -> BoxesPaletteState -> Widget s e
makeBoxesPalette 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 .~ makeBoxesPalette cfg oldState
handleEvent wenv node target evt = case evt of
Move (Point x y) -> Just (resultReqs newNode [RenderOnce]) where
newState = BoxesPaletteState x y
newNode = node
& L.widget .~ makeBoxesPalette 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)
colors <- makePalette (cfg ^. paletteType) (cfg ^. paletteSize)
mapM_ (drawRectangle renderer state colors vp cols rows) [0..cols * rows - 1]
where
style = currentStyle wenv node
vp = getContentArea node style
iw = cfg ^. itemWidth
fw = 0.5 + 5 * (state ^. mouseX - vp ^. L.x) / vp ^. L.w
fh = 0.5 + 5 * (state ^. mouseY - vp ^. L.y) / vp ^. L.h
cols = round $ vp ^. L.w / (fw * iw)
rows = round $ vp ^. L.h / (fh * iw)
drawRectangle
:: Renderer
-> BoxesPaletteState
-> [Color]
-> Rect
-> Int
-> Int
-> Int
-> IO ()
drawRectangle renderer state colors vp cols rows idx = do
colorIdx :: Double <- randomIO
let color = colors !! floor (fromIntegral (length colors) * colorIdx)
beginPath renderer
setFillColor renderer color
renderRect renderer rect
fill renderer
where
rw = fromIntegral . round $ vp ^. L.w / fromIntegral cols
rh = fromIntegral . round $ vp ^. L.h / fromIntegral rows
rx = vp ^. L.x + rw * fromIntegral (idx `rem` cols)
ry = vp ^. L.y + rh * fromIntegral (idx `div` cols)
rect = Rect rx ry rw rh
makePalette :: Int -> Int -> IO [Color]
makePalette palette count = forM [0..count - 1] $ \idx -> do
h <- randomIO
s <- randomIO
v <- randomIO
return $ hsvToRGB (makeHSV idx h s v)
where
makeHSV idx h s v
| palette == 1 = ColorHSV h s 1
| palette == 2 = ColorHSV h 1 v
| palette == 3 && even idx = ColorHSV h 1 v
| palette == 3 = ColorHSV (195 / 360) s 1
| otherwise = ColorHSV h s v
hsvToRGB :: ColorHSV -> Color
hsvToRGB (ColorHSV h s v) = Color r g b 1 where
i = floor (h * 6)
f = h * 6 - fromIntegral i
p = v * (1 - s)
q = v * (1 - f * s)
t = v * (1 - (1 - f) * s)
(r1, g1, b1) = case i of
0 -> (v, t, p)
1 -> (q, v, p)
2 -> (p, v, t)
3 -> (p, q, v)
4 -> (t, p, v)
_ -> (v, p, q)
to255 v = round (v * 255)
(r, g, b) = (to255 r1, to255 g1, to255 b1)