packages feed

codeworld-api 0.4.0 → 0.5.0

raw patch · 9 files changed

+858/−175 lines, 9 filesdep +witherabledep ~blank-canvasdep ~reflexdep ~timePVP ok

version bump matches the API change (PVP)

Dependencies added: witherable

Dependency ranges changed: blank-canvas, reflex, time

API changes (from Hackage documentation)

+ CodeWorld: clipped :: HasCallStack => Double -> Double -> Picture -> Picture
+ CodeWorld: reflected :: HasCallStack => Double -> Picture -> Picture
+ CodeWorld: reflectedPoint :: Double -> Point -> Point
+ CodeWorld.Image: image :: HasCallStack => Text -> Text -> Double -> Double -> Picture
+ CodeWorld.Parameter: constant :: Text -> Double -> Parameter
+ CodeWorld.Parameter: converted :: (Double -> Double) -> Parameter -> Parameter
+ CodeWorld.Parameter: counter :: Text -> Parameter
+ CodeWorld.Parameter: currentHour :: Parameter
+ CodeWorld.Parameter: currentMinute :: Parameter
+ CodeWorld.Parameter: currentSecond :: Parameter
+ CodeWorld.Parameter: data Parameter
+ CodeWorld.Parameter: parametricDrawingOf :: [Parameter] -> ([Double] -> Picture) -> IO ()
+ CodeWorld.Parameter: random :: Text -> Parameter
+ CodeWorld.Parameter: renamed :: Text -> Parameter -> Parameter
+ CodeWorld.Parameter: slider :: Text -> Parameter
+ CodeWorld.Parameter: timer :: Text -> Parameter
+ CodeWorld.Parameter: toggle :: Text -> Parameter
- CodeWorld.Reflex: class (Reflex t, MonadHold t m, MonadFix m, PerformEvent t m, Adjustable t m, MonadIO (Performable m), PostBuild t m) => ReflexCodeWorld t m | m -> t
+ CodeWorld.Reflex: class (Reflex t, Adjustable t m, MonadHold t m, NotReady t m, PostBuild t m, PerformEvent t m, TriggerEvent t m, MonadFix m, MonadIO m, MonadIO (Performable m)) => ReflexCodeWorld t m | m -> t
- CodeWorld.Reflex: reflexOf :: (forall t m. (Reflex t, MonadHold t m, MonadFix m, PerformEvent t m, MonadIO (Performable m), Adjustable t m, PostBuild t m) => ReactiveInput t -> m (Dynamic t Picture)) -> IO ()
+ CodeWorld.Reflex: reflexOf :: (forall t m. (Reflex t, MonadHold t m, MonadFix m, TriggerEvent t m, PerformEvent t m, MonadIO m, MonadIO (Performable m), Adjustable t m, NotReady t m, PostBuild t m) => ReactiveInput t -> m (Dynamic t Picture)) -> IO ()

Files

codeworld-api.cabal view
@@ -1,5 +1,5 @@ Name:                codeworld-api-Version:             0.4.0+Version:             0.5.0 Synopsis:            Graphics library for CodeWorld License:             Apache License-file:        LICENSE@@ -33,6 +33,8 @@   Exposed-modules:     CodeWorld,                        CodeWorld.App,                        CodeWorld.App2,+                       CodeWorld.Image,+                       CodeWorld.Parameter,                        CodeWorld.Reflex,                        CodeWorld.Sketches   Other-modules:       CodeWorld.CanvasM,@@ -59,9 +61,11 @@                        mtl                  >= 2.2.1 && < 2.3,                        random               >= 1.1   && < 1.2,                        ref-tf               >= 0.4   && < 0.5,-                       reflex               >= 0.6.2.3   && < 0.7,+                       reflex               >= 0.6.3 && < 0.7,                        template-haskell     >= 2.8   && < 2.15,-                       text                 >= 1.2.2 && < 1.3+                       text                 >= 1.2.2 && < 1.3,+                       time                 >= 1.8   && < 2.0,+                       witherable           >= 0.3   && < 0.4    if impl(ghcjs)     Js-sources:        jsbits/sim_fp.js@@ -116,9 +120,11 @@                        mtl                  >= 2.2.1 && < 2.3,                        random               >= 1.1   && < 1.2,                        ref-tf               >= 0.4   && < 0.5,-                       reflex               >= 0.6   && < 0.7,+                       reflex               >= 0.6.3 && < 0.7,                        template-haskell     >= 2.8   && < 2.15,-                       text                 >= 1.2.2 && < 1.3+                       text                 >= 1.2.2 && < 1.3,+                       time                 >= 1.8   && < 2.0,+                       witherable           >= 0.3   && < 0.4    if impl(ghcjs)     Js-sources:        jsbits/sim_fp.js@@ -129,7 +135,7 @@                        ghcjs-dom             >= 0.8 && < 0.9,                        transformers   else-    Build-depends:     blank-canvas          >= 0.6 && < 0.7,+    Build-depends:     blank-canvas          >= 0.6 && < 0.8,                        time                  >= 1.6.0 && < 1.9    Ghc-options:         -O2
src/CodeWorld.hs view
@@ -66,6 +66,8 @@     scaled,     dilated,     rotated,+    reflected,+    clipped,     pictures,     (<>),     (&),@@ -74,6 +76,7 @@     Point,     translatedPoint,     rotatedPoint,+    reflectedPoint,     scaledPoint,     dilatedPoint,     Vector,
src/CodeWorld/CanvasM.hs view
@@ -76,6 +76,7 @@            (Double, Double) -> (Double, Double) -> (Double, Double) -> m ()     arc :: Double -> Double -> Double -> Double -> Double -> Bool -> m ()     rect :: Double -> Double -> Double -> Double -> m ()+    clip :: m ()     fill :: m ()     stroke :: m ()     fillRect :: Double -> Double -> Double -> Double -> m ()@@ -192,6 +193,7 @@         CanvasM (const (Canvas.bezierCurveTo x1 y1 x2 y2 x3 y3))     arc x y r a1 a2 dir = CanvasM (const (Canvas.arc x y r a1 a2 dir))     rect x y w h = CanvasM (const (Canvas.rect x y w h))+    clip = CanvasM (const Canvas.clip)     fill = CanvasM (const Canvas.fill)     stroke = CanvasM (const Canvas.stroke)     fillRect x y w h = CanvasM (const (Canvas.fillRect x y w h))@@ -301,6 +303,7 @@     arc x y r a1 a2 dir = liftCanvas $         Canvas.arc (x, y, r, a1, a2, dir)     rect x y w h = liftCanvas $ Canvas.rect (x, y, w, h)+    clip = liftCanvas $ Canvas.clip ()     fill = liftCanvas $ Canvas.fill ()     stroke = liftCanvas $ Canvas.stroke ()     fillRect x y w h = liftCanvas $ Canvas.fillRect (x, y, w, h)
src/CodeWorld/DrawState.hs view
@@ -73,6 +73,17 @@     e     f +reflectDS :: Double -> DrawState -> DrawState+reflectDS th = mapDSAT $ \(AffineTransformation a b c d e f) ->+  AffineTransformation+    (a * cos r + c * sin r)+    (b * cos r + d * sin r)+    (a * sin r - c * cos r)+    (b * sin r - d * cos r)+    e+    f+  where r = 2 * th+ setColorDS :: Color -> DrawState -> DrawState setColorDS col = mapDSColor $ \mcol ->   case (col, mcol) of
src/CodeWorld/Driver.hs view
@@ -1,6 +1,5 @@ {-# OPTIONS_GHC -Wno-name-shadowing -Wno-orphans -Wno-unticked-promoted-constructors #-} -{-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveGeneric #-}@@ -10,7 +9,6 @@ {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE JavaScriptFFI #-}-{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternGuards #-} {-# LANGUAGE RankNTypes #-}@@ -53,6 +51,7 @@ import Data.Bool import Data.Char (chr) import Data.Dependent.Sum+import Data.Foldable import Data.IORef import Data.List (zip4, intercalate) import Data.Maybe@@ -60,6 +59,7 @@ import Data.Serialize.Text () import Data.Text (Text) import qualified Data.Text as T+import Data.Witherable import GHC.Fingerprint.Type import GHC.Generics import GHC.Stack@@ -156,15 +156,16 @@ viaOffscreen (RGBA r g b a) pic = do     w <- CM.getScreenWidth     h <- CM.getScreenHeight-    img <- CM.newImage (round w) (round h)-    CM.withImage img $ do-        setupScreenContext (round w) (round h)-        pic (RGBA r g b 1)-    CM.saveRestore $ do-        px <- pixelSize-        CM.scale px (-px)-        CM.globalAlpha a-        CM.drawImage img (round (-w/2)) (round (-h/2)) (round w) (round h)+    when (w > 0.5 && h > 0.5) $ do+        img <- CM.newImage (round w) (round h)+        CM.withImage img $ do+            setupScreenContext (round w) (round h)+            pic (RGBA r g b 1)+        CM.saveRestore $ do+            px <- pixelSize+            CM.scale px (-px)+            CM.globalAlpha a+            CM.drawImage img (round (-w/2)) (round (-h/2)) (round w) (round h)  followPath :: MonadCanvas m => [Point] -> Bool -> Bool -> m () followPath [] _ _ = return ()@@ -172,7 +173,7 @@ followPath ((sx, sy):ps) closed False = do     CM.moveTo (sx, sy)     forM_ ps $ \(x, y) -> CM.lineTo (x, y)-    when closed $ CM.closePath+    when closed CM.closePath followPath [p1, p2] False True = followPath [p1, p2] False False followPath ps False True = do     let [p1@(x1, y1), p2@(x2, y2), p3@(x3, y3)] = take 3 ps@@ -243,7 +244,7 @@  fillFigure :: MonadCanvas m => DrawState -> m () -> m () fillFigure ds figure = do-    withDS ds $ figure+    withDS ds figure     applyColor ds     CM.fill @@ -281,6 +282,10 @@ drawPicture (Scale _ x y p) ds = drawPicture p (scaleDS x y ds) drawPicture (Dilate _ k p) ds = drawPicture p (scaleDS k k ds) drawPicture (Rotate _ r p) ds = drawPicture p (rotateDS r ds)+drawPicture (Reflect _ r p) ds = drawPicture p (reflectDS r ds)+drawPicture (Clip _ x y p) ds = do+    withDS ds $ followPath (rectangleVertices x y) True False+    CM.saveRestore $ CM.clip >> drawPicture p ds drawPicture (Pictures _ ps) ds = forM_ (reverse ps) $ \p -> drawPicture p ds drawPicture (PictureAnd _ ps) ds = forM_ (reverse ps) $ \p -> drawPicture p ds @@ -314,6 +319,10 @@ pictureContains (Scale _ x y p) ds pt = pictureContains p (scaleDS x y ds) pt pictureContains (Dilate _ k p) ds pt = pictureContains p (scaleDS k k ds) pt pictureContains (Rotate _ r p) ds pt = pictureContains p (rotateDS r ds) pt+pictureContains (Reflect _ r p) ds pt = pictureContains p (reflectDS r ds) pt+pictureContains (Clip _ x y p) ds pt =+    (&&) <$> polygonContains (rectangleVertices x y) False ds pt+         <*> pictureContains p ds pt pictureContains (Pictures _ ps) ds pt = orM [pictureContains p ds pt | p <- ps] pictureContains (PictureAnd _ ps) ds pt = orM [pictureContains p ds pt | p <- ps] @@ -328,6 +337,8 @@ isSimplePic (Scale _ _ _ p) = isSimplePic p isSimplePic (Dilate _ _ p) = isSimplePic p isSimplePic (Rotate _ _ p) = isSimplePic p+isSimplePic (Reflect _ _ p) = isSimplePic p+isSimplePic (Clip _ _ _ p) = isSimplePic p isSimplePic (Color _ c p) = not (isOpaque c) || isSimplePic p isSimplePic _ = True @@ -353,7 +364,7 @@     CM.isPointInStroke p  drawSector :: MonadCanvas m => Double -> Double -> Double -> DrawState -> m ()-drawSector b e r ds = do+drawSector b e r ds =     fillFigure ds $ CM.arc 0 0 (abs r) b e (b > e) >> CM.lineTo (0, 0)  sectorContains :: MonadCanvas m => Double -> Double -> Double -> DrawState -> Point -> m Bool@@ -362,7 +373,7 @@     CM.isPointInPath p  drawArc :: MonadCanvas m => Double -> Double -> Double -> Double -> DrawState -> m ()-drawArc b e r w ds = do+drawArc b e r w ds =     drawFigure ds w $ CM.arc 0 0 (abs r) b e (b > e)  arcContains :: MonadCanvas m => Double -> Double -> Double -> Double -> DrawState -> Point -> m Bool@@ -418,7 +429,7 @@         CM.fillRect (-w/2) (-h/2) w h         CM.globalCompositeOperation "destination-in"         withDS ds $ do-            CM.scale (1) (-1)+            CM.scale 1 (-1)             CM.drawImgURL name url imgw imgh  imageContains :: MonadCanvas m => Text -> Text -> Double -> Double -> DrawState -> Point -> m Bool@@ -495,18 +506,12 @@ getChildNodes (Scale _ _ _ p) = [p] getChildNodes (Dilate _ _ p) = [p] getChildNodes (Rotate _ _ p) = [p]+getChildNodes (Reflect _ _ p) = [p]+getChildNodes (Clip _ _ _ p) = [p] getChildNodes (Pictures _ ps) = ps getChildNodes (PictureAnd _ ps) = ps getChildNodes _ = [] -getRootTransform :: Picture -> DrawState -> DrawState-getRootTransform (Color _ c _) = setColorDS c-getRootTransform (Translate _ x y _) = translateDS x y-getRootTransform (Scale _ x y _) = scaleDS x y-getRootTransform (Dilate _ k _) = scaleDS k k-getRootTransform (Rotate _ r _) = rotateDS r-getRootTransform _ = id- findTopShape :: MonadCanvas m => DrawState -> Picture -> Double -> Double -> m (Maybe NodeId) findTopShape ds pic x y = do     (found, n) <- searchSingle ds pic x y@@ -514,20 +519,42 @@         then Just (NodeId n)         else Nothing   where-    searchSingle ds pic x y = case getChildNodes pic of-        [] -> do-            contained <- pictureContains pic ds (x, y)-            case contained of-                True -> return (True, 0)-                False -> return (False, 1)-        pics -> fmap (+ 1) <$> searchMulti (getRootTransform pic ds) pics x y+    searchSingle ds (Color _ _ p) x y =+        fmap (+ 1) <$> searchSingle ds p x y+    searchSingle ds (Translate _ dx dy p) x y =+        fmap (+ 1) <$> searchSingle (translateDS dx dy ds) p x y+    searchSingle ds (Scale _ sx sy p) x y =+        fmap (+ 1) <$> searchSingle (scaleDS sx sy ds) p x y+    searchSingle ds (Dilate _ k p) x y =+        fmap (+ 1) <$> searchSingle (scaleDS k k ds) p x y+    searchSingle ds (Rotate _ a p) x y =+        fmap (+ 1) <$> searchSingle (rotateDS a ds) p x y+    searchSingle ds (Reflect _ a p) x y =+        fmap (+ 1) <$> searchSingle (reflectDS a ds) p x y+    searchSingle ds (Clip _ w h p) x y = do+        inClip <- polygonContains (rectangleVertices w h) False ds (x, y)+        fmap (+ 1) <$> if inClip+                       then searchSingle ds p x y+                       else return (False, countNodes p)+    searchSingle ds (Pictures _ ps) x y =+        fmap (+ 1) <$> searchMulti ds ps x y+    searchSingle ds (PictureAnd _ ps) x y =+        fmap (+ 1) <$> searchMulti ds ps x y+    searchSingle ds p x y = do+        contained <- pictureContains p ds (x, y)+        if contained+          then pure (True, 0)+          else pure (False, 1)+     searchMulti _ [] _ _ = return (False, 0)     searchMulti ds (pic:pics) x y = do         (found, count) <- searchSingle ds pic x y-        case found of-            True -> return (True, count)-            False -> fmap (+ count) <$> searchMulti ds pics x y+        if found+          then pure (True, count)+          else fmap (+ count) <$> searchMulti ds pics x y +    countNodes p = 1 + sum (map countNodes (getChildNodes p))+ -- If a picture is found, the result will include an array of the base picture -- and all transformations. findTopShapeFromPoint :: MonadCanvas m => Point -> Picture -> m (Maybe NodeId)@@ -542,12 +569,12 @@ trim :: Int -> String -> String trim x y   | x >= length y = y-  | otherwise = take mid y ++ "..." ++ (reverse $ take mid $ reverse y)+  | otherwise = take mid y ++ "..." ++ reverse (take mid $ reverse y)   where mid = (x - 3) `div` 2 -showFloat :: Double -> String-showFloat x-  | haskellMode && x < 0 = "(" ++ result ++ ")"+showFloat :: Bool -> Double -> String+showFloat needNegParens x+  | needNegParens && x < 0 = "(" ++ result ++ ")"   | otherwise = result   where result = stripZeros (showFFloatAlt (Just 4) x "")         stripZeros = reverse . dropWhile (== '.') . dropWhile (== '0') . reverse@@ -556,7 +583,7 @@ showPoints pts =     "[" ++     intercalate ", " [-        "(" ++ showFloat x ++ ", " ++ showFloat y ++ ")"+        "(" ++ showFloat False x ++ ", " ++ showFloat False y ++ ")"         | (x, y) <- pts     ] ++     "]"@@ -574,30 +601,34 @@   | c == pink = "pink"   | c == purple = "purple"   | c == gray = "gray"-  | haskellMode, a == 1 = printf "(RGB %s %s %s)" (showFloat r) (showFloat g) (showFloat b)-  | a == 1 = printf "RGB(%s, %s, %s)" (showFloat r) (showFloat g) (showFloat b)-  | haskellMode = printf "(RGBA %s %s %s %s)" (showFloat r) (showFloat g) (showFloat b) (showFloat a)-  | otherwise = printf "RGBA(%s, %s, %s, %s)" (showFloat r) (showFloat g) (showFloat b) (showFloat a)+  | haskellMode, a == 1 =+      printf "(RGB %s %s %s)" (showFloat True r) (showFloat True g) (showFloat True b)+  | a == 1 =+      printf "RGB(%s, %s, %s)" (showFloat False r) (showFloat False g) (showFloat False b)+  | haskellMode =+      printf "(RGBA %s %s %s %s)" (showFloat True r) (showFloat True g) (showFloat True b) (showFloat True a)+  | otherwise =+      printf "RGBA(%s, %s, %s, %s)" (showFloat False r) (showFloat False g) (showFloat False b) (showFloat False a)  describePicture :: Picture -> String describePicture (Rectangle _ w h)-  | haskellMode = printf "rectangle %s %s" (showFloat w) (showFloat h)-  | otherwise   = printf "rectangle(%s, %s)" (showFloat w) (showFloat h)+  | haskellMode = printf "rectangle %s %s" (showFloat True w) (showFloat True h)+  | otherwise   = printf "rectangle(%s, %s)" (showFloat False w) (showFloat False h) describePicture (SolidRectangle _ w h)-  | haskellMode = printf "solidRectangle %s %s" (showFloat w) (showFloat h)-  | otherwise   = printf "solidRectangle(%s, %s)" (showFloat w) (showFloat h)+  | haskellMode = printf "solidRectangle %s %s" (showFloat True w) (showFloat True h)+  | otherwise   = printf "solidRectangle(%s, %s)" (showFloat False w) (showFloat False h) describePicture (ThickRectangle _ lw w h)-  | haskellMode = printf "thickRectangle %s %s %s" (showFloat lw) (showFloat w) (showFloat h)-  | otherwise   = printf "thickRectangle(%s, %s, %s)" (showFloat w) (showFloat h) (showFloat lw)+  | haskellMode = printf "thickRectangle %s %s %s" (showFloat True lw) (showFloat True w) (showFloat True h)+  | otherwise   = printf "thickRectangle(%s, %s, %s)" (showFloat False w) (showFloat False h) (showFloat False lw) describePicture (Circle _ r)-  | haskellMode = printf "circle %s" (showFloat r)-  | otherwise   = printf "circle(%s)" (showFloat r)+  | haskellMode = printf "circle %s" (showFloat True r)+  | otherwise   = printf "circle(%s)" (showFloat False r) describePicture (SolidCircle _ r)-  | haskellMode = printf "solidCircle %s" (showFloat r)-  | otherwise   = printf "solidCircle(%s)" (showFloat r)+  | haskellMode = printf "solidCircle %s" (showFloat True r)+  | otherwise   = printf "solidCircle(%s)" (showFloat False r) describePicture (ThickCircle _ lw r)-  | haskellMode = printf "thickCircle %s %s" (showFloat lw) (showFloat r)-  | otherwise   = printf "thickCircle(%s, %s)" (showFloat r) (showFloat lw)+  | haskellMode = printf "thickCircle %s %s" (showFloat True lw) (showFloat True r)+  | otherwise   = printf "thickCircle(%s, %s)" (showFloat False r) (showFloat False lw) describePicture (SolidPolygon _ pts)   | haskellMode = printf "solidPolygon %s" (showPoints pts)   | otherwise   = printf "solidPolygon(%s)" (showPoints pts)@@ -608,35 +639,35 @@   | haskellMode = printf "polygon %s" (showPoints pts)   | otherwise   = printf "polygon(%s)" (showPoints pts) describePicture (ThickPolygon _ pts w)-  | haskellMode = printf "thickPolygon %s %s" (showFloat w) (showPoints pts)-  | otherwise   = printf "thickPolygon(%s, %s)" (showPoints pts) (showFloat w)+  | haskellMode = printf "thickPolygon %s %s" (showFloat True w) (showPoints pts)+  | otherwise   = printf "thickPolygon(%s, %s)" (showPoints pts) (showFloat False w) describePicture (ClosedCurve _ pts)   | haskellMode = printf "closedCurve %s" (showPoints pts)   | otherwise   = printf "closedCurve(%s)" (showPoints pts) describePicture (ThickClosedCurve _ pts w)-  | haskellMode = printf "thickClosedCurve %s %s" (showFloat w) (showPoints pts)-  | otherwise   = printf "thickClosedCurve(%s, %s)" (showPoints pts) (showFloat w)+  | haskellMode = printf "thickClosedCurve %s %s" (showFloat True w) (showPoints pts)+  | otherwise   = printf "thickClosedCurve(%s, %s)" (showPoints pts) (showFloat False w) describePicture (Polyline _ pts)   | haskellMode = printf "polyline %s" (showPoints pts)   | otherwise   = printf "polyline(%s)" (showPoints pts) describePicture (ThickPolyline _ pts w)-  | haskellMode = printf "thickPolyline %s %s" (showFloat w) (showPoints pts)-  | otherwise   = printf "thickPolyline(%s, %s)" (showPoints pts) (showFloat w)+  | haskellMode = printf "thickPolyline %s %s" (showFloat True w) (showPoints pts)+  | otherwise   = printf "thickPolyline(%s, %s)" (showPoints pts) (showFloat False w) describePicture (Curve _ pts)   | haskellMode = printf "curve %s" (showPoints pts)   | otherwise   = printf "curve(%s)" (showPoints pts) describePicture (ThickCurve _ pts w)-  | haskellMode = printf "thickCurve %s %s" (showFloat w) (showPoints pts)-  | otherwise   = printf "thickCurve(%s, %s)" (showPoints pts) (showFloat w)+  | haskellMode = printf "thickCurve %s %s" (showFloat True w) (showPoints pts)+  | otherwise   = printf "thickCurve(%s, %s)" (showPoints pts) (showFloat False w) describePicture (Sector _ b e r)-  | haskellMode = printf "sector %s %s %s" (showFloat b) (showFloat e) (showFloat r)-  | otherwise   = printf "sector(%s°, %s°, %s)" (showFloat (180 * b / pi)) (showFloat (180 * e / pi)) (showFloat r)+  | haskellMode = printf "sector %s %s %s" (showFloat True b) (showFloat True e) (showFloat True r)+  | otherwise   = printf "sector(%s°, %s°, %s)" (showFloat False (180 * b / pi)) (showFloat False (180 * e / pi)) (showFloat False r) describePicture (Arc _ b e r)-  | haskellMode = printf "arc %s %s %s" (showFloat b) (showFloat e) (showFloat r)-  | otherwise   = printf "arc(%s°, %s°, %s)" (showFloat (180 * b / pi)) (showFloat (180 * e / pi)) (showFloat r)+  | haskellMode = printf "arc %s %s %s" (showFloat True b) (showFloat True e) (showFloat True r)+  | otherwise   = printf "arc(%s°, %s°, %s)" (showFloat False (180 * b / pi)) (showFloat False (180 * e / pi)) (showFloat False r) describePicture (ThickArc _ b e r w)-  | haskellMode = printf "thickArc %s %s %s %s" (showFloat w) (showFloat b) (showFloat e) (showFloat r)-  | otherwise   = printf "thickArc(%s°, %s°, %s, %s)" (showFloat (180 * b / pi)) (showFloat (180 * e / pi)) (showFloat r) (showFloat w)+  | haskellMode = printf "thickArc %s %s %s %s" (showFloat True w) (showFloat True b) (showFloat True e) (showFloat True r)+  | otherwise   = printf "thickArc(%s°, %s°, %s, %s)" (showFloat False (180 * b / pi)) (showFloat False (180 * e / pi)) (showFloat False r) (showFloat False w) describePicture (Lettering _ txt)   | haskellMode = printf "lettering %s" (show txt)   | otherwise   = printf "lettering(%s)" (show txt)@@ -648,17 +679,23 @@   | haskellMode = printf "colored %s" (showColor c)   | otherwise   = printf "colored(..., %s)" (showColor c) describePicture (Translate _ x y _)-  | haskellMode = printf "translated %s %s" (showFloat x) (showFloat y)-  | otherwise   = printf "translated(..., %s, %s)" (showFloat x) (showFloat y)+  | haskellMode = printf "translated %s %s" (showFloat True x) (showFloat True y)+  | otherwise   = printf "translated(..., %s, %s)" (showFloat False x) (showFloat False y) describePicture (Scale _ x y _)-  | haskellMode = printf "scaled %s %s" (showFloat x) (showFloat y)-  | otherwise   = printf "scaled(..., %s, %s)" (showFloat x) (showFloat y)+  | haskellMode = printf "scaled %s %s" (showFloat True x) (showFloat True y)+  | otherwise   = printf "scaled(..., %s, %s)" (showFloat False x) (showFloat False y) describePicture (Rotate _ angle _)-  | haskellMode = printf "rotated %s" (showFloat angle)-  | otherwise   = printf "rotated(..., %s°)" (showFloat (180 * angle / pi))+  | haskellMode = printf "rotated %s" (showFloat True angle)+  | otherwise   = printf "rotated(..., %s°)" (showFloat False (180 * angle / pi))+describePicture (Reflect _ angle _)+  | haskellMode = printf "reflected %s" (showFloat True angle)+  | otherwise   = printf "reflected(..., %s°)" (showFloat False (180 * angle / pi))+describePicture (Clip _ x y _)+  | haskellMode = printf "clipped %s %s" (showFloat True x) (showFloat True y)+  | otherwise   = printf "rotated(..., %s, %s)" (showFloat False x) (showFloat False y) describePicture (Dilate _ k _)-  | haskellMode = printf "dilated %s" (showFloat k)-  | otherwise   = printf "dilated(..., %s)" (showFloat k)+  | haskellMode = printf "dilated %s" (showFloat True k)+  | otherwise   = printf "dilated(..., %s)" (showFloat False k) describePicture (Sketch _ name _ _ _) = T.unpack name describePicture (CoordinatePlane _) = "coordinatePlane" describePicture (Pictures _ _)@@ -696,6 +733,8 @@ getPictureSrcLoc (Scale loc _ _ _) = loc getPictureSrcLoc (Dilate loc _ _) = loc getPictureSrcLoc (Rotate loc _ _) = loc+getPictureSrcLoc (Reflect loc _ _) = loc+getPictureSrcLoc (Clip loc _ _ _) = loc getPictureSrcLoc (Sketch loc _ _ _ _) = loc getPictureSrcLoc (CoordinatePlane loc) = loc getPictureSrcLoc (Pictures loc _) = loc@@ -732,7 +771,7 @@         withScreen (elementFromCanvas offscreenCanvas) rect (drawFrame pic)         cw <- ClientRect.getWidth rect         ch <- ClientRect.getHeight rect-        when (cw > 0 && ch > 0) $+        when (cw > 0.5 && ch > 0.5) $             canvasDrawImage screen (elementFromCanvas offscreenCanvas)                             0 0 (round cw) (round ch) @@ -784,6 +823,8 @@         Scale _ _ _ p -> nodeWithChild pic p         Dilate _ _ p -> nodeWithChild pic p         Rotate _ _ p -> nodeWithChild pic p+        Reflect _ _ p -> nodeWithChild pic p+        Clip _ _ _ p -> nodeWithChild pic p         SolidPolygon _ _ -> leafNode pic         SolidClosedCurve _ _ -> leafNode pic         Polygon _ _ -> leafNode pic@@ -956,7 +997,7 @@  modifyMVarIfDifferent :: MVar s -> (s -> s) -> IO Bool modifyMVarIfDifferent var f =-    modifyMVar var $ \s0 -> do+    modifyMVar var $ \s0 ->         case ifDifferent f s0 of             Nothing -> return (s0, False)             Just s1 -> return (s1, True)@@ -1407,6 +1448,10 @@     = Dilate loc k <$> indexNode True (i + 1) n p indexNode True i n (Rotate loc r p)     = Rotate loc r <$> indexNode True (i + 1) n p+indexNode True i n (Reflect loc r p)+    = Reflect loc r <$> indexNode True (i + 1) n p+indexNode True i n (Clip loc x y p)+    = Clip loc x y <$> indexNode True (i + 1) n p indexNode keepTx i n p = go keepTx (i + 1) (getChildNodes p)   where go _ i [] = Left i         go keepTx i (pic:pics) =@@ -1705,9 +1750,12 @@ deriving instance Monad m => Applicative (ReactiveProgram t m) deriving instance Monad m => Monad (ReactiveProgram t m) deriving instance MonadFix m => MonadFix (ReactiveProgram t m)+deriving instance MonadIO m => MonadIO (ReactiveProgram t m) deriving instance R.MonadSample t m => R.MonadSample t (ReactiveProgram t m) deriving instance R.MonadHold t m => R.MonadHold t (ReactiveProgram t m)+deriving instance R.NotReady t m => R.NotReady t (ReactiveProgram t m) deriving instance R.PerformEvent t m => R.PerformEvent t (ReactiveProgram t m)+deriving instance R.TriggerEvent t m => R.TriggerEvent t (ReactiveProgram t m) deriving instance R.PostBuild t m => R.PostBuild t (ReactiveProgram t m)  instance (MonadFix m, R.MonadHold t m, R.Adjustable t m) => R.Adjustable t (ReactiveProgram t m) where@@ -1726,16 +1774,14 @@     -> ReactiveInput t     -> m (R.Dynamic t Picture, R.Dynamic t Picture) runReactiveProgram (ReactiveProgram program) input = do-    ((), output) <- R.runDynamicWriterT (runReaderT program input)-    return $ R.splitDynPure $ do-        pics <- userPictures <$> output-        let userPicture = case pics of-                []  -> blank-                [p] -> p-                ps  -> pictures ps-        tform <- userTransform <$> output-        sysPic <- systemPicture <$> output-        return (userPicture, sysPic & tform userPicture)+    (_, output) <- R.runDynamicWriterT (runReaderT program input)+    let pic = coalescePics . userPictures <$> output+    let sysPic = (&) <$> (systemPicture <$> output)+                     <*> (userTransform <$> output <*> pic)+    return (pic, sysPic)+  where coalescePics []  = blank+        coalescePics [p] = p+        coalescePics ps  = pictures ps  withReactiveInput     :: ReactiveInput t@@ -1755,9 +1801,19 @@     ReactiveProgram . R.tellDyn . fmap (\a -> mempty { userTransform = a })  -- | Type class for the builder monad of a CodeWorld/Reflex app.-class (R.Reflex t, R.MonadHold t m, MonadFix m, R.PerformEvent t m,-       R.Adjustable t m, MonadIO (R.Performable m), R.PostBuild t m)-  => ReflexCodeWorld t m | m -> t where+class+  (+    R.Reflex t,+    R.Adjustable t m,+    R.MonadHold t m,+    R.NotReady t m,+    R.PostBuild t m,+    R.PerformEvent t m,+    R.TriggerEvent t m,+    MonadFix m,+    MonadIO m,+    MonadIO (R.Performable m)+  ) => ReflexCodeWorld t m | m -> t where     -- | Gets an Event of key presses.  The event value is a logical key name.     getKeyPress :: m (R.Event t Text) @@ -1783,9 +1839,19 @@     -- | Emits a given Dynamic picture to be drawn to the screen.     draw :: R.Dynamic t Picture -> m () -instance (R.Reflex t, R.MonadHold t m, MonadFix m, R.PerformEvent t m,-          R.Adjustable t m, MonadIO (R.Performable m), R.PostBuild t m)-  => ReflexCodeWorld t (ReactiveProgram t m) where+instance+  (+    R.Reflex t,+    R.Adjustable t m,+    R.MonadHold t m,+    R.NotReady t m,+    R.PostBuild t m,+    R.PerformEvent t m,+    R.TriggerEvent t m,+    MonadFix m,+    MonadIO m,+    MonadIO (R.Performable m)+  ) => ReflexCodeWorld t (ReactiveProgram t m) where     getKeyPress = ReactiveProgram $ asks keyPress      getKeyRelease = ReactiveProgram $ asks keyRelease@@ -1802,13 +1868,36 @@      draw = ReactiveProgram . R.tellDyn . fmap (\a -> mempty { userPictures = [a] }) -splitDyn :: forall t a. R.Reflex t => R.Dynamic t (a -> Bool) -> R.Event t a -> (R.Event t a, R.Event t a)-splitDyn predicate e = R.fanEither $ R.attachPromptlyDynWith f predicate e-  where f pred val = if pred val then Right val else Left val- gateDyn :: forall t a. R.Reflex t => R.Dynamic t Bool -> R.Event t a -> R.Event t a gateDyn dyn e = R.switchDyn (bool R.never e <$> dyn) +type EventChannel t = Chan [DSum (R.EventTriggerRef t) R.TriggerInvocation]++-- | Handle the event channel used with 'runTriggerEventT'.+asyncProcessEventTriggers+    :: EventChannel t+    -> R.FireCommand t (R.SpiderHost R.Global)+    -> IO ThreadId+asyncProcessEventTriggers events fireCommand = forkIO . forever $ do+    -- Collect event triggers, and fire callbacks after propagation+    eventsAndTriggers <- readChan events+    eventsToFire <- flip wither eventsAndTriggers $+        \(R.EventTriggerRef ref :=> R.TriggerInvocation a _) ->+            fmap (==> a) <$> readRef ref+    void . R.runSpiderHost $+        R.runFireCommand fireCommand eventsToFire (pure ())+    -- Run callbacks+    traverse_ (\(_ :=> R.TriggerInvocation _ cb) -> cb) eventsAndTriggers++sendEvent+    :: R.FireCommand t (R.SpiderHost R.Global)+    -> IORef (Maybe (R.EventTrigger t a))+    -> a+    -> IO ()+sendEvent (R.FireCommand fire) triggerRef a =+    R.runSpiderHost $ readRef triggerRef+        >>= traverse_ (\t -> fire [t ==> a] (pure ()))+ #ifdef ghcjs_HOST_OS  createPhysicalReactiveInput@@ -1866,7 +1955,7 @@      pointerPosition <- R.holdDyn (0, 0) pointerMovement     pointerDown <- R.holdDyn False $-        R.mergeWith (&&) [True <$ pointerPress, False <$ pointerRelease]+        R.leftmost [True <$ pointerPress, False <$ pointerRelease]      return ReactiveInput{..} @@ -1898,9 +1987,18 @@             }  runReactive-    :: (forall t m. (R.Reflex t, R.MonadHold t m, MonadFix m, R.PerformEvent t m,-                     R.Adjustable t m, MonadIO (R.Performable m), R.PostBuild t m)-        => (ReactiveInput t -> m (R.Dynamic t Picture, R.Dynamic t Picture)))+    :: (forall t m.+        ( R.Reflex t,+          R.Adjustable t m,+          R.MonadHold t m,+          R.NotReady t m,+          R.PostBuild t m,+          R.PerformEvent t m,+          R.TriggerEvent t m,+          MonadFix m,+          MonadIO m,+          MonadIO (R.Performable m)+        ) => (ReactiveInput t -> m (R.Dynamic t Picture, R.Dynamic t Picture)))     -> IO () runReactive program = do     showCanvas@@ -1929,12 +2027,16 @@         resizeEvent <- R.runSpiderHost $ R.newEventWithTrigger $ \trigger -> do             on window resize $ liftIO $ fireAndRedraw [trigger ==> ()]         logicalInput <- R.runSpiderHost $ inspectLogicalInput debugState physicalInput+        eventTriggers <- newChan         (inspectPicture, fireCommand) <- R.runSpiderHost $ R.hostPerformEventT $ do-            (inspectPicture, displayPicture) <- R.runPostBuildT (program logicalInput) postBuild+            (inspectPicture, displayPicture) <-+                flip R.runTriggerEventT eventTriggers .+                flip R.runPostBuildT postBuild $+                program logicalInput             let logicalPicture = drawDebugState <$> debugState                                                 <*> inspectPicture                                                 <*> displayPicture-            R.performEvent_ $ liftIO <$> R.mergeWith const [+            R.performEvent_ $ liftIO <$> R.leftmost [                 (setCanvasSize canvas canvas >>) . asyncRender <$>                     R.tagPromptlyDyn logicalPicture resizeEvent,                 asyncRender <$> R.updated logicalPicture,@@ -1945,29 +2047,31 @@         let fireAndRedraw events = R.runSpiderHost $ void $                 R.runFireCommand fireCommand events (return ()) -    let fireDebugUpdateAndRedraw f = R.runSpiderHost $ do-            state <- readRef debugUpdateTriggerRef-            case state of-                Just trigger -> void $-                    R.runFireCommand fireCommand [trigger ==> f] (return ())-                Nothing -> return ()-    let samplePicture = R.runSpiderHost $ R.runHostFrame $ R.sample $ R.current inspectPicture+    let+        fireDebugUpdateAndRedraw = sendEvent fireCommand debugUpdateTriggerRef+        samplePicture = R.runSpiderHost $ R.runHostFrame $ R.sample $ R.current inspectPicture     connectInspect canvas samplePicture fireDebugUpdateAndRedraw -    maybePostBuildTrigger <- readRef postBuildTriggerRef-    case maybePostBuildTrigger of-        Just trigger -> R.runSpiderHost $ void $-            R.runFireCommand fireCommand [trigger ==> ()] (return ())-        Nothing -> return ()+    sendEvent fireCommand postBuildTriggerRef () +    void $ asyncProcessEventTriggers eventTriggers fireCommand     waitForever  #else  runReactive-    :: (forall t m. (R.Reflex t, R.MonadHold t m, MonadFix m, R.PerformEvent t m,-                     R.Adjustable t m, MonadIO (R.Performable m), R.PostBuild t m)-        => (ReactiveInput t -> m (R.Dynamic t Picture, R.Dynamic t Picture)))+    :: (forall t m.+        ( R.Reflex t,+          R.Adjustable t m,+          R.MonadHold t m,+          R.NotReady t m,+          R.PostBuild t m,+          R.PerformEvent t m,+          R.TriggerEvent t m,+          MonadFix m,+          MonadIO m,+          MonadIO (R.Performable m)+        ) => (ReactiveInput t -> m (R.Dynamic t Picture, R.Dynamic t Picture)))     -> IO () runReactive program = runBlankCanvas $ \context -> do     let cw = Canvas.width context@@ -1993,31 +2097,29 @@      pointerPosition <- R.runSpiderHost $ R.holdDyn (0, 0) pointerMovement     pointerDown <- R.runSpiderHost $ R.holdDyn False $-        R.mergeWith (&&) [True <$ pointerPress, False <$ pointerRelease]+        R.leftmost [True <$ pointerPress, False <$ pointerRelease]      let input = ReactiveInput{..} +    triggeredEvents <- newChan     (_, fireCommand) <- R.runSpiderHost $ R.hostPerformEventT $ do-        (_inspectPicture, displayPicture) <- R.runPostBuildT (program input) postBuild-        R.performEvent_ $ liftIO <$> R.mergeWith const [+        (_inspectPicture, displayPicture) <-+            flip R.runTriggerEventT triggeredEvents .+            flip R.runPostBuildT postBuild $+            program input+        R.performEvent_ $ liftIO <$> R.leftmost [             frame <$> R.updated displayPicture,             frame <$> R.tagPromptlyDyn displayPicture postBuild             ]         return () -    let sendEvent :: forall a. IORef (Maybe (R.EventTrigger (R.SpiderTimeline R.Global) a)) -> a -> IO ()-        sendEvent triggerRef val = do-            mtrigger <- readRef triggerRef-            case mtrigger of-                Just trigger -> R.runSpiderHost $ void $-                    R.runFireCommand fireCommand [trigger ==> val] (return ())-                Nothing -> return ()+    let sendEvent'+            :: IORef (Maybe (R.EventTrigger (R.SpiderTimeline R.Global) a))+            -> a+            -> IO ()+        sendEvent' = sendEvent fireCommand -    maybePostBuildTrigger <- readRef postBuildTriggerRef-    case maybePostBuildTrigger of-        Just trigger -> R.runSpiderHost $ void $-            R.runFireCommand fireCommand [trigger ==> ()] (return ())-        Nothing -> return ()+    sendEvent' postBuildTriggerRef ()      t0 <- getCurrentTime     let go t1 = do@@ -2025,25 +2127,28 @@             forM_ events $ \event -> case Canvas.eType event of                 "keydown" | Just code <- Canvas.eWhich event -> do                     let keyName = keyCodeToText (fromIntegral code)-                    sendEvent keyPressTrigger keyName-                    when (T.length keyName == 1) $ sendEvent textEntryTrigger keyName+                    sendEvent' keyPressTrigger keyName+                    when (T.length keyName == 1) $ sendEvent' textEntryTrigger keyName                 "keyup" | Just code <- Canvas.eWhich event -> do                     let keyName = keyCodeToText (fromIntegral code)-                    sendEvent keyReleaseTrigger keyName+                    sendEvent' keyReleaseTrigger keyName                 "mousedown" | Just pos <- getMousePos (cw, ch) <$> Canvas.ePageXY event -> do-                    sendEvent pointerPressTrigger pos+                    sendEvent' pointerPressTrigger pos                 "mouseup" | Just pos <- getMousePos (cw, ch) <$> Canvas.ePageXY event -> do-                    sendEvent pointerReleaseTrigger pos+                    sendEvent' pointerReleaseTrigger pos                 "mousemove" | Just pos <- getMousePos (cw, ch) <$> Canvas.ePageXY event -> do-                    sendEvent pointerMovementTrigger pos+                    sendEvent' pointerMovementTrigger pos                 _ -> return ()              tn <- getCurrentTime             threadDelay $ max 0 (50000 - (round ((tn `diffUTCTime` t0) * 1000000)))             t2 <- getCurrentTime             let dt = realToFrac (t2 `diffUTCTime` t1)-            sendEvent timePassingTrigger dt+            sendEvent' timePassingTrigger dt             go t2-    go t0+    bracket+        (asyncProcessEventTriggers triggeredEvents fireCommand)+        killThread+        (const $ go t0)  #endif
+ src/CodeWorld/Image.hs view
@@ -0,0 +1,19 @@+{-+  Copyright 2019 The CodeWorld Authors. All rights reserved.++  Licensed under the Apache License, Version 2.0 (the "License");+  you may not use this file except in compliance with the License.+  You may obtain a copy of the License at++      http://www.apache.org/licenses/LICENSE-2.0++  Unless required by applicable law or agreed to in writing, software+  distributed under the License is distributed on an "AS IS" BASIS,+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+  See the License for the specific language governing permissions and+  limitations under the License.+-}++module CodeWorld.Image (image) where++import CodeWorld.Picture
+ src/CodeWorld/Parameter.hs view
@@ -0,0 +1,420 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ParallelListComp #-}+{-# LANGUAGE PatternGuards #-}+{-# LANGUAGE RankNTypes #-}++{-+  Copyright 2019 The CodeWorld Authors. All rights reserved.++  Licensed under the Apache License, Version 2.0 (the "License");+  you may not use this file except in compliance with the License.+  You may obtain a copy of the License at++      http://www.apache.org/licenses/LICENSE-2.0++  Unless required by applicable law or agreed to in writing, software+  distributed under the License is distributed on an "AS IS" BASIS,+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+  See the License for the specific language governing permissions and+  limitations under the License.+-}++module CodeWorld.Parameter+  {-# WARNING "This is an experimental API.  It can change at any time." #-}+  ( Parameter,+    parametricDrawingOf,+    slider,+    toggle,+    counter,+    constant,+    random,+    timer,+    currentHour,+    currentMinute,+    currentSecond,+    converted,+    renamed,+  )+where++import CodeWorld+import CodeWorld.Driver (runInspect)+import Data.Function (on)+import Data.List (sortBy)+import Data.Text (Text, pack)+import qualified Data.Text as T+import Data.Time.Clock+import Data.Time.LocalTime+import Numeric (showFFloatAlt)+import System.IO.Unsafe (unsafePerformIO)+import System.Random (newStdGen, randomR)++-- | Bounds information for a parameter UI.  The fields are the+-- left and top coordinate, then the width and height.+type Bounds = (Double, Double, Double, Double)++-- | The source for a parameter that can be adjusted in a parametric+-- drawing.  Parameters can get their values from sliders, buttons,+-- counters, timers, etc.+data Parameter where+  Parameter ::+    Text ->+    Double ->+    Picture ->+    Bounds ->+    (Event -> Parameter) ->+    Parameter++-- | A drawing that depends on parameters.  The first argument is a+-- list of parameters.  The second is a picture, which depends on the+-- values of those parameters.  Each number used to retrieve the picture+-- is the value of the corresponding parameter in the first list.+parametricDrawingOf :: [Parameter] -> ([Double] -> Picture) -> IO ()+parametricDrawingOf initialParams mainPic =+  runInspect+    (zip [0 :: Int ..] (layoutParams 0 (-9.5) 9.5 initialParams), True, 5)+    (const id)+    change+    picture+    rawPicture+  where+    change (KeyPress " ") (params, vis, _) = (params, not vis, 2)+    change (PointerPress pt) (params, vis, t) =+      case (vis, pullMatch (hitTest pt . snd) params) of+        (True, (Just p, ps)) -> (fmap (changeParam (PointerPress pt)) p : ps, vis, t)+        _ -> (params, vis, t)+    change event (params, vis, t) =+      (map (fmap (changeParam event)) params, vis, changeTime event t)+    picture (params, vis, t) =+      showHideBanner t+        & (picWhen vis $ pictures (map (showParam . snd) params))+        & rawPicture (params, vis, t)+    rawPicture (params, _, _) =+      mainPic (map (getParam . snd) (sortBy (compare `on` fst) params))+    changeParam event (Parameter _ _ _ _ handle) = handle event+    showParam (Parameter _ _ pic _ _) = pic+    getParam (Parameter _ val _ _ _) = val+    changeTime (TimePassing dt) t = max 0 (t - dt)+    changeTime _ t = t+    showHideBanner t =+      picWhen (t > 0)+        $ translated 0 (-9)+        $ dilated 0.5+        $ colored (RGBA 0 0 0 t) (rectangle 18 2)+          & colored+            (RGBA 0 0 0 t)+            (lettering "Press Space to show/hide parameters.")+          & colored (RGBA 0.75 0.75 0.75 (min 0.8 t)) (solidRectangle 18 2)++-- | Wraps a list of parameters in frames to lay them out on the screen.+layoutParams :: Double -> Double -> Double -> [Parameter] -> [Parameter]+layoutParams _ _ _ [] = []+layoutParams maxw x y (p : ps)+  | y > (-9.5) + h + titleHeight =+    framedParam (x - left) (y - top - titleHeight) p+      : layoutParams (max maxw w) x (y - h - titleHeight - gap) ps+  | otherwise = layoutParams 0 (x + maxw + gap) 9.5 (p : ps)+  where+    Parameter _ _ _ (left, top, w, h) _ = p+    gap = 0.5++-- | Finds the first element of a list that matches the predicate, if any,+-- and removes it from the list, returning it separately from the remaining+-- elements.+pullMatch :: (a -> Bool) -> [a] -> (Maybe a, [a])+pullMatch _ [] = (Nothing, [])+pullMatch p (a : as)+  | p a = (Just a, as)+  | otherwise = fmap (a :) (pullMatch p as)++-- | Determines if a point is inside the screen area for a given parameter.+hitTest :: Point -> Parameter -> Bool+hitTest (x, y) (Parameter _ _ _ (left, top, w, h) _) =+  x > left && x < left + w && y < top && y > top - h++-- | Builds a parameter from an explicit state.+parameterOf ::+  Text ->+  state ->+  (Event -> state -> state) ->+  (state -> Double) ->+  (state -> Picture) ->+  (state -> Bounds) ->+  Parameter+parameterOf name initial change value picture bounds =+  Parameter+    name+    (value initial)+    (picture initial)+    (bounds initial)+    (\e -> parameterOf name (change e initial) change value picture bounds)++-- Puts a simple parameter in a draggable widget that let the user+-- manipulate it on the screen, and displays the name and value.+-- All parameters are enclosed in one of these automatically.+framedParam :: Double -> Double -> Parameter -> Parameter+framedParam ix iy iparam =+  parameterOf+    name+    (iparam, (ix, iy), True, Nothing)+    framedHandle+    (\(Parameter _ v _ _ _, _, _, _) -> v)+    framedPicture+    framedBounds+  where+    (Parameter name _ _ _ _) = iparam++-- | The state of a framedParam, which includes the original parameter,+-- its location, whether it's open (expanded) or not, and the anchor if+-- it is currently being dragged.+type FrameState = (Parameter, Point, Bool, Maybe Point)++framedHandle :: Event -> FrameState -> FrameState+framedHandle (PointerPress (px, py)) (param, (x, y), open, anchor)+  | onOpenButton = (param, (x, y), not open, anchor)+  | onTitleBar = (param, (x, y), open, Just (px, py))+  where+    Parameter _ _ _ (left, top, w, h) _ = param+    onTitleBar =+      abs (px - x - (left + w / 2)) < w / 2+        && abs (py - y - top - titleHeight / 2) < titleHeight / 2+    onOpenButton+      | w * h > 0 =+        abs (px - x - (left + w - titleHeight / 2)) < 0.2+          && abs (py - y - (top + titleHeight / 2)) < 0.2+      | otherwise = False+framedHandle (PointerRelease _) (param, loc, open, Just _) =+  (param, loc, open, Nothing)+framedHandle (PointerMovement (px, py)) (param, (x, y), open, Just (ax, ay)) =+  (param, (x + px - ax, y + py - ay), open, Just (px, py))+framedHandle (TimePassing dt) (Parameter _ _ _ _ handle, loc, open, anchor) =+  (handle (TimePassing dt), loc, open, anchor)+framedHandle event (Parameter _ _ _ _ handle, (x, y), True, anchor) =+  (handle (untranslated x y event), (x, y), True, anchor)+framedHandle _ other = other++framedPicture :: FrameState -> Picture+framedPicture (Parameter n v pic (left, top, w, h) _, (x, y), open, _) =+  translated x y $+    translated (left + w / 2) (top + titleHeight / 2) titleBar+      & translated (left + w / 2) (top - h / 2) clientArea+  where+    titleBar+      | w * h > 0 =+        rectangle w titleHeight+          & translated+            ((w - titleHeight) / 2)+            0+            (if open then collapseButton else expandButton)+          & translated+            (- titleHeight / 2)+            0+            ( clipped+                (w - titleHeight)+                titleHeight+                (dilated 0.5 (lettering titleText))+            )+          & colored titleColor (solidRectangle w titleHeight)+      | otherwise =+        rectangle w titleHeight+          & clipped w titleHeight (dilated 0.5 (lettering titleText))+          & colored titleColor (solidRectangle w titleHeight)+    titleText+      | T.length n > 10 = T.take 8 n <> "... = " <> formattedVal+      | otherwise = n <> " = " <> formattedVal+    formattedVal = pack (showFFloatAlt (Just 2) v "")+    collapseButton = rectangle 0.4 0.4 & solidPolygon [(-0.1, -0.1), (0.1, -0.1), (0, 0.1)]+    expandButton = rectangle 0.4 0.4 & solidPolygon [(-0.1, 0.1), (0.1, 0.1), (0, -0.1)]+    clientArea =+      picWhen (w * h > 0) $+        rectangle w h+          & clipped w h pic+          & colored bgColor (solidRectangle 5 1)++framedBounds :: FrameState -> Bounds+framedBounds (Parameter _ _ _ (left, top, w, h) _, (x, y), True, _) =+  (x + left, y + top + titleHeight, w, h + titleHeight)+framedBounds (Parameter _ _ _ (left, top, w, _) _, (x, y), False, _) =+  (x + left, y + top + titleHeight, w, titleHeight)++titleHeight :: Double+titleHeight = 0.7++untranslated :: Double -> Double -> Event -> Event+untranslated x y (PointerPress (px, py)) = PointerPress (px - x, py - y)+untranslated x y (PointerRelease (px, py)) = PointerRelease (px - x, py - y)+untranslated x y (PointerMovement (px, py)) = PointerMovement (px - x, py - y)+untranslated _ _ other = other++-- | Adjusts the output of a parameter by passing it through a conversion+-- function.  Built-in parameters usually range from 0 to 1, and conversions+-- can be used to rescale the output to a different range.+converted :: (Double -> Double) -> Parameter -> Parameter+converted c (Parameter name val pic bounds handle) =+  Parameter name (c val) pic bounds (converted c . handle)++-- | Changes the name of an existing parameter.+renamed :: Text -> Parameter -> Parameter+renamed name (Parameter _ val pic bounds handle) =+  Parameter name val pic bounds (renamed name . handle)++-- | A 'Parameter' with a constant value, and no way to change it.+constant :: Text -> Double -> Parameter+constant name n =+  parameterOf+    name+    n+    (const id)+    id+    (const blank)+    (const (-2.5, 0, 5, 0))++-- | Builder for 'Parameter' types that are clickable and 5x1 in size.+buttonOf ::+  Text ->+  state ->+  (state -> state) ->+  (state -> Double) ->+  (state -> Picture) ->+  Parameter+buttonOf name initial click value pic =+  parameterOf+    name+    (initial, False)+    change+    (value . fst)+    ( \(state, press) ->+        pic state+          & picWhen press (colored (RGBA 0 0 0 0.3) (solidRectangle 5 1))+    )+    (const (-2.5, 0.5, 5, 1))+  where+    change (PointerPress (px, py)) (state, _)+      | abs px < 2.5, abs py < 0.5 = (state, True)+    change (PointerRelease (px, py)) (state, True)+      | abs px < 2.5, abs py < 0.5 = (click state, False)+      | otherwise = (state, False)+    change _ (state, press) = (state, press)++-- | A 'Parameter' that can be toggled between 0 (off) and 1 (on).+toggle :: Text -> Parameter+toggle name = buttonOf name False not value picture+  where+    value True = 1+    value False = 0+    picture True = dilated 0.5 $ lettering "\x2611"+    picture False = dilated 0.5 $ lettering "\x2610"++-- | A 'Parameter' that counts how many times it has been clicked.+counter :: Text -> Parameter+counter name = buttonOf name 0 (+ 1) id picture+  where+    picture _ = dilated 0.5 (lettering "Next")++-- | A 'Parameter' that can be adjusted continuously between 0 and 1.+slider :: Text -> Parameter+slider name =+  parameterOf+    name+    (0.5, False)+    change+    fst+    picture+    (const (-2.5, 0.5, 5, 1))+  where+    change (PointerPress (px, py)) (_, _)+      | abs px < 2, abs py < 0.25 = (min 1 $ max 0 $ (px + 2) / 4, True)+    change (PointerRelease _) (v, _) = (v, False)+    change (PointerMovement (px, _)) (_, True) =+      (min 1 $ max 0 $ (px + 2) / 4, True)+    change _ state = state+    picture (v, _) =+      translated (v * 4 - 2) 0 (solidRectangle 0.125 0.5)+        & solidRectangle 4 0.1++-- | A 'Parameter' that has a randomly chosen value.  It offers a button to+-- regenerate its value.+random :: Text -> Parameter+random name = buttonOf name initial (next . snd) fst picture+  where+    initial = next (unsafePerformIO newStdGen)+    picture _ = dilated 0.5 $ lettering "\x21ba Regenerate"+    next = randomR (0.0, 1.0)++-- | A 'Parameter' that changes over time. It can be paused or reset.+timer :: Text -> Parameter+timer name =+  parameterOf+    name+    (0, 1)+    change+    fst+    picture+    (const (-2.5, 0.5, 5, 1))+  where+    change (TimePassing dt) (t, r) = (t + r * dt, r)+    change (PointerPress (px, py)) (t, r)+      | abs (px - 5 / 6) < 5 / 6, abs py < 0.75 = (t, 1 - r)+      | abs (px + 5 / 6) < 5 / 6, abs py < 0.75 = (0, 0)+    change _ state = state+    picture (_, 0) =+      (translated (5 / 6) 0 $ dilated 0.5 $ lettering "\x23e9")+        & (translated (-5 / 6) 0 $ dilated 0.5 $ lettering "\x23ee")+    picture _ =+      (translated (5 / 6) 0 $ dilated 0.5 $ lettering "\x23f8")+        & (translated (-5 / 6) 0 $ dilated 0.5 $ lettering "\x23ee")++-- | A 'Parameter' that tracks the current hour, in local time.  The hour+-- is on a scale from 0 (meaning midnight) to 23 (meaning 11:00 pm).+currentHour :: Parameter+currentHour =+  parameterOf+    "hour"+    ()+    (const id)+    (\_ -> unsafePerformIO $ fromIntegral <$> todHour <$> getTimeOfDay)+    (const blank)+    (const (-2.5, 0, 5, 0))++-- | A 'Parameter' that tracks the current minute, in local time.  It+-- ranges from 0 to 59.+currentMinute :: Parameter+currentMinute =+  parameterOf+    "minute"+    ()+    (const id)+    (\_ -> unsafePerformIO $ fromIntegral <$> todMin <$> getTimeOfDay)+    (const blank)+    (const (-2.5, 0, 5, 0))++-- | A 'Parameter' that tracks the current second, in local time.  It+-- ranges from 0.0 up to (but not including) 60.0.  This includes+-- fractions of a second.  If that's not what you want, you can use+-- 'withConversion' to truncate the number.+currentSecond :: Parameter+currentSecond =+  parameterOf+    "second"+    ()+    (const id)+    (\_ -> unsafePerformIO $ realToFrac <$> todSec <$> getTimeOfDay)+    (const blank)+    (const (-2.5, 0, 5, 0))++getTimeOfDay :: IO TimeOfDay+getTimeOfDay = do+  now <- getCurrentTime+  timezone <- getCurrentTimeZone+  return (localTimeOfDay (utcToLocalTime timezone now))++titleColor :: Color+titleColor = RGBA 0.7 0.7 0.7 0.9++bgColor :: Color+bgColor = RGBA 0.8 0.85 0.95 0.8++picWhen :: Bool -> Picture -> Picture+picWhen True = id+picWhen False = const blank
src/CodeWorld/Picture.hs view
@@ -29,65 +29,110 @@ import GHC.Stack import Util.EmbedAsUrl +-- | A point in two dimensions.  A point is written with the x coordinate+-- first, and the y coordinate second.  For example, (3, -2) is the point+-- with x coordinate 3 a y coordinate -2. type Point = (Double, Double) --- | Move given point by given X-axis and Y-axis offsets--- >>> translatedPoint 1 2 (10,10)--- (11.0,12.0)--- >>> translatedPoint (-1) (-2) (0,0)--- (-1.0,-2.0)+-- | Moves a given point by given x and y offsets+--+-- >>> translatedPoint 1 2 (10, 10)+-- (11.0, 12.0)+-- >>> translatedPoint (-1) (-2) (0, 0)+-- (-1.0, -2.0) translatedPoint :: Double -> Double -> Point -> Point translatedPoint tx ty (x, y) = (x + tx, y + ty) +-- | Rotates a given point by given angle, in radians+--+-- >>> rotatedPoint 45 (10, 0)+-- (7.071, 7.071) rotatedPoint :: Double -> Point -> Point rotatedPoint = rotatedVector +-- | Reflects a given point across a line through the origin at this+-- angle, in radians.  For example, an angle of 0 reflects the point+-- vertically across the x axis, while an angle of @pi / 2@ reflects the+-- point horizontally across the y axis.+reflectedPoint :: Double -> Point -> Point+reflectedPoint th (x, y) = (x * cos a + y * sin a, x * sin a - y * cos a)+  where a = 2 * th++-- | Scales a given point by given x and y scaling factor.  Scaling by a+-- negative factor also reflects across that axis.+--+-- >>> scaledPoint 2 3 (10, 10)+-- (20, 30) scaledPoint :: Double -> Double -> Point -> Point scaledPoint kx ky (x, y) = (kx * x, ky * y) +-- | Dilates a given point by given uniform scaling factor.  Dilating by a+-- negative factor also reflects across the origin.+--+-- >>> dilatedPoint 2 (10, 10)+-- (20, 20) dilatedPoint :: Double -> Point -> Point dilatedPoint k (x, y) = (k * x, k * y) +-- | A two-dimensional vector type Vector = (Double, Double) +-- | The length of the given vector.+--+-- >>> vectorLength (10, 10)+-- 14.14 vectorLength :: Vector -> Double vectorLength (x, y) = sqrt (x*x + y*y) -{-| Given vector, calculate angle in radians that it has with the X-axis.-->>> vectorDirection (1,0)-0.0->>> vectorDirection (1,1)-0.7853981633974483->>> vectorDirection (0,1)-1.5707963267948966--}+-- | The counter-clockwise angle, in radians, that a given vector make with the X-axis+--+-- >>> vectorDirection (1,0)+-- 0.0+-- >>> vectorDirection (1,1)+-- 0.7853981633974483+-- >>> vectorDirection (0,1)+-- 1.5707963267948966 vectorDirection :: Vector -> Double vectorDirection (x, y) = atan2 y x +-- | The sum of two vectors vectorSum :: Vector -> Vector -> Vector vectorSum (x1, y1) (x2, y2) = (x1 + x2, y1 + y2) +-- | The difference of two vectors vectorDifference :: Vector -> Vector -> Vector vectorDifference (x1, y1) (x2, y2) = (x1 - x2, y1 - y2) +-- | Scales a given vector by a given scalar multiplier.+--+-- >>> scaledPoint 2 (10, 10)+-- (20, 20) scaledVector :: Double -> Vector -> Vector scaledVector k (x, y) = (k * x, k * y) -{-| Rotate given vector by given angle in radians-->>> rotatedVector pi (1.0, 0.0)-(-1.0,1.2246467991473532e-16)->>> rotatedVector (pi / 2) (1.0, 0.0)-(6.123233995736766e-17,1.0)- -}+-- | Rotates a given vector by a given angle in radians+--+-- >>> rotatedVector pi (1.0, 0.0)+-- (-1.0, 1.2246467991473532e-16)+-- >>> rotatedVector (pi / 2) (1.0, 0.0)+-- (6.123233995736766e-17, 1.0) rotatedVector :: Double -> Vector -> Vector rotatedVector angle (x, y) =     (x * cos angle - y * sin angle, x * sin angle + y * cos angle) +-- | The dot product of two vectors dotProduct :: Vector -> Vector -> Double dotProduct (x1, y1) (x2, y2) = x1 * x2 + y1 * y2 +-- | A design, diagram, or drawing that can be displayed and seen.+-- In technical terms, a picture is an assignment of a color to+-- every point of the coordinate plane.  CodeWorld contains functions+-- to create pictures from simple geometry primitives, to transform+-- existing pictures, and to combine simpler pictures into more+-- complex compositions.+--+-- Ultimately, a picture can be drawn on the screen using one of the+-- CodeWorld entry points such as 'drawingOf'. data Picture     = SolidPolygon (Maybe SrcLoc) [Point]     | SolidClosedCurve (Maybe SrcLoc) [Point]@@ -115,6 +160,8 @@     | Scale (Maybe SrcLoc) !Double !Double !Picture     | Dilate (Maybe SrcLoc) !Double !Picture     | Rotate (Maybe SrcLoc) !Double !Picture+    | Reflect (Maybe SrcLoc) !Double !Picture+    | Clip (Maybe SrcLoc) !Double !Double !Picture     | CoordinatePlane (Maybe SrcLoc)     | Sketch (Maybe SrcLoc) !Text !Text !Double !Double     | Pictures (Maybe SrcLoc) [Picture]@@ -124,14 +171,22 @@  instance NFData Picture +-- A style in which to draw lettering.  Either 'Plain', 'Bold', or+-- 'Italic' data TextStyle-    = Plain-    | Bold-    | Italic+    = Plain   -- ^ Plain letters with no style+    | Bold    -- ^ Heavy, thick lettering used for emphasis+    | Italic  -- ^ Slanted script-like lettering used for emphasis     deriving (Generic, Show)  instance NFData TextStyle +-- A font in which to draw lettering.  There are several built-in+-- font families ('SansSerif', 'Serif', 'Monospace', 'Handwriting',+-- and 'Fancy') that can look different on each screen.  'NamedFont'+-- can be used for a specific font.  However, if the font is not+-- installed on the computer running your program, a different font+-- may be used instead. data Font     = SansSerif     | Serif@@ -284,20 +339,33 @@ translated :: HasCallStack => Double -> Double -> Picture -> Picture translated = Translate (getDebugSrcLoc callStack) --- | A picture scaled by these factors in the x and y directions.+-- | A picture scaled by these factors in the x and y directions.  Scaling+-- by a negative factoralso reflects across that axis. scaled :: HasCallStack => Double -> Double -> Picture -> Picture scaled = Scale (getDebugSrcLoc callStack)  -- | A picture scaled uniformly in all directions by this scale factor.+-- Dilating by a negative factor also reflects across the origin. dilated :: HasCallStack => Double -> Picture -> Picture dilated = Dilate (getDebugSrcLoc callStack) --- | A picture rotated by this angle.+-- | A picture rotated by this angle about the origin. -- -- Angles are in radians. rotated :: HasCallStack => Double -> Picture -> Picture rotated = Rotate (getDebugSrcLoc callStack) +-- | A picture reflected across a line through the origin at this angle, in+-- radians.  For example, an angle of 0 reflects the picture vertically+-- across the x axis, while an angle of @pi / 2@ reflects the picture+-- horizontally across the y axis.+reflected :: HasCallStack => Double -> Picture -> Picture+reflected = Reflect (getDebugSrcLoc callStack)++-- | A picture clipped to a rectangle around the origin with this width and height.+clipped :: HasCallStack => Double -> Double -> Picture -> Picture+clipped = Clip (getDebugSrcLoc callStack)+ -- A picture made by drawing these pictures, ordered from top to bottom. pictures :: HasCallStack => [Picture] -> Picture pictures = Pictures (getDebugSrcLoc callStack)@@ -342,6 +410,18 @@         "codeWorldLogo"         $(embedAsUrl "image/svg+xml" "data/codeworld.svg")         17.68 7.28++-- | An image from a standard image format.  The image can be any universally+-- supported format, including SVG, PNG, JPG, etc.  SVG should be preferred, as+-- it behaves better with transformations.+image+  :: HasCallStack+  => Text  -- ^ Name for the picture, used for debugging+  -> Text  -- ^ Data-scheme URI for the image data+  -> Double  -- ^ Width, in CodeWorld screen units+  -> Double  -- ^ Height, in CodeWorld screen units+  -> Picture+image = Sketch (getDebugSrcLoc callStack)  getDebugSrcLoc :: CallStack -> Maybe SrcLoc getDebugSrcLoc cs = Data.List.find ((== "main") . srcLocPackage) locs
src/CodeWorld/Reflex.hs view
@@ -186,9 +186,12 @@     ( Reflex t,       MonadHold t m,       MonadFix m,+      TriggerEvent t m,       PerformEvent t m,+      MonadIO m,       MonadIO (Performable m),       Adjustable t m,+      NotReady t m,       PostBuild t m     ) =>     ReactiveInput t ->@@ -254,7 +257,10 @@  reactiveDebugControls ::   ( PerformEvent t m,+    TriggerEvent t m,     Adjustable t m,+    NotReady t m,+    MonadIO m,     MonadIO (Performable m),     PostBuild t m,     MonadHold t m,@@ -320,7 +326,10 @@  playPauseButton ::   ( PerformEvent t m,+    TriggerEvent t m,     Adjustable t m,+    NotReady t m,+    MonadIO m,     MonadIO (Performable m),     PostBuild t m,     MonadHold t m,@@ -354,7 +363,10 @@  stepButton ::   ( PerformEvent t m,+    TriggerEvent t m,     Adjustable t m,+    NotReady t m,+    MonadIO m,     MonadIO (Performable m),     PostBuild t m,     MonadHold t m,@@ -382,7 +394,10 @@  fastForwardButton ::   ( PerformEvent t m,+    TriggerEvent t m,     Adjustable t m,+    NotReady t m,+    MonadIO m,     MonadIO (Performable m),     PostBuild t m,     MonadHold t m,@@ -407,7 +422,10 @@  speedSlider ::   ( PerformEvent t m,+    TriggerEvent t m,     Adjustable t m,+    NotReady t m,+    MonadIO m,     MonadIO (Performable m),     PostBuild t m,     MonadHold t m,@@ -440,7 +458,10 @@  resetViewButton ::   ( PerformEvent t m,+    TriggerEvent t m,     Adjustable t m,+    NotReady t m,+    MonadIO m,     MonadIO (Performable m),     PostBuild t m,     MonadHold t m,@@ -464,7 +485,10 @@  panControls ::   ( PerformEvent t m,+    TriggerEvent t m,     Adjustable t m,+    NotReady t m,+    MonadIO m,     MonadIO (Performable m),     PostBuild t m,     MonadHold t m,@@ -493,7 +517,10 @@  zoomControls ::   ( PerformEvent t m,+    TriggerEvent t m,     Adjustable t m,+    NotReady t m,+    MonadIO m,     MonadIO (Performable m),     PostBuild t m,     MonadHold t m,@@ -520,7 +547,10 @@  zoomInButton ::   ( PerformEvent t m,+    TriggerEvent t m,     Adjustable t m,+    NotReady t m,+    MonadIO m,     MonadIO (Performable m),     PostBuild t m,     MonadHold t m,@@ -550,7 +580,10 @@  zoomOutButton ::   ( PerformEvent t m,+    TriggerEvent t m,     Adjustable t m,+    NotReady t m,+    MonadIO m,     MonadIO (Performable m),     PostBuild t m,     MonadHold t m,@@ -579,7 +612,10 @@  zoomSlider ::   ( PerformEvent t m,+    TriggerEvent t m,     Adjustable t m,+    NotReady t m,+    MonadIO m,     MonadIO (Performable m),     PostBuild t m,     MonadHold t m,