diff --git a/reanimate.cabal b/reanimate.cabal
index e07f1a6..9ce59df 100644
--- a/reanimate.cabal
+++ b/reanimate.cabal
@@ -28,17 +28,32 @@ library
build-depends: base >=4.10 && <4.13,
lucid-svg, time, text, unix, lucid, filepath, process, directory,
containers, svg-tree >= 0.6.2.3, xml, bytestring, lens, linear, mtl, matrix,
- JuicyPixels, attoparsec
+ JuicyPixels, attoparsec, parallel
Flag gtk-viewer
Description: Enable gtk-based viewer
- Default: True
+ Default: False
+
+Flag server
+ Description: Enable rendering server
+ Default: False
+
+executable reanimate-server
+ if flag(server)
+ buildable: True
+ else
+ buildable: False
+ hs-source-dirs: server, src
+ main-is: Main.hs
+ other-modules: Reanimate.Misc
+ build-depends: base >=4.10 && <4.13, text, websockets, process, filepath, directory,
+ cache, clock, stm, containers
executable reanimate-viewer
if flag(gtk-viewer)
- buildable: True
+ buildable: True
else
- buildable: False
+ buildable: False
main-is: SvgViewer.hs
-- other-extensions:
other-modules: Reanimate.Arrow
diff --git a/src/Reanimate/Arrow.hs b/src/Reanimate/Arrow.hs
index b3cb6ef..420c279 100644
--- a/src/Reanimate/Arrow.hs
+++ b/src/Reanimate/Arrow.hs
@@ -48,6 +48,15 @@ frameAt n (Animation d fn) = svg $ fn d (n `mod'` d) ()
with (svg11_ content) [width_ "320" , height_ "180", viewBox_ "0 0 320 180"]
-- with (svg11_ content) [width_ "1280" , height_ "720", viewBox_ "0 0 320 180"]
+unboundedFrameAt :: Double -> Ani () -> Svg ()
+unboundedFrameAt n (Animation d fn) = svg $ fn d (n `mod'` d) ()
+ where
+ svg :: Svg () -> Svg ()
+ svg content = do
+ doctype_
+ with (svg11_ content) [viewBox_ "0 0 320 180"]
+ -- with (svg11_ content) [width_ "1280" , height_ "720", viewBox_ "0 0 320 180"]
+
emit :: Animation (Svg ()) ()
emit = Animation 0 (\d t svg -> svg)
diff --git a/src/Reanimate/Examples.hs b/src/Reanimate/Examples.hs
index 2bdae3d..094d324 100644
--- a/src/Reanimate/Examples.hs
+++ b/src/Reanimate/Examples.hs
@@ -36,7 +36,7 @@ sinewave = proc () -> do
idx <- signalOscillate 0 1 -< ()
emit -< do
defs_ $ clipPath_ [id_ "clip"] $ toHtml $
- mkRect (Num 0, Num (-height)) (Num $ idx*width) (Percent 100)
+ mkRect (Num 0, Num (-height)) (Num $ idx*width) (Num 320)
toHtml $ translate margin height $ withStrokeColor "white" $
withClipPathRef (Ref "clip") $ mkPathText $ renderPathText $ approxFnData 1000 wave
toHtml $ withStrokeColor "white" $
@@ -54,9 +54,9 @@ morph_wave :: Ani ()
morph_wave = proc () -> do
duration 5 -< ()
morph <- signalOscillate 0 1 -< ()
+ emit -< toHtml $ mkBackground "black"
emit -< toHtml $ withStrokeColor "white" $ mkGroup
- [ mkBackground "black"
- , translate 30 50 $ mkPathText $ renderPathText wave1
+ [ translate 30 50 $ mkPathText $ renderPathText wave1
, translate 30 130 $ mkPathText $ renderPathText wave2
, translate 30 90 $ mkPathText $ renderPathText $ morphPath wave1 wave2 morph
, mkLine (Num 30, Num 10) (Num 30, Num 170)
@@ -368,7 +368,7 @@ bezier = adjustSpeed 0.4 $ proc () -> do
let new' = map (\(a,b) -> between a b s) (zip old new)
emit -< forM_ (zip new' (tail new')) $ \(a,b) -> do
renderPath $
- approxFnData 1000 $ \idx ->
+ approxFnData 100 $ \idx ->
between a b idx
emit -< mapM_ secondaryCircleAt new'
emit -< primaryCircleAt (head new')
@@ -377,11 +377,11 @@ bezier = adjustSpeed 0.4 $ proc () -> do
s <- signalOscillate 0 1 -< ()
emit -< primaryCircleAt =<< orderN' (map const lst) s <* mapM_ secondaryCircleAt lst
orderN' [a] s = do
- renderPath $ take (round $ 1000*s) $ approxFnData 1000 $ \idx -> a idx
+ renderPath $ take (round $ 100*s) $ approxFnData 100 $ \idx -> a idx
return (a s)
orderN' lst s = do
forM_ (zip lst (tail lst)) $ \(a,b) -> renderPath $
- approxFnData 1000 $ \idx ->
+ approxFnData 100 $ \idx ->
between (a s) (b s) idx
let middlePoints = map (\(a,b) -> \idx -> between (a idx) (b idx) idx) (zip lst (tail lst))
orderN' middlePoints s <* mapM_ secondaryCircleAt (map ($s) middlePoints)
diff --git a/src/Reanimate/Misc.hs b/src/Reanimate/Misc.hs
index 47228fc..6f389a3 100644
--- a/src/Reanimate/Misc.hs
+++ b/src/Reanimate/Misc.hs
@@ -1,19 +1,24 @@
module Reanimate.Misc
( requireExecutable
, runCmd
+ , runCmd_
+ , runCmdLazy
, withTempDir
, withTempFile
) where
import Control.Exception (evaluate, finally, throwIO)
+import qualified Data.Text as T
+import qualified Data.Text.IO as T
import System.Directory (createDirectory, findExecutable,
getTemporaryDirectory,
removeDirectoryRecursive, removeFile)
import System.Exit (ExitCode (..))
import System.FilePath ((<.>), (>))
-import System.IO (hClose, openTempFile)
-import System.Process (readProcessWithExitCode, showCommandForUser)
-
+import System.IO (hClose, openTempFile, hGetContents, hIsEOF)
+import System.Process (readProcessWithExitCode,
+ runInteractiveProcess, showCommandForUser,
+ waitForProcess)
requireExecutable :: String -> IO FilePath
requireExecutable exec = do
@@ -24,16 +29,42 @@ requireExecutable exec = do
runCmd :: FilePath -> [String] -> IO ()
runCmd exec args = do
+ _ <- runCmd_ exec args
+ return ()
+
+runCmd_ :: FilePath -> [String] -> IO (Either String String)
+runCmd_ exec args = do
(ret, stdout, stderr) <- readProcessWithExitCode exec args ""
evaluate (length stdout + length stderr)
case ret of
- ExitSuccess -> return ()
+ ExitSuccess -> return (Right stdout)
ExitFailure err -> do
- putStrLn $
+ return $ Left $
"Failed to run: " ++ showCommandForUser exec args ++ "\n" ++
"Error code: " ++ show err ++ "\n" ++
- "stderr: " ++ show stderr
- throwIO (ExitFailure err)
+ "stderr: " ++ stderr
+
+runCmdLazy :: FilePath -> [String] -> IO (IO (Either String T.Text))
+runCmdLazy exec args = do
+ (inp, out, err, pid) <- runInteractiveProcess exec args Nothing Nothing
+ hClose inp
+ return $ do
+ eof <- hIsEOF out
+ if eof
+ then do
+ stderr <- hGetContents err
+ evaluate (length stderr)
+ ret <- waitForProcess pid
+ case ret of
+ ExitSuccess -> return (Left "")
+ ExitFailure err -> do
+ return $ Left $
+ "Failed to run: " ++ showCommandForUser exec args ++ "\n" ++
+ "Error code: " ++ show err ++ "\n" ++
+ "stderr: " ++ stderr
+ else do
+ line <- T.hGetLine out
+ return (Right line)
withTempDir :: (FilePath -> IO a) -> IO a
withTempDir action = do
diff --git a/src/Reanimate/Render.hs b/src/Reanimate/Render.hs
index 86c0698..368cba8 100644
--- a/src/Reanimate/Render.hs
+++ b/src/Reanimate/Render.hs
@@ -1,14 +1,31 @@
module Reanimate.Render
( render
+ , renderSvgs
) where
import Control.Monad (forM_)
-import Lucid.Svg (renderToFile)
-import Reanimate.Arrow (Ani, animationDuration, frameAt)
+import Lucid.Svg (renderToFile, renderBS)
+import Reanimate.Arrow (Ani, animationDuration, frameAt, unboundedFrameAt)
import Reanimate.Examples
import Reanimate.Misc
-import System.FilePath (takeExtension, (>))
+import System.Directory (renameFile)
+import System.FilePath (takeExtension, takeFileName, (>))
import Text.Printf (printf)
+import qualified Data.ByteString.Lazy.Char8 as BS
+import Control.Parallel.Strategies
+
+renderSvgs :: Ani () -> FilePath -> IO ()
+renderSvgs ani tmpDir = do
+ let frameName nth = tmpDir > printf nameTemplate nth
+ renderedFrames = map (BS.concat . BS.lines . renderBS . nthFrame) frames
+ mapM_ BS.putStrLn (renderedFrames `using` parBuffer 16 rdeepseq)
+ where
+ frames = [0..frameCount-1]
+ rate = 60
+ nthFrame nth = unboundedFrameAt (recip (fromIntegral rate) * fromIntegral nth) ani
+ frameCount = round (animationDuration ani * fromIntegral rate) :: Int
+ nameTemplate :: String
+ nameTemplate = "render-%05d.svg"
data Format = RenderMp4 | RenderGif | RenderWebm
diff --git a/src/Reanimate/Svg.hs b/src/Reanimate/Svg.hs
index 7e73434..fe38dd7 100644
--- a/src/Reanimate/Svg.hs
+++ b/src/Reanimate/Svg.hs
@@ -425,6 +425,12 @@ mkRect corner width height = RectangleTree $ defaultSvg
& rectWidth .~ width
& rectHeight .~ height
+mkBoundingRect :: Tree -> Double -> Tree
+mkBoundingRect src margin =
+ mkRect (Num $ x-margin, Num $ y-margin) (Num $ w+margin*2) (Num $ h+margin*2)
+ where
+ (x, y, w, h) = boundingBox src
+
mkLine :: Point -> Point -> Tree
mkLine point1 point2 = LineTree $ defaultSvg
& linePoint1 .~ point1
diff --git a/web/.gitignore b/web/.gitignore
new file mode 100755
index 0000000..4d29575
--- /dev/null
+++ b/web/.gitignore
@@ -0,0 +1,23 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# production
+/build
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
diff --git a/web/public/index.html b/web/public/index.html
index 75980d5..6694681 100755
--- a/web/public/index.html
+++ b/web/public/index.html
@@ -22,7 +22,7 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
-
React App
+ Reanimate Live Editor
diff --git a/web/src/App.css b/web/src/App.css
index 0194702..ebe1ea9 100755
--- a/web/src/App.css
+++ b/web/src/App.css
@@ -7,22 +7,32 @@
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: max-content auto;
- grid-gap: 1em;
+ /* grid-gap: 1em; */
grid-auto-flow: column
}
-
+.editor {
+ /* min-width: 35em; */
+}
+.controls {
+ text-align:center;
+}
+.controls svg {
+ padding-left: 5px;
+ padding-right: 5px;
+}
+.viewer {
+ position: static;
+}
div.messages {
position: fixed;
- bottom: 0;
+ top: 0;
width: 100%;
- transition: height 1s ease;
- height: 1em + 4px;
+ background: #282c34;
+}
+div.messages pre {
+ margin: 0;
}
-div.messages:hover {
- height: 100px;
- transition: height 1s ease;
-}
#editor {
diff --git a/web/src/App.jsx b/web/src/App.jsx
index 371d7d5..2d73db3 100755
--- a/web/src/App.jsx
+++ b/web/src/App.jsx
@@ -1,45 +1,142 @@
import React, {Component} from 'react';
import './App.css';
import AceEditor from 'react-ace';
-import brace from 'brace';
import 'brace/mode/haskell';
import 'brace/theme/github';
import 'brace/theme/monokai';
+import preset from './Presets';
+
class App extends Component {
+ connect = () => {
+ const ws = new WebSocket("ws://localhost:9160");
+
+ ws.onopen = event => {
+ this.setState(state => ({...state, message: "Connected."}));
+ ws.send(this.state.program);
+ }
+ ws.onclose = event => {
+ this.setState(state => ({...state, message: "Disconnected."}));
+ setTimeout(this.connect, 1000);
+ }
+ ws.onmessage = event => {
+ if( event.data === "Success!" ) {
+ console.log("Success");
+ } else if( event.data === "Rendering" ) {
+ this.setState({message: "Rendering..."});
+ this.nFrames_new = 0;
+ this.svgs_new = [];
+ } else if( event.data === "Done" ) {
+ this.setState({message: "Success!"});
+ console.log("Done");
+ this.nFrames = this.nFrames_new;
+ this.svgs = this.svgs_new;
+ this.nFrames_new = 0;
+ this.svgs_new = [];
+ this.start = Date.now();
+ } else if( event.data.startsWith("Error") ) {
+ console.log("Error");
+ this.setState({message: event.data });
+ } else {
+ this.setState({message: `Rendering: ${this.nFrames_new}`});
+ this.nFrames_new++;
+ const div = document.createElement('div');
+ div.innerHTML = event.data;
+ this.svgs_new.push(div);
+ }
+ }
+ this.setState(state => ({...state, socket: ws, message: "Connecting..."}));
+ }
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ program: preset[0].programs[0].code
+ };
+ setTimeout(this.connect,0);
+ this.nFrames_new = 0;
+ this.svgs_new = [];
+ this.nFrames = 0;
+ this.svgs = [];
+ this.start = Date.now();
+ const self = this;
+ const animate = () => {
+ const now = Date.now();
+ const nFrames = self.nFrames;
+ const thisFrame = (Math.round((now-this.start)/1000*60))%nFrames
+ // const thisFrame = 0;
+ // console.log('Animation frame:', thisFrame, nFrames);
+ if(nFrames) {
+ // self.svg.innerHTML = self.svgs[thisFrame];
+ while(self.svg.firstChild)
+ self.svg.removeChild(self.svg.firstChild);
+ self.svg.appendChild(self.svgs[thisFrame]);
+ } else {
+ self.svg.innerText = "Loading...";
+ }
+ requestAnimationFrame(animate);
+ };
+ requestAnimationFrame(animate);
+ }
+ onLoad = ace => {
+ setTimeout(function() {
+ ace.resize();
+ }, 0);
+ }
+ onChange = text => {
+ this.setState({program: text});
+ if (this.timeout)
+ clearTimeout(this.timeout);
+ const socket = this.state.socket
+ const self = this;
+
+ this.timeout = setTimeout(function() {
+ console.log('change', text);
+ self.setState(state => ({...state, message: "Compiling..."}));
+ socket.send(text);
+ }, 500);
+ };
+ selectPreset = evt => {
+ this.onChange(evt.target.value);
+ };
render() {
+ const {message, program} = this.state;
return (
-
-
+
-
Controls
-
- Viewer
+
+
+
+
+
this.svg = node}/>
-
Compilation successful.
+
{message}
diff --git a/web/src/Presets.jsx b/web/src/Presets.jsx
new file mode 100644
index 0000000..ca81b4f
--- /dev/null
+++ b/web/src/Presets.jsx
@@ -0,0 +1,231 @@
+const latex_draw =
+`animation = pauseAtEnd 1 $ proc () -> do
+ emit -< toHtml $ mkBackground "black"
+ drawText msg \`andThen\` fillText msg -< ()
+ where
+ msg = "\\\\sum_{k=1}^\\\\infty {1 \\\\over k^2} = {\\\\pi^2 \\\\over 6}"
+ placement = translate (320/2) (180/2) . scale 5
+ fillText txt = proc () -> do
+ duration 1 -< ()
+ s <- signal 0 1 -< ()
+ emit -< toHtml $ placement $
+ withFillColor "white" $ withFillOpacity s $
+ center $ latexAlign txt
+ drawText txt = proc () -> do
+ duration 2 -< ()
+ s <- signal 0 1 -< ()
+ emit -< toHtml $ placement $
+ withStrokeColor "white" $ withFillOpacity 0 $ withStrokeWidth (Num 0.1) $
+ partialSvg s $ center $ latexAlign txt`;
+
+const bbox =
+`animation :: Ani ()
+animation = proc () -> do
+ emit -< toHtml $ mkBackground "black"
+ duration 5 -< ()
+ annotate' bbox1 -< g_ [transform_ $ Lucid.translate (320/2-50) (180/2)]
+ annotate' bbox2 -< g_ [transform_ $ Lucid.translate (320/2+50) (180/2)]
+
+bbox1 :: Ani ()
+bbox1 = proc () -> do
+ s <- signal 0 1 -< ()
+ emit -< do
+ toHtml $ mkBoundingBox $ rotate (360*s) svg
+ toHtml $ withFillColor "white" $ rotate (360*s) svg
+ where
+ svg = scale 3 $ center $ latexAlign "\\\\sum_{k=1}^\\\\infty"
+
+bbox2 :: Ani ()
+bbox2 = proc () -> do
+ s <- signalOscillate 0 1 -< ()
+ emit -< do
+ toHtml $ mkBoundingBox $ partialSvg s heartShape
+ toHtml $ withStrokeColor "white" $ withFillOpacity 0 $ partialSvg s heartShape
+
+mkBoundingBox :: Tree -> Tree
+mkBoundingBox svg = withStrokeColor "red" $ withFillOpacity 0 $
+ mkRect (S.Num x, S.Num y) (S.Num w) (S.Num h)
+ where
+ (x, y, w, h) = boundingBox svg
+
+heartShape =
+ center $ rotateAroundCenter 225 $ mkPathString
+ "M0.0,40.0 v-40.0 h40.0\
+ \a20.0 20.0 90.0 0 1 0.0,40.0\
+ \a20.0 20.0 90.0 0 1 -40.0,0.0 Z"`;
+
+const sinewave =
+`animation :: Ani ()
+animation = proc () -> do
+ duration 10 -< ()
+ emit -< toHtml $ mkBackground "black"
+ idx <- signalOscillate 0 1 -< ()
+ emit -< do
+ defs_ $ clipPath_ [id_ "clip"] $ toHtml $
+ mkRect (Num 0, Num (-height)) (Num $ idx*width) (Num 320)
+ toHtml $ translate margin height $ withStrokeColor "white" $
+ withClipPathRef (Ref "clip") $ mkPathText $ renderPathText $ approxFnData 1000 wave
+ toHtml $ withStrokeColor "white" $
+ mkLine (Num margin, Num 10) (Num margin, Num 170)
+ toHtml $ withStrokeColor "white" $
+ mkLine (Num margin, Num height) (Num (margin+width), Num height)
+ let (circX, circY) = wave idx
+ emit -< g_ [transform_ $ Lucid.translate margin height] $
+ circle_ [num_ cx_ circX, num_ cy_ circY, r_ "3", fill_ "red"]
+ where
+ freq = 3; margin = 30; width = 260; height = 90
+ wave idx = (idx*width, sin (idx*pi*2*freq) * 50)`;
+
+const morph_wave =
+`animation :: Ani ()
+animation = proc () -> do
+ duration 5 -< ()
+ morph <- signalOscillate 0 1 -< ()
+ emit -< toHtml $ mkBackground "black"
+ emit -< toHtml $ withStrokeColor "white" $ mkGroup
+ [ translate 30 50 $ mkPathText $ renderPathText wave1
+ , translate 30 130 $ mkPathText $ renderPathText wave2
+ , translate 30 90 $ mkPathText $ renderPathText $ morphPath wave1 wave2 morph
+ , mkLine (Num 30, Num 10) (Num 30, Num 170)
+ , mkLine (Num 30, Num 90) (Num 290, Num 90) ]
+ where
+ freq = 3; width = 260
+ wave1 = approxFnData 1000 $ \\idx -> (idx*width, sin (idx*pi*2*freq) * 20)
+ wave2 = approxFnData 1000 $ \\idx -> (idx*width, sin (idx*pi*2*(freq*3)) * 20)`;
+
+const morph_wave_circle =
+`animation :: Ani ()
+animation = proc t -> do
+ duration 5 -< ()
+ idx <- signalOscillate 0 1 -< ()
+ emit -< toHtml $ mkBackground "black"
+ emit -< toHtml $ withStrokeColor "white" $ mkGroup
+ [ translate 30 90 $ mkPathText $ renderPathText $ morphPath circle wave1 idx
+ , mkLine (Num 30, Num 10) (Num 30, Num 170)
+ , mkLine (Num 30, Num 90) (Num 290, Num 90) ]
+ where
+ freq = 5; width = 260; radius = 50
+ wave1 = approxFnData 100 $ \\idx -> (idx*width, sin (idx*pi*2*freq) * 20)
+ circle = approxFnData 100 $ \\idx ->
+ (cos (idx*pi*2+pi/2)*radius + width/2, sin (idx*pi*2+pi/2)*radius)`;
+
+const progressMeters =
+`animation :: Ani ()
+animation = proc () -> do
+ emit -< rect_ [width_ "100%", height_ "100%", fill_ "black"]
+ annotate' (adjustSpeed 1.0 progressMeter) -< g_ [transform_ $ Lucid.translate 40 20]
+ annotate' (adjustSpeed 2.0 progressMeter) -< g_ [transform_ $ Lucid.translate 140 20]
+ annotate' (adjustSpeed 0.5 progressMeter) -< g_ [transform_ $ Lucid.translate 240 20]
+
+ emit -< do
+ text_ [x_ "55", y_ "150", font_size_ "20"
+ , text_anchor_ "middle"
+ , fill_ "white"] "1x"
+ text_ [x_ "155", y_ "150", font_size_ "20"
+ , text_anchor_ "middle"
+ , fill_ "white"] "2x"
+ text_ [x_ "255", y_ "150", font_size_ "20"
+ , text_anchor_ "middle"
+ , fill_ "white"] "0.5x"
+
+progressMeter :: Ani ()
+progressMeter = loop $ proc () -> do
+ duration 5 -< ()
+ h <- signal 0 100 -< ()
+ emit -< rect_ [ width_ "30", height_ "100", stroke_ "white", stroke_width_ "2", fill_opacity_ "0" ]
+ emit -< rect_ [ width_ "30", num_ height_ h, stroke_ "white", fill_ "white" ]
+ returnA -< ()`
+
+const highlight =
+`animation :: Ani ()
+animation = proc () -> do
+ emit -< rect_ [width_ "100%", height_ "100%", fill_ "black"]
+ emit -< do
+ path_ (commonAttrs "white" ++ [d_ $ renderPathText rect1])
+ path_ (commonAttrs "white" ++ [d_ $ renderPathText rect2])
+
+ path_ (commonAttrs "white" ++ [d_ $ renderPathText rect3])
+ path_ (commonAttrs "lightblue" ++ [d_ $ renderPathText rect4])
+ path_ (commonAttrs "yellow" ++ [d_ $ renderPathText rect5])
+ path_ (commonAttrs "red" ++ [d_ $ renderPathText rect6])
+
+ follow
+ [ mkTransition highlight1 highlight2
+ , mkTransition highlight2 highlight3
+ , mkTransition highlight3 highlight4
+ , mkTransition highlight4 highlight5
+ , mkTransition highlight5 highlight6
+ , mkTransition highlight6 highlight1
+ ] -< ()
+
+ where
+ mkTransition from to = pauseAtEnd 1 $ proc () -> do
+ duration 1 -< ()
+ s <- signalSCurve 2 0 1 -< ()
+ let trans = morphPath from to s
+ emit -<
+ path_ (highlightAttrs "green" ++ [d_ $ renderPathText trans <> "Z"])
+ mkRect x y width height =
+ [ (x,y), (x+width, y), (x+width, y+height), (x,y+height) ]
+ rect1 = mkRect margin margin w h
+ rect2 = mkRect (320-margin-w*2) margin (w*2) h
+ rect3 = mkRect margin (180-margin-h) w h
+ rect4 = mkRect (320/3) (180-margin-h) w h
+ rect5 = mkRect (320/3*2-w) (180-margin-h) w h
+ rect6 = mkRect (320-margin-w) (180-margin-h) w h
+ highlight1 = mkRect (margin-b) (margin-b) (w+2*b) (h+2*b)
+ highlight2 = mkRect (320-margin-w*2-b) (margin-b) (w*2+2*b) (h+2*b)
+ highlight3 = mkRect (320-margin-w-b) (180-margin-h-b) (w+2*b) (h+2*b)
+ highlight4 = mkRect (320/3*2-w-b) (180-margin-h-b) (320/3+2*b) (h+2*b)
+ highlight5 = mkRect (320/3-b) (180-margin-h-b) (320/3+2*b) (h+2*b)
+ highlight6 = mkRect (margin-b) (180-margin-h-b) (320/3+2*b) (h+2*b)
+ b = 7; margin = 30; w = 30; h = 30
+ commonAttrs c = [stroke_width_ "2", stroke_ c, fill_ c]
+ highlightAttrs c = [stroke_width_ "2", stroke_ c, fill_opacity_ "0"]`;
+
+const latex_basic =
+`animation :: Ani ()
+animation = proc () -> do
+ duration 2 -< ()
+ s <- signalOscillate 0 1 -< ()
+ emit -< toHtml $ mkGroup
+ [ mkBackground "black"
+ , translate (320/2) (180/2) $ mkGroup
+ [ withStrokeColor "white" $ withFillOpacity 0 $ withStrokeWidth (Num 0.1) text
+ , withFillColor "white" $ withFillOpacity s text] ]
+ where
+ text = scale 4 $ center $ latexAlign
+ "\\\\sum_{k=1}^\\\\infty {1 \\\\over k^2} = {\\\\pi^2 \\\\over 6}"`
+
+const latex_color =
+`animation :: Ani ()
+animation = proc () -> do
+ duration 0.1 -< ()
+ emit -< toHtml $ mkBackground "black"
+ emit -< toHtml $ translate (320/2) (180/2) $ withStrokeWidth (Num 0.2) $
+ withStrokeColor "white" $
+ withSubglyphs [0] (withFillColor "blue") $
+ withSubglyphs [1] (withFillColor "yellow") $
+ withSubglyphs [2] (withFillColor "green") $
+ withSubglyphs [3] (withFillColor "red") $
+ withSubglyphs [4] (withFillColor "darkslategrey") $
+ svg
+ where
+ svg = scale 10 $ center $ latex "\\\\LaTeX"`
+
+
+export default [
+ { name: "Examples"
+ , programs:
+ [ {name: "LaTeX Draw", code: latex_draw }
+ , {name: "LaTeX Color", code: latex_color }
+ , {name: "LaTeX Basic", code: latex_basic }
+ , {name: "Bounding boxes", code: bbox }
+ , {name: "Sinewave", code: sinewave }
+ , {name: "Morphwave", code: morph_wave }
+ , {name: "Morphwave Circle", code: morph_wave_circle }
+ , {name: "Progress meters", code: progressMeters }
+ // , {name: "Highlight", code: highlight }
+ ]
+ },
+];