packages feed

happlets-lib-gtk-0.1.0.0: src/Happlets/Lib/Gtk/TestSuite.hs

module Happlets.Lib.Gtk.TestSuite where

import           Happlets.Lib.Gtk

import           Control.Arrow

import           Linear.V2

import qualified Graphics.Rendering.Cairo as Cairo

----------------------------------------------------------------------------------------------------

main :: IO ()
main = happlet gtkHapplet $ do
  registeredAppName   .= "Happlets-Test"
  windowTitleBar      .= "Happlets Test"
  recommendWindowSize .= (640, 480)
  quitOnWindowClose   .= True
  
  win         <- newWindow
  pulsecircle <- newHapplet PulseCircle
    { thePulseCircleRadius     = 20
    , thePulseCirclePosition   = V2   0   0
    , thePulseCircleWindowSize = V2 640 480
    }

  redgrid     <- newHapplet (RedGrid 64.0)

  let (changeToPulseCircle, changeToRedGrid) =
        ( windowChangeHapplet pulsecircle $ pulseCircleGUI changeToRedGrid
        , windowChangeHapplet redgrid $ redGridGUI changeToPulseCircle
        )
  attachWindow True win pulsecircle $ pulseCircleGUI changeToRedGrid
  launchGUIEventLoop
  deleteWindow win

----------------------------------------------------------------------------------------------------

newtype RedGrid = RedGrid { theRedGridScale :: RealApprox }
  deriving (Eq, Ord, Show, Read)

redGridScale :: Lens' RedGrid RealApprox
redGridScale = lens theRedGridScale $ \ a b -> a{ theRedGridScale = b }

redGridDraw :: RealApprox -> PixSize -> CairoRender ()
redGridDraw scale = fmap realToFrac >>> \ size@(V2 w h) ->
  if scale <= 1 then clearScreen red else do
    let (V2 centerX centerY) = (/ 2.0) <$> size
    let around top center =
          (takeWhile (>=  0) $ [1 ..] >>= \ i -> [center - scale * i :: RealApprox]) ++
          (takeWhile (< top) $ [0 ..] >>= \ i -> [center + scale * i :: RealApprox])
    let mkLine v2 top i = Line2D (Point2D $ v2 i 0) (Point2D $ v2 i top)
    clearScreen (black & alphaChannel .~ 0.9)
    forM_ (around w centerX) $ drawLine red 1.0 . mkLine V2 h
    forM_ (around h centerY) $ drawLine red 1.0 . mkLine (flip V2) w

redGridGUI :: GtkGUI RedGrid () -> PixSize -> GtkGUI RedGrid ()
redGridGUI ctxSwitch _size = do
  let draw = use redGridScale >>= onView . redGridDraw
  resizeEvents $ const draw
  mouseEvents MouseButton $ \ (Mouse _ _ _ button _) -> case button of
    RightClick -> ctxSwitch
    _          -> do
      scale <- use redGridScale
      redGridScale .= if scale <= 4.0 then 64.0 else scale / 2.0
  draw

----------------------------------------------------------------------------------------------------

data PulseCircle
  = PulseCircle
    { thePulseCircleRadius     :: !Double
    , thePulseCirclePosition   :: !(V2 Double)
    , thePulseCircleWindowSize :: !PixSize
    }
  deriving (Eq, Ord, Show, Read)

pulseCircleRadius :: Lens' PulseCircle Double
pulseCircleRadius = lens thePulseCircleRadius $ \ a b -> a{ thePulseCircleRadius = b }

pulseCirclePosition :: Lens' PulseCircle (V2 Double)
pulseCirclePosition = lens thePulseCirclePosition $ \ a b -> a{ thePulseCirclePosition = b }

pulseCircleWindowSize :: Lens' PulseCircle PixSize
pulseCircleWindowSize = lens thePulseCircleWindowSize $ \ a b -> a{ thePulseCircleWindowSize = b }

type Radius  = Double
type PenSize = Double
type Red     = Double
type Green   = Double
type Blue    = Double
type Alpha   = Double

circle :: V2 Double -> Radius -> Red -> Green -> Blue -> Alpha -> CairoRender ()
circle (V2 x y) radius red green blue alpha = CairoRender $ do
  op <- Cairo.getOperator
  Cairo.setOperator Cairo.OperatorSource
  Cairo.setSourceRGBA red green blue alpha
  Cairo.arc (x + 0.5) (y + 0.5) radius 0.0 (2.0 * pi)
  Cairo.fill
  Cairo.setOperator op

pulseCircleGUI :: GtkGUI PulseCircle () -> PixSize -> GtkGUI PulseCircle ()
pulseCircleGUI ctxSwitch initSize@(V2 (SampCoord w) (SampCoord h)) = do
  -- Initialize the model such that the circle is placed in the middle of the window.
  pulseCirclePosition .= V2 (realToFrac w / 2) (realToFrac h / 2)
  pulseCircleWindowSize .= initSize

  -- Declare a function for drawing the model:
  let drawDot clear
        (PulseCircle{thePulseCircleRadius=oldR,thePulseCirclePosition=oldXY})
        (PulseCircle{thePulseCircleRadius=newR,thePulseCirclePosition=newXY}) = \ _size -> do
            when clear $ clearScreen (black & alphaChannel .~ 0.9)
            circle oldXY (max oldR newR + 1.0) 0.0  0.0  0.0  0.9
            circle newXY           newR        0.0  0.0  1.0  1.0

  -- Install the mouse event handling function.
  mouseEvents MouseDrag $ \ (Mouse _ _ _ button newXY) -> case button of
    RightClick -> ctxSwitch
    _          -> do
      old <- get
      pulseCirclePosition .= (realToFrac <$> newXY)
      new <- get
      onView $ drawDot False old new

  -- On resize, simply redraw the window without modifying the model. We don't need to use the new
  -- size information for the window.
  resizeEvents $ \ newSize@(V2 newW newH) -> do
    old <- get
    let (V2 oldW oldH) = old ^. pulseCircleWindowSize
    let (V2 x    y   ) = old ^. pulseCirclePosition
    let f n new old = n * realToFrac new / realToFrac old
    pulseCirclePosition   .= V2 (f x newW oldW) (f y newH oldH)
    pulseCircleWindowSize .= newSize
    new <- get
    onView $ drawDot True old new

  -- Install an animator thread which makes the radius of the circle as a function of time.
  stepFrameEvents $ \ t -> do
    old <- get
    pulseCircleRadius .= 20 * sin (2*pi * realToFrac t) + 40
    new <- get
    onView $ drawDot False old new

  -- Clear the window. If you don't do this, the behavior of what the initial window will look
  -- like is not defined in the Happlets protocol. It could be a default solid color, or it could
  -- be random garbage.
  model <- get
  onView $ drawDot True model model