[GUI] Re: fruit demo
Antony Courtney
antony@apocalypse.org
Mon, 03 Feb 2003 18:27:28 -0500
This is a multi-part message in MIME format.
--------------070607090600000707000600
Content-Type: text/plain; charset=us-ascii; format=flowed
Content-Transfer-Encoding: 7bit
Hi Daan,
Daan Leijen wrote:
> Hi Anthony,
>
> Very interesting to see your program -- it is neat how one
> can separate the model from the interface! (allthough the wiring
> still seems a bit convoluted.)
Hmmm. Can you expand on why you think the wiring is a bit convoluted?
Is it the syntax that you find difficult, or the conceptual framework of
signal functions?
> I don't know if you have seen Koen Claessens bouncing balls
> demo, but I would be quite interested to see how that would look in
> Fruit (I think that especially the behaviour of the balls can be modelled nicer
> in Fruit). See http://www.cs.chalmers.se/Cs/Grundutb/Kurser/afp/yahu.html
> for the demo.
I hadn't seen Koen's demo, but I had already done something quite
similar. If you think the previous code looked convoluted, you ain't
seen nothin' yet.... :-)
The attached file contains a few increasingly sophisticated versions of
the bouncing balls demo. Most versions use the pSwitch family of
combinators to maintain a dynamic collection of bouncing balls. The
user can spawn a new ball at any location using the mouse, and the balls
bounce off of each other as well as off of the walls. One version also
includes self-termination, where each ball only "lives" for 5 seconds
after it is spawned.
The pSwitch combinators are described in the paper:
Henrik Nilsson, Antony Courtney and John Peterson, Functional
Reactive Programming, Continued. In Proceedings of the Haskell
Workshop, September, 2002.
which is available here:
http://apocalypse.org/~antony/pubs/genuinely-functional-guis.pdf
regards,
-antony
--
Antony Courtney
Grad. Student, Dept. of Computer Science, Yale University
antony@apocalypse.org http://www.apocalypse.org/pub/u/antony
--------------070607090600000707000600
Content-Type: text/plain;
name="BallTests.as"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="BallTests.as"
--
-- BallTests.as -- animated bouncing balls to illustrate the basics
-- of using pSwitch to switch over dynamic collections.
--
module BallTests where
import AFRP
import Haven
import GUI
import FMUI
-- hmmm. should probably put this in HavenCoreUtils:
rpath :: Point -> Double -> Double -> Path
rpath pt w h = outline $ rectangle pt w h
-- walls:
vWalls :: Path
vWalls = rpath (point 0 0) 10 300
<++> rpath (point 290 0) 10 300
hWalls :: Path
hWalls = rpath (point 0 290) 300 10
<++> rpath (point 0 0) 300 10
wallPath :: Path
wallPath = hWalls <++> vWalls
wallPic :: Picture
wallPic = withColor red $ picFill wallPath
wallGUI :: GUI () ()
wallGUI = constant (wallPic,())
ballShape :: Path
ballShape = outline (circle origin 25)
ballPic :: Picture
ballPic = withColor green (picFill ballShape)
-- A simple ball bouncing inside the walls:
simpleBall :: Point -> Double -> SF () (Path)
simpleBall pt0 vel = proc _ -> do
rec xi <- integral -< xvel
yi <- integral -< yvel
let xpos = (pointX pt0) + xi
let ypos = (pointY pt0) + yi
let ball = translate (vector xpos ypos) %$ ballShape
let bbounds = bounds ball
xbounce <- edge -< intersectsRect vWalls bbounds
ybounce <- edge -< intersectsRect hWalls bbounds
xvel <- accumHold vel -< xbounce `tag` negate
yvel <- accumHold vel -< ybounce `tag` negate
returnA -< ball
simpleBallGUI :: Point -> Double -> GUI () Path
simpleBallGUI pt0 vel = proc _ -> do
b <- simpleBall pt0 vel -< ()
let bpic = withColor green $ picFill b
returnA -< (bpic,b)
sbGUI :: GUI () ()
sbGUI = guiDropIO $ simpleBallGUI (point 75 120) 120
-- just sbTest in front of walls:
dynTest0 :: GUI () ()
dynTest0 =
let wallGUI :: GUI () ()
wallGUI = constGUI wallPic
in guiDropIO $ sbGUI `overGUI` wallGUI
-- first: let's just try a version of dynTest0 which is a singleton
-- collection used in rpSwitch:
dynTest1 :: GUI () ()
dynTest1 = proc (gin,_) -> do
neChild <- never -< ()
pps <- rpSwitchB [dynTest0] -< ((gin,()),neChild)
let pic = foldr (\ (p,_) bg -> p <++> bg) picEmpty pps
returnA -< (pic,())
-- now let's try to spawn a ball under the mouse on lbp:
spawnTest1 :: GUI () ()
spawnTest1 = proc (gin,_) -> do
lbPress <- ginLbp -< gin
mouse <- ginMouse -< gin
let forkE = lbPress `tag` ((guiDropIO $ simpleBallGUI mouse 120):)
pps <- rpSwitchB [dynTest0] -< ((gin,()),forkE)
let pic = foldr (\ (p,_) bg -> p <++> bg) wallPic pps
returnA -< (pic,())
-- a variation on simpleBallGUI that terminates after 5 seconds:
--
-- could make a more general-purpose wrapper that would
-- terminate ANY signal function after N seconds...
--
termBallGUI :: Point -> Double -> GUI a (Event ())
termBallGUI pt0 vel = proc (gin,_) -> do
t <- time -< ()
done <- edge -< (t > 5)
(bpic,_) <- simpleBallGUI pt0 vel -< (gin,())
returnA -< (bpic,done)
-- now let's try a version of spawnTest that allows for self-termination:
-- We use pSwitch here. Note, however, that a switching Event may occur
-- either because we are adding a new ball to the collection, or because
-- some of the balls have terminated. We account for this by using the
-- type (Maybe (GUI () Event ()),[Bool]) on the event occurence. The
-- first component of the pair is (Just b) if we spawn a new ball b,
-- and the second component is the indices of balls to remove from
-- the collection.
-- compute spawn or kill events for spawnTest2:
spawnOrKill :: SF ((GUIInput,()),[(Picture,Event ())])
(Event ((Maybe (GUI () (Event ()))),[Bool]))
spawnOrKill = proc ((gin,_),pes) -> do
lbPress <- ginLbp -< gin
mouse <- ginMouse -< gin
let spawnE = lbPress `tag` (Just (termBallGUI mouse 120),repeat False)
let termEs = map snd pes
let killE = (mergeEvents termEs) `tag` (Nothing,map isEvent termEs)
returnA -< mergeBy (\ (x,_) (_,y) -> (x,y)) spawnE killE
-- A simple auxiliary filtering function:
dropSome :: [a] -> [Bool] -> [a]
dropSome as bs = [ a | (a,b) <- zip as bs, not b ]
-- given the current World (a list of TermBallGUI's), and a
-- (spawn,kill) pair, compute a new World:
nextWorld :: [GUI () (Event ())]
-> (Maybe (GUI () (Event ())),[Bool])
-> SF (GUIInput,()) [(Picture,Event ())]
nextWorld world (Nothing,termEs) =
let world' = dropSome world termEs
in pSwitchB world' spawnOrKill nextWorld
nextWorld world (Just b,termEs) =
let world' = b:(dropSome world termEs)
in pSwitchB world' spawnOrKill nextWorld
spawnTest2 :: GUI () ()
spawnTest2 =
let world0 = [termBallGUI (point 100 100) 120]
in proc (gin,_) -> do
pes <- pSwitchB world0 spawnOrKill nextWorld -< (gin,())
let pic = foldr (\ (p,_) bg -> p <++> bg) wallPic pes
returnA -< (pic,())
-- A ball that changes direction whenever it hits another ball:
-- like simpleBallGUI, but takes an input event whose occurence
-- indicates a collision:
hitBall :: Point -> Double -> SF (Event ()) (Path)
hitBall pt0 vel = proc hitE -> do
rec xi <- integral -< xvel
yi <- integral -< yvel
let xpos = (pointX pt0) + xi
let ypos = (pointY pt0) + yi
let ball = translate (vector xpos ypos) %$ ballShape
let bbounds = bounds ball
xHitWallE <- edge -< intersectsRect vWalls bbounds
yHitWallE <- edge -< intersectsRect hWalls bbounds
let xbounce = xHitWallE `lMerge` hitE
let ybounce = yHitWallE `lMerge` hitE
xvel <- accumHold vel -< xbounce `tag` negate
yvel <- accumHold vel -< ybounce `tag` negate
returnA -< ball
hitBallGUI :: Point -> Double -> GUI (Event ()) Path
hitBallGUI pt0 vel = proc (_,hitE) -> do
b <- hitBall pt0 vel -< hitE
let bpic = withColor green $ picFill b
returnA -< (bpic,b)
termHitBallGUI :: Point -> Double -> GUI (Event ()) (Path,Event ())
termHitBallGUI pt0 vel = proc (gin,hitE) -> do
t <- time -< ()
done <- edge -< (t > 5)
(pic,path) <- hitBallGUI pt0 vel -< (gin,hitE)
returnA -< (pic,(path,done))
-- A WorldBall is a Ball that observes a World. However, we'll arrange
-- it so that every ball sees all other balls but itself.
worldBallGUI :: Point -> Double -> GUI [Path] Path
worldBallGUI pt0 vel = proc (gin,others) -> do
rec (bpic,b) <- hitBallGUI pt0 vel -< (gin,hitE)
let bbounds = bounds b
let touches = any (\ p -> intersectsRect p bbounds) others
hitE <- edge -< touches
returnA -< (bpic,b)
-- Given a World of length n, construct n localized perceptions of the
-- world, in which each member does not see himself.
locPercept :: [a] -> [[a]]
locPercept world = map (\i -> dropNth i world) [0..(length world)-1]
dropNth :: Int -> [a] -> [a]
dropNth n as =
let (pre,suf) = splitAt n as
in pre ++ (drop 1 suf)
-- An implementation of the bouncing balls where each ball bounces off the
-- walls and off all other balls. Note that each balls has a localized
-- perception of the world in which it observes all other balls but does
-- not see itself.
-- other balls but itself.
--
-- The implementation here is like spawnTest1, but using rpSwitchZ
-- instead of rpSwitchB:
spawnTest3 :: GUI () ()
spawnTest3 = proc (gin,_) -> do
rec lbPress <- ginLbp -< gin
mouse <- ginMouse -< gin
let forkE = lbPress `tag` ((worldBallGUI mouse 120):)
pps <- rpSwitchZ [worldBallGUI (point 100 100) 120]
-< (zip (repeat gin) locWorlds, forkE)
let locWorlds = locPercept (map snd pps)
let pic = foldr (\ (p,_) bg -> p <++> bg) wallPic pps
returnA -< (pic,())
--------------070607090600000707000600--