-- Source code for "Declarative Event-Oriented Programming" paper module Deop where import IO import Win32 (setWindowText) import Fran import qualified StaticTypes as S ---- Functions for making new curve files. Do these first. saveSingle = save "single.crv" (sinPoints 1) saveDouble = save "double.crv" (sinPoints 2) saveTriple = save "triple.crv" (sinPoints 3) -- A control point is a Fran 2D point behavior and a boolean behavior -- saying whether grabbable. type CPoint = (Point2B, BoolB) -- The looks of a control point. This version suitable for gray-scale -- reproduction. It uses shape instead of color. renderCPoint :: CPoint -> ImageB renderCPoint (pos, excited) = moveTo pos ( stretch pointSize ( ifB excited (star 3 5) circle)) pointSize :: RealB pointSize = 0.07 -- Control point editor. From a start point, come up with a moving point -- under user control. User can grab with left button when the mouse is -- close enough (within two radii). editCPoint :: User -> S.Point2 -> CPoint editCPoint u p0 = (pos, closeEnough) where pos, lastRelease :: Point2B pos = ifB grabbing (mouse u) lastRelease lastRelease = stepper p0 (release `snapshot_` pos) closeEnough, grabbing :: BoolB closeEnough = distance2 pos (mouse u) <* grabDistance grabbing = stepper False (grab -=> True .|. release -=> False) grab, release :: Event () grab = lbp u `whenE` closeEnough release = lbr u `whenE` grabbing grabDistance :: RealB grabDistance = 2 * pointSize -- proximity for grabbing -- Try it out editCPointTest :: User -> ImageB editCPointTest u = renderCPoint (editCPoint u S.origin2) `over` whiteIm whiteIm = withColor white solidImage main1 = dispSmall editCPointTest renderCurve :: [CPoint] -> ImageB renderCurve cpoints = overs (map renderCPoint cpoints) `over` withColor blue (polyBezier (map fst cpoints)) editCurve :: [S.Point2] -> User -> [CPoint] editCurve initPoints u = map (editCPoint u) initPoints -- Simulated graph paper background graphPaper, graphPaper' :: ImageB graphPaper' = withColor lightBlue (horizontal `over` vertical) `over` whiteIm where horizontal = overs [ moveXY 0 (constantB y) hLine | y <- [-2, -2+spacing .. 2] ] vertical = turn (pi/2) horizontal hLine = lineSegment (point2XY (-2) 0) (point2XY 2 0) spacing = 0.2 -- I displayed graphPaper', screen-captured the result, and saved it to -- "graphPaper.bmp". Someday, Fran will optimize well enough to make this -- sort of thing unnecessary. Shameful hack: I put one black pixel in the -- upper left, so the white background wouldn't become transparent. -- Transparency should really be specifiable. -- -- Also (unrelated), the "line" primitive should be renamed "lineSegment", -- and there should really be an infinite "line", which`` exploits cropping. graphPaper = importBitmap "graphPaper.bmp" editor1 :: [S.Point2] -> User -> ImageB editor1 initPoints u = renderCurve (editCurve initPoints u) `over` graphPaper main2 = dispBig (editor1 (sinPoints 2)) -- Now add undo -- Polymorphic, unbounded "stack". Feed pushes and pop attempts in, and -- get successful pops out. stacker :: Event a -> Event () -> Event a stacker push tryPop = legitPop `snapshot_` headB stack where legitPop :: Event () legitPop = tryPop `whenE` notB (nullB stack) -- changeStack :: Event ([a] -> [a]) changeStack = legitPop -=> tail .|. push ==> (:) -- stack :: Behavior [a] stack = stepAccum [] changeStack -- This one has a problem (see text) editCPointUndo1 :: User -> S.Point2 -> CPoint editCPointUndo1 u p0 = (pos, closeEnough) where pos, lastRelease :: Point2B pos = ifB grabbing (mouse u) lastRelease lastRelease = stepper p0 (release `snapshot_` pos .|. undo) closeEnough, grabbing :: BoolB closeEnough = distance2 pos (mouse u) <* grabDistance grabbing = stepper False (grab -=> True .|. release -=> False) grab, release :: Event () grab = lbp u `whenE` closeEnough release = lbr u `whenE` grabbing grabPos, undo :: Event S.Point2 grabPos = grab `snapshot_` pos undo = stacker grabPos (charPress '\^Z' u) -- For visualizing the moving point and the undo stack. editCPointUndo1Test u = renderCPoint (editCPointUndo1 u S.origin2) `over` whiteIm main3 = dispSmall editCPointUndo1Test -- Fixed version. editCPointUndo :: User -> Event S.Point2 -> S.Point2 -> (CPoint, Event S.Point2) editCPointUndo u undo p0 = ((pos, closeEnough), grabPos) where pos = ifB grabbing (mouse u) lastRelease lastRelease = stepper p0 (release `snapshot_` pos .|. undo) closeEnough = distance2 pos (mouse u) <* grabDistance grabbing = stepper False (grab -=> True .|. release -=> False) grab = lbp u `whenE` closeEnough release = lbr u `whenE` grabbing grabPos = grab `snapshot_` pos -- Manage a shared undo stack that remembers which xPoint was grabbed and -- its grab position. type UndoRecord = (Int, S.Point2) editCurveUndo :: [S.Point2] -> User -> [CPoint] editCurveUndo initPoints u = cpoints where -- Tag and merge the CPoint grab events (defined below) curveGrab :: Event UndoRecord curveGrab = anyE (zipWith tag indices pointGrabs) where tag i e = e ==> (i `pair`) -- pair i with e's occurrence data indices = [1 .. length initPoints] -- The undo event: stack curve grabs and try to restore on control-Z's undo :: Event UndoRecord undo = stacker curveGrab (charPress '\^Z' u) -- Edit an indexed CPoint. editCP :: Int -> S.Point2 -> (CPoint, Event S.Point2) editCP i p0 = editCPointUndo u undoThis p0 where -- Undo if a point tagged i comes off the undo stack. Drop tag. undoThis = undo `suchThat` ((== i) . fst) ==> snd -- Apply editCP to corresponding indices and initial points, and split -- (unzip) the resulting cpoints and grabs into two lists. (cpoints, pointGrabs) = unzip (zipWith editCP indices initPoints) editorUndo :: [S.Point2] -> User -> ImageB editorUndo initPoints u = renderCurve (editCurveUndo initPoints u) `over` graphPaper main4 = dispBig (editorUndo (sinPoints 2)) -- Visual feedback to save request. -- Message spins & shrinks. spinMessage :: String -> Event a -> ImageB spinMessage message saveE = stretch saveSize $ turn saveAngle $ withColor (colorHSL saveAngle 0.5 0.5) $ stringIm message where saveDur = 1.5 -- artificial duration sinceSave = switcher saveDur (timeSinceE saveE) -- Fraction remaining (one down to zero) saveLeft = 0 `max` (saveDur - sinceSave)/saveDur saveSize = 2.5 * saveLeft saveAngle = 2 * pi * saveLeft timeSinceE :: Event a -> Event TimeB timeSinceE e = e `snapshot_` time ==> since where since t0 = time - constantB t0 -- Try it out: press any key to save spinMessageTest u = spinMessage "goodbye" (keyPressAny u) `over` whiteIm -- This editor version saves when "s" is -- pressed or window closed. editor2 :: String -> IO () editor2 fileName = withBig $ do initPoints <- load fileName window <- makeWindow setWindowText window ("Curve editor 3: " ++ fileName) displayExMon blue (editRenderSave initPoints) window eventLoop window where editRenderSave initPoints u = ( spinMessage "Saving ..." saveNow `over` renderCurve xPoints `over` graphPaper , doSave ) where xPoints = editCurve initPoints u ptsB = bListToListB (map fst xPoints) saveNow = charPress 's' u .|. quit u doSave = saveNow `snapshot_` ptsB ==> save fileName main5 = editor2 "double.crv" editCPointRel :: User -> S.Point2 -> CPoint editCPointRel u p0 = (pos, closeEnough) where pos = switcher (constantB p0) $ grab `snapshot` mouse u ==> relMotion .|. release ==> constantB where -- Oops: mouse u (space-time leak) relMotion (p, mp) = constantB p .+^ (mouse u .-. constantB mp) grab = lbp u `whenE` closeEnough `snapshot_` pos release = lbr u `whenE` grabbing `snapshot_` pos grabbing = stepper False ( grab -=> True .|. release -=> False) closeEnough = distance2 pos (mouse u) <* grabDistance ----- Load and save values load :: Read a => String -> IO a load fileName = do --putStrLn ("loading from " ++ show fileName) hFile <- openFile fileName ReadMode str <- hGetContents hFile return (read str) -- Note: no hClose. It will happen automatically on eof or gc. save :: Show a => String -> a -> IO () save fileName val = do --putStrLn ("saving to " ++ show fileName) hFile <- openFile fileName WriteMode hPutStr hFile (show val) hClose hFile ---- Curve generator sinPoints :: Int -> [S.Point2] sinPoints nCurves = take nPoints [S.point2XY t (0.5 * sin (2 * pi * t)) | t <- [-1, -1+eps .. ]] where eps = 2 / fromInt (nPoints-1) nPoints = 1 + 3 * nCurves ---- Window size settings withSmall = withInitialViewSize 1.5 1.5 withBig = withInitialViewSize 3.0 1.5 dispSmall = withSmall . displayUMon dispBig = withBig . displayUMon