packages feed

threepenny-gui 0.1.0.1 → 0.2.0.0

raw patch · 20 files changed

+630/−166 lines, 20 filesdep +stmnew-component:exe:threepenny-examples-drummachinebinary-addedPVP ok

version bump matches the API change (PVP)

Dependencies added: stm

API changes (from Hackage documentation)

- Graphics.UI.Threepenny.Attributes: code :: WriteAttr Element String
- Graphics.UI.Threepenny.Attributes: link :: WriteAttr Element String
- Graphics.UI.Threepenny.Attributes: start :: WriteAttr Element Int
- Graphics.UI.Threepenny.Elements: thecode :: IO Element
- Graphics.UI.Threepenny.Elements: thelink :: IO Element
- Graphics.UI.Threepenny.Elements: themap :: IO Element
- Graphics.UI.Threepenny.Elements: thetitle :: IO Element
+ Graphics.UI.Threepenny.Attributes: checked_ :: WriteAttr Element Bool
+ Graphics.UI.Threepenny.Attributes: code_ :: WriteAttr Element String
+ Graphics.UI.Threepenny.Attributes: enabled :: Attr Element Bool
+ Graphics.UI.Threepenny.Attributes: selection :: Attr Element (Maybe Int)
+ Graphics.UI.Threepenny.Canvas: clearCanvas :: Canvas -> IO ()
+ Graphics.UI.Threepenny.Canvas: drawImage :: Element -> Vector -> Canvas -> IO ()
+ Graphics.UI.Threepenny.Canvas: type Canvas = Element
+ Graphics.UI.Threepenny.Core: audioPlay :: Element -> IO ()
+ Graphics.UI.Threepenny.Core: class FFI a
+ Graphics.UI.Threepenny.Core: class ToJS a
+ Graphics.UI.Threepenny.Core: data JSFunction a
+ Graphics.UI.Threepenny.Core: ffi :: FFI a => String -> a
+ Graphics.UI.Threepenny.Core: fromProp :: String -> (JSValue -> a) -> (a -> JSValue) -> Attr Element a
+ Graphics.UI.Threepenny.Core: manifestElement :: Window -> Element -> IO Element
+ Graphics.UI.Threepenny.Elements: audio :: IO Element
+ Graphics.UI.Threepenny.Elements: canvas :: IO Element
+ Graphics.UI.Threepenny.Elements: code :: IO Element
+ Graphics.UI.Threepenny.Elements: link :: IO Element
+ Graphics.UI.Threepenny.Elements: map :: IO Element
+ Graphics.UI.Threepenny.Elements: title_ :: IO Element
+ Graphics.UI.Threepenny.Events: keydown :: Element -> Event KeyCode
+ Graphics.UI.Threepenny.Events: keyup :: Element -> Event KeyCode
+ Graphics.UI.Threepenny.Events: mousemove :: Element -> Event (Int, Int)
+ Graphics.UI.Threepenny.Timer: data Timer
+ Graphics.UI.Threepenny.Timer: interval :: Attr Timer Int
+ Graphics.UI.Threepenny.Timer: running :: Attr Timer Bool
+ Graphics.UI.Threepenny.Timer: start :: Timer -> IO ()
+ Graphics.UI.Threepenny.Timer: stop :: Timer -> IO ()
+ Graphics.UI.Threepenny.Timer: tick :: Timer -> Event ()
+ Graphics.UI.Threepenny.Timer: timer :: IO Timer
- Graphics.UI.Threepenny.Attributes: checked :: WriteAttr Element Bool
+ Graphics.UI.Threepenny.Attributes: checked :: Attr Element Bool
- Graphics.UI.Threepenny.Attributes: width :: WriteAttr Element String
+ Graphics.UI.Threepenny.Attributes: width :: WriteAttr Element Int
- Graphics.UI.Threepenny.Core: callFunction :: Window -> String -> [String] -> IO [Maybe String]
+ Graphics.UI.Threepenny.Core: callFunction :: Window -> JSFunction a -> IO a
- Graphics.UI.Threepenny.Core: runFunction :: Window -> String -> [String] -> IO ()
+ Graphics.UI.Threepenny.Core: runFunction :: Window -> JSFunction () -> IO ()

Files

src/Buttons.hs view
@@ -1,6 +1,5 @@ {-# LANGUAGE CPP, PackageImports #-} - import Control.Monad import Control.Concurrent (threadDelay) @@ -59,8 +58,8 @@     on UI.leave button1 $ \_ -> do         element button1 # set text button1Title     on UI.click button1 $ \_ -> do-        threadDelay $ 1000 * 1000 * 1         element button1 # set text (button1Title ++ " [pressed]")+        threadDelay $ 1000 * 1000 * 1         element list    #+ [UI.li # set html "<b>Delayed</b> result!"]          (button2, view2) <- mkButton button2Title@@ -70,7 +69,7 @@     on UI.leave button2 $ \_ -> do         element button2 # set text button2Title     on UI.click button2 $ \_ -> do-        element button1 # set text (button1Title ++ " [pressed]")+        element button2 # set text (button2Title ++ " [pressed]")         element list    #+ [UI.li # set html "Zap! Quick result!"]          return [list, view1, view2]
+ src/DrumMachine.hs view
@@ -0,0 +1,108 @@+{-# LANGUAGE CPP, PackageImports #-}++import Control.Monad+import Data.IORef+import Data.Functor++#ifdef CABAL+import qualified "threepenny-gui" Graphics.UI.Threepenny as UI+import "threepenny-gui" Graphics.UI.Threepenny.Core+#else+import qualified Graphics.UI.Threepenny as UI+import Graphics.UI.Threepenny.Core+#endif+import Paths+import System.FilePath++{-----------------------------------------------------------------------------+    Configuration+------------------------------------------------------------------------------}+bars  = 4+beats = 4+defaultBpm = 120++bpm2ms :: Int -> Int+bpm2ms bpm = ceiling $ 1000*60 / fromIntegral bpm++-- NOTE: Samples taken from "conductive-examples"++instruments = words "kick snare hihat"++loadInstrumentSample w name = do+    static <- getStaticDir+    loadFile w "audio/wav" (static </> name <.> "wav")++{-----------------------------------------------------------------------------+    Main+------------------------------------------------------------------------------}+main :: IO ()+main = do+    static <- getStaticDir+    startGUI Config+        { tpPort       = 10000+        , tpCustomHTML = Nothing+        , tpStatic     = static+        } setup++setup :: Window -> IO ()+setup w = void $ do+    return w # set title "Ha-ha-ha-ks-ks-ks-ha-ha-ha-ell-ell-ell"++    elBpm  <- UI.input # set value (show defaultBpm)+    elTick <- UI.span+    (kit, elInstruments) <- mkDrumKit w+    let status = grid [[UI.string "BPM:" , element elBpm]+                      ,[UI.string "Beat:", element elTick]]+    getBody w #+ [UI.div #. "wrap" #+ (status : map element elInstruments)]+    +    timer   <- UI.timer # set UI.interval (bpm2ms defaultBpm)+    refBeat <- newIORef 0+    +    -- play sounds on timer events+    on UI.tick timer $ const $ void $ do+        -- get and increase beat count+        beat <- readIORef refBeat+        writeIORef refBeat $ (beat + 1) `mod` (beats * bars)+        element elTick # set text (show $ beat + 1)+        +        -- play corresponding sounds+        sequence_ $ map (!! beat) kit+    +    -- allow user to set BPM+    on (domEvent "livechange") elBpm $ const $ void $ do+        bpm <- read <$> get value elBpm+        return timer # set UI.interval (bpm2ms bpm)+    +    -- star the timer+    UI.start timer+++type Kit        = [Instrument]+type Instrument = [Beat]+type Beat       = IO ()         -- play the corresponding sound++mkDrumKit :: Window -> IO (Kit, [Element])+mkDrumKit w = unzip <$> mapM (mkInstrument w) instruments++mkInstrument :: Window -> String -> IO (Instrument, Element)+mkInstrument window name = do+    elCheckboxes <-+        sequence $ replicate bars  $+        sequence $ replicate beats $+            UI.input # set UI.type_ "checkbox"++    url     <- loadInstrumentSample window name+    elAudio <- UI.audio # set (attr "preload") "1" # set UI.src url++    let+        play box = do+            checked <- get UI.checked box+            when checked $ audioPlay elAudio+        beats    = map play . concat $ elCheckboxes+        elGroups = [UI.span #. "bar" #+ map element bar | bar <- elCheckboxes]+    +    elInstrument <- UI.div #. "instrument"+        #+ (element elAudio : UI.string name : elGroups)+    +    return (beats, elInstrument)+
src/Graphics/UI/Threepenny.hs view
@@ -1,15 +1,19 @@ module Graphics.UI.Threepenny (     module Graphics.UI.Threepenny.Attributes,     module Graphics.UI.Threepenny.Core,+    module Graphics.UI.Threepenny.Canvas,     module Graphics.UI.Threepenny.DragNDrop,     module Graphics.UI.Threepenny.Elements,     module Graphics.UI.Threepenny.Events,     module Graphics.UI.Threepenny.JQuery,+    module Graphics.UI.Threepenny.Timer,     ) where  import Graphics.UI.Threepenny.Attributes import Graphics.UI.Threepenny.Core+import Graphics.UI.Threepenny.Canvas import Graphics.UI.Threepenny.DragNDrop import Graphics.UI.Threepenny.Elements import Graphics.UI.Threepenny.Events import Graphics.UI.Threepenny.JQuery+import Graphics.UI.Threepenny.Timer
src/Graphics/UI/Threepenny/Attributes.hs view
@@ -1,26 +1,53 @@ module Graphics.UI.Threepenny.Attributes (     -- * Synopsis-    -- | Element Attributes, for convenience.+    -- | Element attributes.     -    -- * Documentation+    -- * Input elements+    checked, selection, enabled,+    +    -- * HTML attributes     action, align, alink, alt, altcode, archive,     background, base, bgcolor, border, bordercolor,-    cellpadding, cellspacing, checked, class_, clear_, code, codebase,+    cellpadding, cellspacing, checked_, class_, clear_, code_, codebase,     color, cols, colspan, compact, content, coords,     enctype, face, frameborder, height, href, hspace, httpequiv,-    id_, ismap, lang, link, marginheight, marginwidth, maxlength, method, multiple,+    id_, ismap, lang, marginheight, marginwidth, maxlength, method, multiple,     name, nohref, noresize, noshade, nowrap,     rel, rev, rows, rowspan, rules,-    scrolling, selected, shape, size, src, start,+    scrolling, selected, shape, size, src,     target, text_, type_, usemap, valign, version, vlink, vspace, width,     ) where -+import Text.JSON import Graphics.UI.Threepenny.Core  {-----------------------------------------------------------------------------     Attributes ------------------------------------------------------------------------------}+-- | The @checked@ status of an input element of type checkbox.+checked :: Attr Element Bool+checked = fromProp "checked" (== JSBool True) JSBool++-- | The @enabled@ status of an input element+enabled :: Attr Element Bool+enabled = fromProp "disabled" (== JSBool False) (JSBool . not)++-- | Index of the currently selected option of a @<select>@ element.+--+-- The index starts at @0@.+-- If no option is selected, then the selection is 'Nothing'.+selection :: Attr Element (Maybe Int)+selection = fromProp "selectedIndex" from (showJSON . maybe (-1) id)+    where+    from s = let Ok x = readJSON s in if x == -1 then Nothing else Just x+++{-----------------------------------------------------------------------------+    HTML atributes+    +    Taken from the HTML library (BSD3 license)+    http://hackage.haskell.org/package/html+------------------------------------------------------------------------------} strAttr :: String -> WriteAttr Element String strAttr name = mkWriteAttr (set' (attr name)) @@ -32,13 +59,7 @@     where     f True  = "1"     f False = "0"--{------------------------------------------------------------------------------    Primitives     -    Taken from the HTML library (BSD3 license)-    http://hackage.haskell.org/package/html-------------------------------------------------------------------------------} action              =   strAttr "action" align               =   strAttr "align" alink               =   strAttr "alink"@@ -52,9 +73,9 @@ bordercolor         =   strAttr "bordercolor" cellpadding         =   intAttr "cellpadding" cellspacing         =   intAttr "cellspacing"-checked             = emptyAttr "checked"+checked_            = emptyAttr "checked" clear_              =   strAttr "clear"-code                =   strAttr "code"+code_               =   strAttr "code" codebase            =   strAttr "codebase" color               =   strAttr "color" cols                =   strAttr "cols"@@ -72,7 +93,6 @@ id_                 =   strAttr "id" ismap               = emptyAttr "ismap" lang                =   strAttr "lang"-link                =   strAttr "link" marginheight        =   intAttr "marginheight" marginwidth         =   intAttr "marginwidth" maxlength           =   intAttr "maxlength"@@ -93,7 +113,6 @@ shape               =   strAttr "shape" size                =   strAttr "size" src                 =   strAttr "src"-start               =   intAttr "start" target              =   strAttr "target" text_               =   strAttr "text" class_              =   strAttr "class"@@ -104,4 +123,4 @@ version             =   strAttr "version" vlink               =   strAttr "vlink" vspace              =   intAttr "vspace"-width               =   strAttr "width"+width               =   intAttr "width"
+ src/Graphics/UI/Threepenny/Canvas.hs view
@@ -0,0 +1,35 @@+module Graphics.UI.Threepenny.Canvas (+    -- * Synopsis+    -- | Partial binding to the HTML5 canvas API.+    +    -- * Documentation+    Canvas,+    drawImage, clearCanvas,+    ) where++import Control.Event+import Graphics.UI.Threepenny.Core+import qualified Graphics.UI.Threepenny.Internal.Core as Core+import qualified Graphics.UI.Threepenny.Internal.Types as Core++{-----------------------------------------------------------------------------+    Canvas+------------------------------------------------------------------------------}+type Canvas = Element++type Vector = (Int,Int)++-- | Draw the image of an image element onto the canvas at a specified position.+drawImage :: Element -> Vector -> Canvas -> IO ()+drawImage eimage (x,y) = updateElement $ \(Core.Element canvas window) -> do+    image <- manifestElement window eimage+    runFunction window $+        ffi "%1.getContext('2d').drawImage(%2,%3,%4)" canvas image x y++-- | Clear the canvas+clearCanvas :: Canvas -> IO ()+clearCanvas = updateElement $ \(Core.Element canvas window) -> do+    runFunction window $+        ffi "%1.getContext('2d').clear()" canvas++
src/Graphics/UI/Threepenny/Core.hs view
@@ -13,7 +13,7 @@     -- * DOM elements     -- | Create and manipulate DOM elements.     Element, mkElement, getWindow, delete, (#+), string,-        getHead, getBody, +        getHead, getBody,         children, text, html, attr, style, value,     getValuesList,     getElementsByTagName, getElementByTagName, getElementsById, getElementById,@@ -34,34 +34,33 @@     Attr, WriteAttr, ReadAttr, ReadWriteAttr(..),     set, get, mkReadWriteAttr, mkWriteAttr, mkReadAttr,     -    -- * Utilities-    -- | A few raw JavaScript utilities.+    -- * JavaScript FFI+    -- | Direct interface to JavaScript in the browser window.     debug, clear,-    callFunction, runFunction, callDeferredFunction,-    atomic,+    ToJS, FFI, ffi, JSFunction, runFunction, callFunction,+    callDeferredFunction, atomic,     -    -- * Internal-    updateElement,+    -- * Internal and oddball functions+    updateElement, manifestElement, audioPlay, fromProp,          ) where +import Data.IORef import Data.Maybe (listToMaybe) import Data.Functor+import Data.String (fromString)+import Control.Concurrent.MVar import Control.Event import Control.Monad import Control.Monad.IO.Class import Control.Monad.Trans.Reader as Reader--import Control.Concurrent.MVar-import Data.IORef--import Data.String (fromString)- import Network.URI+import Text.JSON  import qualified Graphics.UI.Threepenny.Internal.Core  as Core import Graphics.UI.Threepenny.Internal.Core     (getRequestLocation,+     ToJS, FFI, ffi, JSFunction,      debug, clear, callFunction, runFunction, callDeferredFunction, atomic, ) import qualified Graphics.UI.Threepenny.Internal.Types as Core import Graphics.UI.Threepenny.Internal.Types (Window, Config, EventData)@@ -317,7 +316,22 @@ getElementsById window name =     mapM fromAlive =<< Core.getElementsById window name +{-----------------------------------------------------------------------------+    Oddball+------------------------------------------------------------------------------}+audioPlay = updateElement Core.audioPlay +-- Turn a jQuery property @.prop()@ into an attribute.+fromProp :: String -> (JSValue -> a) -> (a -> JSValue) -> Attr Element a+fromProp name from to = mkReadWriteAttr get set+    where+    set x = updateElement (Core.setProp name $ to x)+    get (Element ref) = do+        me <- readMVar ref+        case me of+            Limbo _ _ -> error "'checked' attribute: element must be in a browser window"+            Alive e   -> from <$> Core.getProp name e+ {-----------------------------------------------------------------------------     Layout ------------------------------------------------------------------------------}@@ -336,8 +350,8 @@ -- -- >  <div class="table"> -- >    <div class="table-row">--- >      <div class="table-cell> ... </div>--- >      <div class="table-cell> ... </div>+-- >      <div class="table-cell"> ... </div>+-- >      <div class="table-cell"> ... </div> -- >    </div> -- >    <div class="table-row"> -- >      ...
src/Graphics/UI/Threepenny/Elements.hs view
@@ -6,23 +6,24 @@     new,          -- * Primitive HTML elements-    address, a, anchor, applet, area,+    address, a, anchor, applet, area, audio,     basefont, big, blockquote, body, bold, br, button,-    caption, center, cite, ddef, define, div, dlist,+    canvas, caption, center, cite, code,+    ddef, define, div, dlist,     dterm, emphasize, fieldset, font, form, frame, frameset,     h1, h2, h3, h4, h5, h6, header, hr,     img, image, input, italics,-    keyboard, legend, li, meta, noframes, olist, option,+    keyboard, legend, li, link, map, meta, noframes, olist, option,     p, paragraph, param, pre,     sample, select, small, source, span, strong, sub, sup,-    table, td, textarea, th, thebase, thecode,-    thehtml, thelink, themap, thetitle, tr, tt, ul,+    table, td, textarea, th, thebase,+    thehtml, title_, tr, tt, ul,     underline, variable, video,     ) where  import Control.Monad import Control.Monad.Trans.Reader-import Prelude hiding (span,div)+import Prelude hiding (span,div,map) import Graphics.UI.Threepenny.Core  {-----------------------------------------------------------------------------@@ -63,6 +64,7 @@ anchor              =  tag "a" applet              =  tag "applet" area                = itag "area"+audio               =  tag "audio" basefont            = itag "basefont" big                 =  tag "big" blockquote          =  tag "blockquote"@@ -70,9 +72,11 @@ bold                =  tag "b" br                  = itag "br" button              =  tag "button"+canvas              =  tag "canvas" caption             =  tag "caption" center              =  tag "center" cite                =  tag "cite"+code                =  tag "code" ddef                =  tag "dd" define              =  tag "dfn" div                 =  tag "div"@@ -99,6 +103,8 @@ keyboard            =  tag "kbd" legend              =  tag "legend" li                  =  tag "li"+link                =  tag "link"+map                 =  tag "map" meta                = itag "meta" noframes            =  tag "noframes" olist               =  tag "ol"@@ -119,12 +125,9 @@ textarea            =  tag "textarea" th                  =  tag "th" thebase             = itag "base"-thecode             =  tag "code" thehtml             =  tag "html"-thelink             =  tag "link"-themap              =  tag "map" span                =  tag "span"-thetitle            =  tag "title"+title_              =  tag "title" tr                  =  tag "tr" tt                  =  tag "tt" ul                  =  tag "ul"
src/Graphics/UI/Threepenny/Events.hs view
@@ -3,7 +3,8 @@     -- | Common DOM events, for convenience.          -- * Documentation-    click, hover, blur, leave,+    click, mousemove, hover, blur, leave,+    keyup, keydown,     ) where  import Graphics.UI.Threepenny.Core@@ -18,6 +19,18 @@ hover :: Element -> Event () hover = silence . domEvent "mouseenter" +-- | Event that periodically occurs while the mouse is moving over an element.+--+-- The event value represents the mouse coordinates+-- relative to the upper left corner of the entire page.+--+-- Note: The @<body>@ element responds to mouse move events,+-- but only in the area occupied by actual content,+-- not the whole browser window.+mousemove :: Element -> Event (Int,Int)+mousemove = fmap readPair . domEvent "mousemove"+    where readPair (EventData (Just x:Just y:_)) = (read x, read y)+ -- | Mouse leaving an element. leave :: Element -> Event () leave = silence . domEvent "mouseleave"@@ -25,3 +38,16 @@ -- | Element loses focus. blur :: Element -> Event () blur = silence . domEvent "blur"+++type KeyCode = Int++-- | Key pressed while element has focus.+keydown :: Element -> Event KeyCode+keydown = fmap read1  . domEvent "keydown"++-- | Key released while element has focus.+keyup :: Element -> Event KeyCode+keyup   = fmap read1 . domEvent "keyup"++read1 (EventData (Just s:_)) = read s
src/Graphics/UI/Threepenny/Internal/Core.hs view
@@ -26,6 +26,7 @@   -- $settingattributes   ,setStyle   ,setAttr+  ,setProp   ,setText   ,setHtml   ,setTitle@@ -44,6 +45,7 @@   ,getElementsByTagName   ,getElementsById   ,getWindow+  ,getProp   ,getValue   ,getValuesList   ,readValue@@ -54,11 +56,16 @@   -- * Utilities   ,debug   ,clear-  ,callFunction-  ,runFunction   ,callDeferredFunction   ,atomic++  -- * JavaScript FFI+  ,ToJS, FFI, ffi, JSFunction+  ,runFunction, callFunction   +  -- * Oddball+  ,audioPlay+     -- * Types   ,Window   ,Element@@ -260,10 +267,10 @@   writeJson instructions  -- Write JSON to output.-writeJson :: (MonadSnap m, Data a) => a -> m ()+writeJson :: (MonadSnap m, JSON a) => a -> m () writeJson json = do     modifyResponse $ setContentType "application/json"-    (writeString . encodeJSON) json+    (writeString . (\x -> showJSValue x "") . showJSON) json  -- Write a string to output. writeString :: (MonadSnap m) => String -> m ()@@ -305,19 +312,21 @@ readInput :: (MonadSnap f,Read a) => ByteString -> f (Maybe a) readInput = fmap (>>= readMay) . getInput ------------------------------------------------------------------------------------ Event handling--- --- $eventhandling--- --- To bind events to elements, use the 'bind' function.------ To handle DOM events, use the 'handleEvent' function, or the--- 'handleEvents' function which will block forever.------ See the rest of this section for some helpful functions that do--- common binding, such as clicks, hovers, etc. +{-----------------------------------------------------------------------------+    Event handling+------------------------------------------------------------------------------}+{- $eventhandling++    To bind events to elements, use the 'bind' function.++    To handle DOM events, use the 'handleEvent' function, or the+    'handleEvents' function which will block forever.++    See the rest of this section for some helpful functions that do+    common binding, such as clicks, hovers, etc.+-}+ -- | Handle events signalled from the client. handleEvents :: Window -> IO () handleEvents window = forever $ handleEvent window@@ -358,13 +367,14 @@     _ <- register (sEvent key) $ \(EventData xs) -> thunk xs     return (Closure key) ------------------------------------------------------------------------------------ Setting attributes------ $settingattributes--- --- Text, HTML and attributes of DOM nodes can be set using the--- functions in this section. +{-----------------------------------------------------------------------------+    Setting attributes+------------------------------------------------------------------------------}+{- $settingattributes+ +    Text, HTML and attributes of DOM nodes can be set using the+    functions in this section. +-}  -- | Set the style of the given element. setStyle :: [(String, String)] -- ^ Pairs of CSS (property,value).@@ -379,6 +389,14 @@         -> IO () setAttr key value e@(Element el session) = run session $ SetAttr el key value +-- | Set the property of the given element.+setProp :: String  -- ^ The property name.+        -> JSValue -- ^ The property value.+        -> Element -- ^ The element to update.+        -> IO ()+setProp key value e@(Element el session) =+    runFunction session $ ffi "$(%1).prop(%2,%3);" el key value+ -- | Set the text of the given element. setText :: String  -- ^ The plain text.         -> Element -- ^ The element to update.@@ -407,12 +425,12 @@ delete e@(Element el session)  = run session $ Delete el  ------------------------------------------------------------------------------------ Manipulating tree structure---+{-----------------------------------------------------------------------------+    Manipulating tree structure+------------------------------------------------------------------------------} -- $treestructure--- --- Functions for creating, deleting, moving, appending, prepending, DOM nodes.+--+--  Functions for creating, deleting, moving, appending, prepending, DOM nodes.  -- | Create a new element of the given tag name. newElement :: Window      -- ^ Browser window in which context to create the element@@ -434,10 +452,17 @@     --       Implement transfer of elements across browser windows     run session $ Append parent child ------------------------------------------------------------------------------------- Querying the DOM---++{-----------------------------------------------------------------------------+    Oddball+------------------------------------------------------------------------------}+-- | Invoke the JavaScript expression @audioElement.play();@.+audioPlay :: Element -> IO ()+audioPlay (Element el session) = runFunction session $ ffi "%1.play();" el++{-----------------------------------------------------------------------------+    Querying the DOM+------------------------------------------------------------------------------} -- $querying --  -- The DOM can be searched for elements of a given name, and nodes can@@ -475,6 +500,14 @@       Value str -> return (Just str)       _         -> return Nothing +-- | Get the property of an element. Blocks.+getProp+    :: String     -- ^ The property name.+    -> Element    -- ^ The element to get the value of.+    -> IO JSValue -- ^ The plain text value.+getProp prop e@(Element el window) =+    callFunction window (ffi "$(%1).prop(%2)" el prop)+ -- | Get 'Window' associated to an 'Element'. getWindow :: Element -> Window getWindow (Element _ window) = window@@ -551,10 +584,9 @@ getRequestCookies :: Window -> IO [(String,String)] getRequestCookies = return . snd . sStartInfo ------------------------------------------------------------------------------------- Utilities-+{-----------------------------------------------------------------------------+    Utilities+------------------------------------------------------------------------------} -- | Send a debug message to the client. The behaviour of the client --   is unspecified. debug@@ -567,26 +599,23 @@ clear :: Window -> IO () clear window = run window $ Clear () --- | Call the given function and wait for results. Blocks.-callFunction-  :: Window             -- ^ Browser window-  -> String             -- ^ The function name.-  -> [String]           -- ^ Parameters.-  -> IO [Maybe String]  -- ^ Return tuple.-callFunction window func params =-  call window (CallFunction (func,params)) $ \signal ->-    case signal of-      FunctionCallValues vs -> return (Just vs)-      _                     -> return Nothing+-- | Run the given JavaScript function and carry on. Doesn't block.+--+-- The client window uses JavaScript's @eval()@ function to run the code.+runFunction :: Window -> JSFunction () -> IO ()+runFunction session = run session . RunJSFunction . unJSCode . code --- | Call the given function and carry on. Doesn't block.-runFunction-  :: Window            -- ^ Browser window-  -> String            -- ^ The function name.-  -> [String]          -- ^ Parameters.-  -> IO ()-runFunction window func params =-  run window $ CallFunction (func,params)+-- | Run the given JavaScript function and wait for results. Blocks.+--+-- The client window uses JavaScript's @eval()@ function to run the code.+callFunction :: Window -> JSFunction a -> IO a+callFunction window (JSFunction code marshal) = +    call window (CallJSFunction . unJSCode $ code) $ \signal ->+        case signal of+            FunctionResult v -> case marshal window v of+                Ok    a -> return $ Just a+                Error _ -> return Nothing+            _ -> return Nothing  -- | Call the given function with the given continuation. Doesn't block. callDeferredFunction
src/Graphics/UI/Threepenny/Internal/Types.hs view
@@ -1,16 +1,9 @@-{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}---- | All Threepenny's types. See "Graphics.UI.Threepenny.Types" for only public---   types. Non-public types can be manipulated at your own risk, if you---   know what you're doing and you want to add something that the---   library doesn't do.+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-} -module Graphics.UI.Threepenny.Internal.Types-  where+module Graphics.UI.Threepenny.Internal.Types where -import Prelude              hiding ((++),init)+import Prelude              hiding (init)  import Control.Applicative import Control.Concurrent@@ -23,8 +16,9 @@ import Network.URI import Text.JSON.Generic ------------------------------------------------------------------------------------ Public types+{-----------------------------------------------------------------------------+    Public types+------------------------------------------------------------------------------}  -- | Reference to an element in the DOM of the client window. data Element = Element@@ -87,9 +81,18 @@ -- | Data from an event. At the moment it is empty. data EventData = EventData [Maybe String] ------------------------------------------------------------------------------------ Internal types+-- | Record for configuring the Threepenny GUI server.+data Config = Config+  { tpPort       :: Int               -- ^ Port number.+  , tpCustomHTML :: Maybe FilePath    -- ^ Custom HTML file to replace the default one.+  , tpStatic     :: FilePath          -- ^ Directory that is served under @/static@.+  } ++{-----------------------------------------------------------------------------+    Communication between client and server+------------------------------------------------------------------------------}+ -- | An instruction that is sent to the client as JSON. data Instruction   = Debug String@@ -109,12 +112,17 @@   | GetValues [ElementId]   | SetTitle String   | GetLocation ()-  | CallFunction (String,[String])+  | RunJSFunction String+  | CallJSFunction String   | CallDeferredFunction (Closure,String,[String])   | EmptyEl ElementId   | Delete ElementId   deriving (Typeable,Data,Show) +instance JSON Instruction where+    readJSON _ = error "JSON.Instruction.readJSON: No method implemented."+    showJSON x = toJSON x + -- | A signal (mostly events) that are sent from the client to the server. data Signal   = Init ()@@ -124,6 +132,7 @@   | Values [String]   | Location String   | FunctionCallValues [Maybe String]+  | FunctionResult JSValue   deriving (Show)  instance JSON Signal where@@ -141,7 +150,9 @@         values = Values <$> valFromObj "Values" obj         fcallvalues = do           FunctionCallValues <$> (valFromObj "FunctionCallValues" obj >>= mapM nullable)-    init <|> elements <|> event <|> value <|> values <|> location <|> fcallvalues+        fresult = FunctionResult <$> valFromObj "FunctionResult" obj+    init <|> elements <|> event <|> value <|> values <|> location+         <|> fcallvalues <|> fresult  -- | Read a JSValue that may be null. nullable :: JSON a => JSValue -> Result (Maybe a)@@ -151,11 +162,99 @@ -- | An opaque reference to a closure that the event manager uses to --   trigger events signalled by the client. data Closure = Closure (String,String)-  deriving (Typeable,Data,Show)+    deriving (Typeable,Data,Show) --- | Record for configuring the Threepenny GUI server.-data Config = Config-  { tpPort       :: Int               -- ^ Port number.-  , tpCustomHTML :: Maybe FilePath    -- ^ Custom HTML file to replace the default one.-  , tpStatic     :: FilePath          -- ^ Directory that is served under @/static@.-  }+{-----------------------------------------------------------------------------+    JavaScript Code and Foreign Function Interface+------------------------------------------------------------------------------}+-- | JavaScript code snippet.+newtype JSCode = JSCode { unJSCode :: String }+    deriving (Eq, Ord, Show, Data, Typeable)++-- | Class for rendering Haskell types as JavaScript code.+class ToJS a where+    render :: a -> JSCode++instance ToJS String    where render   = JSCode . show+instance ToJS Int       where render   = JSCode . show+instance ToJS Bool      where render b = JSCode $ if b then "false" else "true"+instance ToJS JSValue   where render x = JSCode $ showJSValue x ""+instance ToJS ElementId where+    render (ElementId x) = apply "elidToElement(%1)" [render x]+instance ToJS Element   where render (Element e _) = render e+++-- | Representation of a JavaScript expression+-- with a girven output type.+data JSFunction a = JSFunction+    { code    :: JSCode                          -- code snippet+    , marshal :: Window -> JSValue -> Result a   -- convert to Haskell value+    }++instance Functor JSFunction where+    fmap f = fmapWindow (const f)++fmapWindow :: (Window -> a -> b) -> JSFunction a -> JSFunction b+fmapWindow f (JSFunction c m) = JSFunction c (\w v -> f w <$> m w v)++fromJSCode :: JSCode -> JSFunction ()+fromJSCode c = JSFunction { code = c, marshal = \_ _ -> Ok () }++-- | Helper class for making a simple JavaScript FFI+class FFI a where+    fancy :: ([JSCode] -> JSCode) -> a++instance (ToJS a, FFI b) => FFI (a -> b) where+    fancy f a = fancy $ f . (render a:)++instance FFI (JSFunction ())        where fancy f = fromJSCode $ f []+instance FFI (JSFunction String)    where fancy   = mkResult "%1.toString()"+instance FFI (JSFunction JSValue)   where fancy   = mkResult "%1"+instance FFI (JSFunction ElementId) where+    fancy   = mkResult "{ Element: elementToElid(%1) }"+instance FFI (JSFunction Element)   where+    fancy   = fmapWindow (\w elid -> Element elid w) . fancy++mkResult :: JSON a => String -> ([JSCode] -> JSCode) -> JSFunction a+mkResult client f = JSFunction+    { code    = apply client [f []]+    , marshal = \w -> readJSON+    }++-- | Simple JavaScript FFI with string substitution.+--+-- Inspired by the Fay language. <http://fay-lang.org/>+--+-- > example :: String -> Int -> JSFunction String+-- > example = ffi "$(%1).prop('checked',%2)"+--+-- The 'ffi' function takes a string argument representing the JavaScript+-- code to be executed on the client.+-- Occurrences of the substrings @%1@ to @%9@ will be replaced by+-- subequent arguments.+--+-- Note: Always specify a type signature! The types automate+-- how values are marshalled between Haskell and JavaScript.+-- The class instances for the 'FFI' class show which conversions are supported.+--+ffi :: FFI a => String -> a+ffi macro = fancy (apply macro)++testFFI :: String -> Int -> JSFunction String+testFFI = ffi "$(%1).prop('checked',%2)"++-- | String substitution.+-- Substitute occurences of %1, %2 up to %9 with the argument strings.+-- The types ensure that the % character has no meaning in the generated output.+-- +-- > apply "%1 and %2" [x,y] = x ++ " and " ++ y+apply :: String -> [JSCode] -> JSCode+apply code args = JSCode $ go code+    where+    argument i = unJSCode (args !! i)+    +    go []         = []+    go ('%':c:cs) = argument index ++ go cs+        where index = fromEnum c - fromEnum '1'+    go (c:cs)     = c : go cs+
src/Graphics/UI/Threepenny/JQuery.hs view
@@ -45,9 +45,9 @@ -- | Focus an element. setFocus :: Element -> IO () setFocus = updateElement $ \(Core.Element el window) ->-    runFunction window "jquery_setFocus" [encode el]+    runFunction window (ffi "$(%1).focus()" el)  -- | Scroll to the bottom of an element. scrollToBottom :: Element -> IO () scrollToBottom = updateElement $ \(Core.Element area window) ->-    runFunction window "jquery_scrollToBottom" [encode area]+    runFunction window (ffi "jquery_scrollToBottom(%1)" area)
+ src/Graphics/UI/Threepenny/Timer.hs view
@@ -0,0 +1,76 @@+{-# LANGUAGE RecordWildCards #-}++module Graphics.UI.Threepenny.Timer (+    -- * Synopsis+    -- | Implementation of a simple timer which runs on the server-side.+    +    -- * Documentation+    Timer, timer,+    interval, running, tick, start, stop,+    ) where+++import Control.Monad (when, forever, void)+import Control.Event+import Control.Concurrent+import Control.Concurrent.STM++import Graphics.UI.Threepenny.Core+++data Timer = Timer+    { tRunning  :: TVar Bool+    , tInterval :: TVar Int      -- in ms+    , tTick     :: Event ()+    }++-- | Create a new timer+timer :: IO Timer+timer = do+    tRunning      <- newTVarIO False+    tInterval     <- newTVarIO 1000+    (tTick, fire) <- newEvent+    +    forkIO $ forever $ do+        wait <- atomically $ do+            b <- readTVar tRunning+            when (not b) retry+            readTVar tInterval+        threadDelay (wait * 1000)+        fire ()+        +    return $ Timer {..}++tick = tTick++-- | Timer interval in milliseconds.+interval :: Attr Timer Int+interval = fromTVar tInterval++-- | Whether the timer is running or not.+running :: Attr Timer Bool+running = fromTVar tRunning++-- | Start the timer.+start :: Timer -> IO ()+start = set' running True++-- | Stop the timer.+stop :: Timer -> IO ()+stop = set' running False++fromTVar :: (x -> TVar a) -> Attr x a+fromTVar f = mkReadWriteAttr+    (atomically . readTVar . f)+    (\i x -> atomically $ writeTVar (f x) i)+++{-----------------------------------------------------------------------------+    Small test+------------------------------------------------------------------------------}+testTimer = do+    t <- timer+    void $ register (tick t) $ const $ putStr "Hello"+    return t+        # set interval 1000+        # set running True
src/Graphics/UI/driver.js view
@@ -59,27 +59,7 @@   window.do_logging = function(x){     $.cookie('tp_log',x.toString());   };-  -  window.jquery_animate = function(el_id,props,duration,easing,complete){-    var el = elidToElement(JSON.parse(el_id));-    $(el).animate(JSON.parse(props),-                  duration * 1,-                  easing * 1,-                  complete);-  }--  window.jquery_scrollToBottom = function(el_id,cont){-    var el = elidToElement(JSON.parse(el_id));-    $(el).scrollTop(el.scrollHeight);-    cont();-  };--  window.jquery_setFocus = function(el_id,cont){-    var el = elidToElement(JSON.parse(el_id));-    $(el).focus();-    cont();-  };-  +       function waitForEvents(){     console_log("Polling… (%d signals so far)",signal_count);     var data = { token: sessionToken };@@ -126,6 +106,7 @@     console_log("Event: %s",JSON.stringify(event));     for(var key in event){       switch(key){+                       case "EmptyEl": {         var id = event.EmptyEl;         var el = elidToElement(id);@@ -153,20 +134,16 @@         continuation();         break;       }-      case "CallFunction": {-        var call = event.CallFunction;-        var theFunction = eval(call[0]);-        var params = call[1];-        theFunction.apply(window, params.concat(function(){-          var args = Array.prototype.slice.call(arguments,0);-          signal({-            FunctionCallValues: args-          },function(){-            continuation();-          });-        }));+      case "RunJSFunction": {+        eval(event.RunJSFunction);+        continuation();         break;       }+      case "CallJSFunction": {+        var result = eval(event.CallJSFunction);+        signal({FunctionResult : result}, continuation);+        break;+      }       case "Delete": {         event_delete(event);         continuation();@@ -231,11 +208,11 @@         break;       }       case "SetAttr": {-        var set = event.SetAttr;-        var id = set[0];-        var key = set[1];+        var set   = event.SetAttr;+        var id    = set[0];+        var key   = set[1];         var value = set[2];-        var el = elidToElement(id);+        var el    = elidToElement(id);         $(el).attr(key,value);         continuation();         break;@@ -297,8 +274,8 @@         break;       }       case "Bind": {-        var bind = event.Bind;-        var eventType = bind[0];+        var bind        = event.Bind;+        var eventType   = bind[0];         var handlerGuid = bind[2];         var el = elidToElement(bind[1]);         console_log('event type: ' + eventType);@@ -329,6 +306,24 @@             });             return true;           });+        } else if(eventType.match('mousemove')) {+          $(el).bind(eventType,function(e){+            signal({+              Event: handlerGuid.concat([[e.pageX.toString(), e.pageY.toString()]])+            },function(){+              // no action+            });+            return true;+          });+        } else if(eventType.match('keydown|keyup')) {+          $(el).bind(eventType,function(e){+            signal({+              Event: handlerGuid.concat([[e.keyCode.toString()]])+            },function(){+              // no action+            });+            return true;+          });         } else {           $(el).bind(eventType,function(e){             signal({@@ -430,6 +425,37 @@     if (tp_enable_log) {       window.console.log.apply(window.console,arguments);     }+  };+++  ////////////////////////////////////////////////////////////////////////////////+  // Additional functions+  +  window.jquery_animate = function(el_id,props,duration,easing,complete){+    var el = elidToElement(JSON.parse(el_id));+    $(el).animate(JSON.parse(props),+                  duration * 1,+                  easing * 1,+                  complete);+  }++  window.jquery_scrollToBottom = function(el){+    $(el).scrollTop(el.scrollHeight);+  };++  // see http://stackoverflow.com/a/9722502/403805+  CanvasRenderingContext2D.prototype.clear = +    CanvasRenderingContext2D.prototype.clear || function (preserveTransform) {+      if (preserveTransform) {+        this.save();+        this.setTransform(1, 0, 0, 1, 0, 0);+      }++      this.clearRect(0, 0, this.canvas.width, this.canvas.height);++      if (preserveTransform) {+        this.restore();+      }              };  })();
threepenny-gui.cabal view
@@ -1,5 +1,5 @@ Name:                threepenny-gui-Version:             0.1.0.1+Version:             0.2.0.0 Synopsis:            Small GUI framework that uses the web browser as a display. Description:     Threepenny-GUI is a small GUI framework that uses the web browser as a display.@@ -14,6 +14,16 @@     Stability forecast: This is an experimental release! Send us your feedback!     Basic functionality should work.     Significant API changes are likely in future versions.+    .+    NOTE: This library contains examples, but they are not built by default.+    To build and install the example, use the @buildExamples@ flag like this+    .+    @cabal install reactive-banana-threepenny -fbuildExamples@+    .+    Changelog:+    .+    * 0.2.0.0 - Snapshot release. First stab at easy JavaScript FFI.+    * 0.1.0.0 - Initial release.  License:             BSD3 License-file:        LICENSE@@ -32,8 +42,10 @@                     ,src/Graphics/UI/*.html                     ,src/Graphics/UI/*.css                     ,wwwroot/css/*.css+                    ,wwwroot/css/*.png                     ,wwwroot/*.html                     ,wwwroot/*.txt+                    ,wwwroot/*.wav   flag buildExamples@@ -52,10 +64,12 @@                     ,Graphics.UI.Threepenny                     ,Graphics.UI.Threepenny.Attributes                     ,Graphics.UI.Threepenny.Core+                    ,Graphics.UI.Threepenny.Canvas                     ,Graphics.UI.Threepenny.DragNDrop                     ,Graphics.UI.Threepenny.Elements                     ,Graphics.UI.Threepenny.Events                     ,Graphics.UI.Threepenny.JQuery+                    ,Graphics.UI.Threepenny.Timer   Other-modules:                      Control.Concurrent.Chan.Extra                     ,Control.Monad.Extra@@ -81,6 +95,7 @@                     ,filepath                     ,data-default                     ,transformers+                    ,stm                      Executable threepenny-examples-bartab     if flag(buildExamples)@@ -118,6 +133,17 @@     other-modules:     Paths_threepenny_gui, Paths     hs-source-dirs:    src +Executable threepenny-examples-drummachine+    if flag(buildExamples)+        cpp-options:       -DCABAL+        build-depends:     base                      >= 4     && < 5+                          ,threepenny-gui+                          ,filepath+    else+        buildable: False+    main-is:           DrumMachine.hs+    other-modules:     Paths_threepenny_gui, Paths+    hs-source-dirs:    src  Executable threepenny-examples-missing-dollars     if flag(buildExamples)
+ wwwroot/css/fade.png view

binary file changed (absent → 4690 bytes)

+ wwwroot/css/stripes-bg.png view

binary file changed (absent → 162 bytes)

+ wwwroot/css/wood-bg.png view

binary file changed (absent → 628939 bytes)

+ wwwroot/hihat.wav view

binary file changed (absent → 28556 bytes)

+ wwwroot/kick.wav view

binary file changed (absent → 31622 bytes)

+ wwwroot/snare.wav view

binary file changed (absent → 76332 bytes)