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-processing/examples/Ball.hs
.
Init application
Let’s start with an empty screen and setup all the defaults:
module Main where
import Dyna.Proc
-- | Screen specification
spec :: Spec
spec = Spec $ size (P2 700 700)
-- | Main app
main = runApp spec $ pure (initDraw <> pic)
where
initDraw = background white
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 Col P2
-- | Draw a Ball
draw :: Ball -> Draw
draw (Ball col pos) = do
strokeFill col
circle 25 pos
-- | Ball at the mouse position
pointer :: Dyn Ball
pointer = liftA2 Ball ballCol mouse
-- | Cols alterate on mouse left clicks
ballCol :: Dyn Col
ballCol = hold green $ cycles [red, green] mouseLeft
pic = pointer
Here we also define the change of the color by mouse left-button clicks:
ballCol = hold green $ cycles [red, green] mouseLeft
The cycles
alterates 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 ballCol mouse
We map with Ball
constructor over dynamic color and dynamic position.
Ok 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 $
(mappend initDraw . foldMap draw <$> balls) -- <> countBalls
where
initDraw = background white
This completes the example.
<=
Introduction=>
Quick reference- Up: Table of contents