Let’s study another example. In this application we are going to paint green and red circles on the screen. We paint in the mouse position when we press Right Button and we can change the color when we press the Left Button.
The complete code can be found in the directory: dyna-gloss/examples/Ball.hs
.
Initialise application
Let’s start with an empty screen and setup all the defaults:
module Main where
import Dyna.Gloss
-- | Screen specification
spec :: Spec
spec = defSpec { spec'display = InWindow "Main" (700, 700) (50, 50) }
-- | Main app
main = runApp spec $ pure pic
pic = mempty
This should draw an empty screen.
Draw a ball at the mouse position
This should be familiar from the tutorial. Let’s draw a filed circle at the mouse position. With it we indicate what we are going to paint:
-- | Ball: color, position
data Ball = Ball Color Vec
-- | Draw a Ball
draw :: Ball -> Picture
draw (Ball col pos) = translate pos $ color col $ circleSolid 25
-- | Ball at the mouse position
pointer :: Dyn Ball
pointer = liftA2 Ball ballColor mouse
-- | Colors alterate on mouse left clicks
ballColor :: Dyn Color
ballColor = hold green $ cycles [red, green] mouseLeft
pic = pointer
Here we also define the change of the color by mouse left-button clicks:
ballColor = hold green $ cycles [red, green] mouseLeft
The cycles
alternates between two values of the color on every
event of left button click. Also we use hold
to turn event stream
to continuous dynamic process.
Note how easy it was to construct the ball with applicative instance:
pointer :: Dyn Ball
pointer = liftA2 Ball ballColor mouse
We map with Ball
constructor over dynamic color and dynamic position.
We can see a paint pointer that can change the color.
How can we draw balls on the screen?
Drawing the balls
To do that on every Mouse Right Button press we will save the current pointer to the list of balls. And on every frame of animation we will draw not only pointer but also all the balls which were saved to the list.
Note that order of painting of the balls matters.
For better user experience instead of list we use Sequence
which allows fast appending to the tail:
import Data.Sequence (Seq)
import qualified Data.Sequence as Seq
-- | Balls are saved to the sequence and redrawn on each frame also we draw the pointer.
-- Note that order of storage affects the drawing. Later pictures go in the foreground.
balls :: Dyn (Seq Ball)
balls =
liftA2 (Seq.|>)
(scanD (flip (Seq.|>)) Seq.empty $ snap pointer mouseRight)
pointer
In the function balls
we save all balls including the pointer
to the sequence. We accumulate the balls with function scanD
.
We snap
every mouse right click with current value of the pointer
then accumulate all the balls with scanD
.
Let’s draw a complete picture:
-- | Main app
main = runApp spec $ pure $ foldMap draw <$> balls
Counting the balls
Let’s add the text message which will count all the balls:
-- | Count the mouse clicks so far (so many balls we have placed on the screen)
countBalls :: Dyn Picture
countBalls = text . show <$> (hold 0 $ count mouseRight)
-- | Main app
main = runApp spec $ pure $
(foldMap draw <$> balls) <> countBalls
We know that each mouse right button click increments the number of the balls on the screen so we count the number of mouse clicks.
<=
Introduction=>
Quick reference- Up: Table of contents