elm-core-sources (empty) → 1.0.0
raw patch · 65 files changed
+11861/−0 lines, 65 filesdep +basedep +bytestringdep +containerssetup-changed
Dependencies added: base, bytestring, containers, file-embed, template-haskell
Files
- LICENSE +30/−0
- Setup.hs +2/−0
- elm-core-sources.cabal +51/−0
- haskell/Language/Elm/CoreSources.hs +98/−0
- src/Array.elm +180/−0
- src/Basics.elm +458/−0
- src/Bitwise.elm +74/−0
- src/Char.elm +62/−0
- src/Color.elm +273/−0
- src/Date.elm +77/−0
- src/Debug.elm +88/−0
- src/Dict.elm +484/−0
- src/Graphics/Collage.elm +265/−0
- src/Graphics/Element.elm +394/−0
- src/Graphics/Input.elm +152/−0
- src/Graphics/Input/Field.elm +195/−0
- src/Http.elm +59/−0
- src/Json/Decode.elm +498/−0
- src/Json/Encode.elm +85/−0
- src/Keyboard.elm +82/−0
- src/List.elm +383/−0
- src/Maybe.elm +112/−0
- src/Mouse.elm +36/−0
- src/Native/Array.js +711/−0
- src/Native/Basics.js +118/−0
- src/Native/Bitwise.js +25/−0
- src/Native/Char.js +31/−0
- src/Native/Color.js +30/−0
- src/Native/Date.js +39/−0
- src/Native/Debug.js +65/−0
- src/Native/Graphics/Collage.js +415/−0
- src/Native/Graphics/Element.js +523/−0
- src/Native/Graphics/Input.js +415/−0
- src/Native/Http.js +63/−0
- src/Native/Json.js +485/−0
- src/Native/Keyboard.js +154/−0
- src/Native/List.js +326/−0
- src/Native/Mouse.js +60/−0
- src/Native/Ports.js +92/−0
- src/Native/Regex.js +103/−0
- src/Native/Runtime.js +560/−0
- src/Native/Show.js +150/−0
- src/Native/Signal.js +235/−0
- src/Native/String.js +269/−0
- src/Native/Text.js +173/−0
- src/Native/Time.js +64/−0
- src/Native/Touch.js +171/−0
- src/Native/Trampoline.js +24/−0
- src/Native/Transform2D.js +99/−0
- src/Native/Utils.js +305/−0
- src/Native/WebSocket.js +36/−0
- src/Native/Window.js +62/−0
- src/Random.elm +314/−0
- src/Regex.elm +123/−0
- src/Result.elm +128/−0
- src/Set.elm +101/−0
- src/Signal.elm +305/−0
- src/String.elm +345/−0
- src/Text.elm +261/−0
- src/Time.elm +105/−0
- src/Touch.elm +41/−0
- src/Trampoline.elm +51/−0
- src/Transform2D.elm +98/−0
- src/WebSocket.elm +21/−0
- src/Window.elm +27/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2014, Evan Czaplicki + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Evan Czaplicki nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ elm-core-sources.cabal view
@@ -0,0 +1,51 @@+Name: elm-core-sources +Version: 1.0.0 +Synopsis: Source files for the Elm runtime and standard libraries +Description: + Embeds the standard libraries and runtime files of the Elm language as Haskell strings, + so that they can be used for compilation and served in a Haskell web server. + For more information on the Elm language, see http://elm-lang.org. +Homepage: http://github.com/JoeyEremondi/elm-build-lib + +License: BSD3 +License-file: LICENSE + +cabal-version: >=1.6 +build-type: Simple +author: Evan Czaplicki +Maintainer: joey@eremondi.com +Copyright: Copyright: (c) 2014 Evan Czaplicki + +Category: Compiler, Language + +Extra-source-files: src/*.elm + + ,src/Json/*.elm + + ,src/Graphics/*.elm + + ,src/Graphics/Input/*.elm + + ,src/Graphics/Input/*.elm + + src/Native/*.js + + ,src/Native/Graphics/*.js + +source-repository head + type: git + location: git://github.com/JoeyEremondi/elm-build-lib + + + +library + exposed-modules: Language.Elm.CoreSources + hs-source-dirs: haskell + + build-depends: base >=4.2 && <5 + , containers + , template-haskell + , file-embed + , bytestring + ghc-options: -Wall +
+ haskell/Language/Elm/CoreSources.hs view
@@ -0,0 +1,98 @@+{-# LANGUAGE TemplateHaskell #-} +module Language.Elm.CoreSources where + +import Data.FileEmbed +import Data.ByteString.Char8 (unpack) + +{-| +JavaScript Header to append at the beginning of a complete, linked Elm program. +-} +header :: String +header = "var Elm = Elm || { Native: {} };\n" + +{-| +Source for the Elm Runtime library +-} +runtime :: String +runtime = unpack $(embedFile "src/Native/Runtime.js") + +{-| +Dictionary mapping strings of the form "Native.ModuleName" to the JS source code +for that stdlib native module. +-} +nativeSources :: [(String, String)] +nativeSources = map (\(x,y) -> (x, unpack y)) $ [ + ("Native.Array", $(embedFile "src/Native/Array.js")) + , ("Native.Basics", $(embedFile "src/Native/Basics.js")) + , ("Native.Bitwise", $(embedFile "src/Native/Bitwise.js")) + , ("Native.Char", $(embedFile "src/Native/Char.js")) + , ("Native.Color", $(embedFile "src/Native/Color.js")) + , ("Native.Date", $(embedFile "src/Native/Date.js")) + , ("Native.Debug", $(embedFile "src/Native/Debug.js")) + , ("Native.Http", $(embedFile "src/Native/Http.js")) + , ("Native.Json", $(embedFile "src/Native/Json.js")) + , ("Native.Keyboard", $(embedFile "src/Native/Keyboard.js")) + , ("Native.List", $(embedFile "src/Native/List.js")) + , ("Native.Mouse", $(embedFile "src/Native/Mouse.js")) + , ("Native.Ports", $(embedFile "src/Native/Ports.js")) + , ("Native.Regex", $(embedFile "src/Native/Regex.js")) + , ("Native.Show", $(embedFile "src/Native/Show.js")) + , ("Native.Signal", $(embedFile "src/Native/Signal.js")) + , ("Native.String", $(embedFile "src/Native/String.js")) + , ("Native.Text", $(embedFile "src/Native/Text.js")) + , ("Native.Time", $(embedFile "src/Native/Time.js")) + , ("Native.Touch", $(embedFile "src/Native/Touch.js")) + , ("Native.Trampoline", $(embedFile "src/Native/Trampoline.js")) + , ("Native.Transform2D", $(embedFile "src/Native/Transform2D.js")) + , ("Native.Utils", $(embedFile "src/Native/Utils.js")) + , ("Native.WebSocket", $(embedFile "src/Native/WebSocket.js")) + , ("Native.Window", $(embedFile "src/Native/Window.js")) + + , ("Native.Graphics.Collage", $(embedFile "src/Native/Graphics/Collage.js")) + , ("Native.Graphics.Element", $(embedFile "src/Native/Graphics/Element.js")) + , ("Native.Graphics.Input", $(embedFile "src/Native/Graphics/Input.js")) + + + ] + +{-| +List of .elm sources for the Elm Standard Library +-} +stdlibSources :: [String] +stdlibSources = map unpack [$(embedFile "src/Array.elm") + ,$(embedFile "src/Array.elm") + ,$(embedFile "src/Basics.elm") + ,$(embedFile "src/Bitwise.elm") + ,$(embedFile "src/Char.elm") + ,$(embedFile "src/Color.elm") + ,$(embedFile "src/Date.elm") + ,$(embedFile "src/Debug.elm") + ,$(embedFile "src/Dict.elm") + ,$(embedFile "src/Http.elm") + ,$(embedFile "src/Keyboard.elm") + ,$(embedFile "src/List.elm") + ,$(embedFile "src/Maybe.elm") + ,$(embedFile "src/Mouse.elm") + ,$(embedFile "src/Random.elm") + ,$(embedFile "src/Regex.elm") + ,$(embedFile "src/Result.elm") + ,$(embedFile "src/Set.elm") + ,$(embedFile "src/Signal.elm") + ,$(embedFile "src/String.elm") + ,$(embedFile "src/Text.elm") + ,$(embedFile "src/Time.elm") + ,$(embedFile "src/Touch.elm") + ,$(embedFile "src/Trampoline.elm") + ,$(embedFile "src/Transform2D.elm") + ,$(embedFile "src/WebSocket.elm") + ,$(embedFile "src/Window.elm") + + ,$(embedFile "src/Json/Encode.elm") + ,$(embedFile "src/Json/Decode.elm") + + ,$(embedFile "src/Graphics/Collage.elm") + ,$(embedFile "src/Graphics/Element.elm") + ,$(embedFile "src/Graphics/Input.elm") + + ,$(embedFile "src/Graphics/Input/Field.elm") + ]
+ src/Array.elm view
@@ -0,0 +1,180 @@+module Array where + +{-| A library for fast immutable arrays. The elements in an array must have the +same type. The arrays are implemented in Relaxed Radix Balanced-Trees for fast +reads, updates, and appends. + +# Creating Arrays +@docs empty, repeat, initialize, fromList + +# Basics +@docs length, push, append + +# Get and Set +@docs get, set + +# Taking Arrays Apart +@docs slice, toList, toIndexedList + +# Mapping and Folding +@docs map, indexedMap, foldl, foldr +-} + +import Native.Array +import Basics (..) +import Maybe (..) +import List + +type Array a = Array + +{-| Initialize an array. `initialize n f` creates an array of length `n` with +the element at index `i` initialized to the result of `(f i)`. + + initialize 4 identity == fromList [0,1,2,3] + initialize 4 (\n -> n*n) == fromList [0,1,4,9] + initialize 4 (always 0) == fromList [0,0,0,0] +-} +initialize : Int -> (Int -> a) -> Array a +initialize = Native.Array.initialize + +{-| Creates an array with a given length, filled with a default element. + + repeat 5 0 == fromList [0,0,0,0,0] + repeat 3 "cat" == fromList ["cat","cat","cat"] + +Notice that `repeat 3 x` is the same as `initialize 3 (always x)`. +-} +repeat : Int -> a -> Array a +repeat n e = initialize n (always e) + +{-| Create an array from a list. -} +fromList : List a -> Array a +fromList = Native.Array.fromList + +{-| Create a list of elements from an array. + + toList (fromList [3,5,8]) == [3,5,8] +-} +toList : Array a -> List a +toList = Native.Array.toList + +-- TODO: make this a native function. +{-| Create an indexed list from an array. Each element of the array will be +paired with its index. + + toIndexedList (fromList ["cat","dog"]) == [(0,"cat"), (1,"dog")] +-} +toIndexedList : Array a -> List (Int, a) +toIndexedList array = + List.map2 (,) [ 0 .. Native.Array.length array - 1 ] (Native.Array.toList array) + +{-| Apply a function on every element in an array. + + map sqrt (fromList [1,4,9]) == fromList [1,2,3] +-} +map : (a -> b) -> Array a -> Array b +map = Native.Array.map + +{-| Apply a function on every element with its index as first argument. + + indexedMap (*) (fromList [5,5,5]) == fromList [0,5,10] +-} +indexedMap : (Int -> a -> b) -> Array a -> Array b +indexedMap = Native.Array.indexedMap + +{-| Reduce an array from the left. Read `foldl` as “fold from the left”. + + foldl (::) [] (fromList [1,2,3]) == [3,2,1] +-} +foldl : (a -> b -> b) -> b -> Array a -> b +foldl = Native.Array.foldl + +{-| Reduce an array from the right. Read `foldr` as “fold from the right”. + + foldr (+) 0 (repeat 3 5) == 15 +-} +foldr : (a -> b -> b) -> b -> Array a -> b +foldr = Native.Array.foldr + +{-| Keep only elements that satisfy the predicate: + + filter isEven (fromList [1..6]) == (fromList [2,4,6]) +-} +filter : (a -> Bool) -> Array a -> Array a +filter isOkay arr = + let update x xs = if isOkay x then Native.Array.push x xs else xs + in + Native.Array.foldl update Native.Array.empty arr + +{-| Return an empty array. + + length empty == 0 +-} +empty : Array a +empty = Native.Array.empty + +{-| Push an element to the end of an array. + + push 3 (fromList [1,2]) == fromList [1,2,3] +-} +push : a -> Array a -> Array a +push = Native.Array.push + +{-| Return Just the element at the index or Nothing if the index is out of range. + + get 0 (fromList [0,1,2]) == Just 0 + get 2 (fromList [0,1,2]) == Just 2 + get 5 (fromList [0,1,2]) == Nothing + get -1 (fromList [0,1,2]) == Nothing + +The `(?)` operator from the `Maybe` library makes it easy to give a default +value. +-} +get : Int -> Array a -> Maybe a +get i array = + if 0 <= i && i < Native.Array.length array + then Just (Native.Array.get i array) + else Nothing + + +{-| Set the element at a particular index. Returns an updated array. +If the index is out of range, the array is unaltered. + + set 1 7 (fromList [1,2,3]) == fromList [1,7,3] +-} +set : Int -> a -> Array a -> Array a +set = Native.Array.set + + +{-| Get a sub-section of an array: `(slice start end array)`. The `start` is a +zero-based index where we will start our slice. The `end` is a zero-based index +that indicates the end of the slice. The slice extracts up to but not including +`end`. + + slice 0 3 (fromList [0,1,2,3,4]) == fromList [0,1,2] + slice 1 4 (fromList [0,1,2,3,4]) == fromList [1,2,3] + +Both the `start` and `end` indexes can be negative, indicating an offset from +the end of the array. + + slice 1 -1 (fromList [0,1,2,3,4]) == fromList [1,2,3] + slice -2 5 (fromList [0,1,2,3,4]) == fromList [3,4] + +This makes it pretty easy to `pop` the last element off of an array: `slice 0 -1 array` +-} +slice : Int -> Int -> Array a -> Array a +slice = Native.Array.slice + +{-| Return the length of an array. + + length (fromList [1,2,3]) == 3 +-} +length : Array a -> Int +length = Native.Array.length + +{-| Append two arrays to a new one. + + append (repeat 2 42) (repeat 3 81) == fromList [42,42,81,81,81] +-} +append : Array a -> Array a -> Array a +append = Native.Array.append
+ src/Basics.elm view
@@ -0,0 +1,458 @@+ +module Basics where + +{-| Tons of useful functions that get imported by default. + +# Equality +@docs (==), (/=) + +# Comparison + +These functions only work on `comparable` types. This includes numbers, +characters, strings, lists of comparable things, and tuples of comparable +things. Note that tuples with 7 or more elements are not comparable; why +are your tuples so big? + +@docs (<), (>), (<=), (>=), max, min, Order, compare + +# Booleans +@docs not, (&&), (||), xor, otherwise + +# Mathematics +@docs (+), (-), (*), (/), (^), (//), rem, (%), negate, abs, sqrt, clamp, logBase, e + +# Trigonometry +@docs pi, cos, sin, tan, acos, asin, atan, atan2 + +# Number Conversions +@docs round, floor, ceiling, truncate, toFloat + +# Angle Conversions +All angle conversions result in “standard Elm angles” +which happen to be radians. + +@docs degrees, radians, turns + +# Polar Coordinates +@docs toPolar, fromPolar + +# Floating Point Checks +@docs isNaN, isInfinite + +# Strings and Lists +@docs toString, (++) + +# Tuples +@docs fst, snd + +# Higher-Order Helpers +@docs identity, always, (<|), (|>), (<<), (>>), flip, curry, uncurry + +-} + +import Native.Basics +import Native.Ports +import Native.Show +import Native.Utils +import Native.Runtime + + +{-| Convert radians to standard Elm angles (radians). -} +radians : Float -> Float +radians t = t + +{-| Convert degrees to standard Elm angles (radians). -} +degrees : Float -> Float +degrees = Native.Basics.degrees + +{-| Convert turns to standard Elm angles (radians). +One turn is equal to 360°. +-} +turns : Float -> Float +turns = Native.Basics.turns + +{-| Convert polar coordinates (r,θ) to cartesian coordinates (x,y). -} +fromPolar : (Float,Float) -> (Float,Float) +fromPolar = Native.Basics.fromPolar + +{-| Convert cartesian coordinates (x,y) to polar coordinates (r,θ). -} +toPolar : (Float,Float) -> (Float,Float) +toPolar = Native.Basics.toPolar + +(+) : number -> number -> number +(+) = Native.Basics.add + +(-) : number -> number -> number +(-) = Native.Basics.sub + +(*) : number -> number -> number +(*) = Native.Basics.mul + +{-| Floating point division. -} +(/) : Float -> Float -> Float +(/) = Native.Basics.floatDiv + +infixl 6 + +infixl 6 - +infixl 7 * +infixl 7 / +infixl 8 ^ + +infixl 7 // +infixl 7 % +infixl 7 `rem` + +{-| Integer division. The remainder is discarded. -} +(//) : Int -> Int -> Int +(//) = Native.Basics.div + +{-| Find the remainder after dividing one number by another. + + 7 `rem` 2 == 1 + -1 `rem` 4 == -1 +-} +rem : Int -> Int -> Int +rem = Native.Basics.rem + +{-| Perform [modular arithmetic](http://en.wikipedia.org/wiki/Modular_arithmetic). + + 7 % 2 == 1 + -1 % 4 == 3 +-} +(%) : Int -> Int -> Int +(%) = Native.Basics.mod + +{-| Exponentiation + + 3^2 == 9` +-} +(^) : number -> number -> number +(^) = Native.Basics.exp + +cos : Float -> Float +cos = Native.Basics.cos + +sin : Float -> Float +sin = Native.Basics.sin + +tan : Float -> Float +tan = Native.Basics.tan + +acos : Float -> Float +acos = Native.Basics.acos + +asin : Float -> Float +asin = Native.Basics.asin + +{-| You probably do not want to use this. It takes `(y/x)` as the +argument, so there is no way to know whether the negative signs comes from +the `y` or `x`. Thus, the resulting angle is always between π/2 and -π/2 +(in quadrants I and IV). You probably want to use `atan2` instead. +-} +atan : Float -> Float +atan = Native.Basics.atan + +{-| This helps you find the angle of a cartesian coordinate. +You will almost certainly want to use this instead of `atan`. +So `atan2 y x` computes *atan(y/x)* but also keeps track of which +quadrant the angle should really be in. The result will be between +π and -π, giving you the full range of angles. +-} +atan2 : Float -> Float -> Float +atan2 = Native.Basics.atan2 + +{-| Take the square root of a number. -} +sqrt : Float -> Float +sqrt = Native.Basics.sqrt + +{-| Negate a number. + + negate 42 == -42 + negate -42 == 42 + negate 0 == 0 +-} +negate : number -> number +negate = Native.Basics.negate + +{-| Take the absolute value of a number. -} +abs : number -> number +abs = Native.Basics.abs + +{-| Calculate the logarithm of a number with a given base. + + logBase 10 100 == 2 + logBase 2 256 == 8 +-} +logBase : Float -> Float -> Float +logBase = Native.Basics.logBase + +{-| Clamps a number within a given range. With the expression +`clamp 100 200 x` the results are as follows: + + 100 if x < 100 + x if 100 <= x < 200 + 200 if 200 <= x +-} +clamp : number -> number -> number -> number +clamp = Native.Basics.clamp + +{-| An approximation of pi. -} +pi : Float +pi = Native.Basics.pi + +{-| An approximation of e. -} +e : Float +e = Native.Basics.e + +(==) : a -> a -> Bool +(==) = Native.Basics.eq + +(/=) : a -> a -> Bool +(/=) = Native.Basics.neq + +(<) : comparable -> comparable -> Bool +(<) = Native.Basics.lt + +(>) : comparable -> comparable -> Bool +(>) = Native.Basics.gt + +(<=) : comparable -> comparable -> Bool +(<=) = Native.Basics.le + +(>=) : comparable -> comparable -> Bool +(>=) = Native.Basics.ge + +infix 4 == +infix 4 /= +infix 4 < +infix 4 > +infix 4 <= +infix 4 >= + +{-| Compare any two comparable values. Comparable values include `String`, `Char`, +`Int`, `Float`, `Time`, or a list or tuple containing comparable values. +These are also the only values that work as `Dict` keys or `Set` members. +-} +compare : comparable -> comparable -> Order +compare = Native.Basics.compare + +{-| Represents the relative ordering of two things. +The relations are less than, equal to, and greater than. +-} +type Order = LT | EQ | GT + +{-| Find the smaller of two comparables. -} +min : comparable -> comparable -> comparable +min = Native.Basics.min + +{-| Find the larger of two comparables. -} +max : comparable -> comparable -> comparable +max = Native.Basics.max + +{-| The logical AND operator. `True` if both inputs are `True`. +This operator short-circuits to `False` if the first argument is `False`. +-} +(&&) : Bool -> Bool -> Bool +(&&) = Native.Basics.and + +{-| The logical OR operator. `True` if one or both inputs are `True`. +This operator short-circuits to `True` if the first argument is True. +-} +(||) : Bool -> Bool -> Bool +(||) = Native.Basics.or + +infixr 3 && +infixr 2 || + +{-| The exclusive-or operator. `True` if exactly one input is `True`. -} +xor : Bool -> Bool -> Bool +xor = Native.Basics.xor + +{-| Negate a boolean value. + + not True == False + not False == True +-} +not : Bool -> Bool +not = Native.Basics.not + +{-| Equal to `True`. Useful as the last case of a multi-way-if. -} +otherwise : Bool +otherwise = True + + +-- Conversions + +{-| Round a number to the nearest integer. -} +round : Float -> Int +round = Native.Basics.round + +{-| Truncate a number, rounding towards zero. -} +truncate : Float -> Int +truncate = Native.Basics.truncate + +{-| Floor function, rounding down. -} +floor : Float -> Int +floor = Native.Basics.floor + +{-| Ceiling function, rounding up. -} +ceiling : Float -> Int +ceiling = Native.Basics.ceiling + +{-| Convert an integer into a float. -} +toFloat : Int -> Float +toFloat = Native.Basics.toFloat + +{- | Determine whether a float is an undefined or unrepresentable number. +NaN stands for *not a number* and it is [a standardized part of floating point +numbers](http://en.wikipedia.org/wiki/NaN). + + isNaN (0/0) == True + isNaN (sqrt -1) == True + isNaN (1/0) == False -- infinity is a number + isNaN 1 == False +-} +isNaN : Float -> Bool +isNaN = Native.Basics.isNaN + +{- | Determine whether a float is positive or negative infinity. + + isInfinite (0/0) == False + isInfinite (sqrt -1) == False + isInfinite (1/0) == True + isInfinite 1 == False + +Notice that NaN is not infinite! For float `n` to be finite implies that +`not (isInfinite n || isNaN n)` evaluates to `True`. +-} +isInfinite : Float -> Bool +isInfinite = Native.Basics.isInfinite + + +{-| Turn any kind of value into a string. + + toString 42 == "42" + toString [1,2] == "[1,2]" +-} +toString : a -> String +toString = Native.Show.toString + + +{-| Put two appendable things together. This includes strings, lists, and text. + + "hello" ++ "world" == "helloworld" + [1,1,2] ++ [3,5,8] == [1,1,2,3,5,8] +-} +(++) : appendable -> appendable -> appendable +(++) = Native.Utils.append + +infixr 5 ++ + + +-- Function Helpers + +{-| Function composition, passing results along in the suggested direction. For +example, the following code checks if the square root of a number is odd: + + not << isEven << sqrt + +You can think of this operator as equivalent to the following: + + (g << f) == (\x -> g (f x)) + +So our example expands out to something like this: + + \n -> not (isEven (sqrt n)) +-} +(<<) : (b -> c) -> (a -> b) -> (a -> c) +(<<) g f x = g (f x) + +{-| Function composition, passing results along in the suggested direction. For +example, the following code checks if the square root of a number is odd: + + sqrt >> isEven >> not + +This direction of function composition seems less pleasant than `(<<)` which +reads nicely in expressions like: `filter (not << isRegistered) students` +-} +(>>) : (a -> b) -> (b -> c) -> (a -> c) +(>>) f g x = g (f x) + +{-| Forward function application `x |> f == f x`. This function is useful +for avoiding parenthesis and writing code in a more natural way. +Consider the following code to create a pentagon: + + scale 2 (move (10,10) (filled blue (ngon 5 30))) + +This can also be written as: + + ngon 5 30 + |> filled blue + |> move (10,10) + |> scale 2 +-} +(|>) : a -> (a -> b) -> b +x |> f = f x + +{-| Backward function application `f <| x == f x`. This function is useful for +avoiding parenthesis. Consider the following code to create a text element: + + leftAligned (monospace (fromString "code")) + +This can also be written as: + + leftAligned << monospace <| fromString "code" +-} +(<|) : (a -> b) -> a -> b +f <| x = f x + +infixr 9 << +infixl 9 >> +infixr 0 <| +infixl 0 |> + +{-| Given a value, returns exactly the same value. This is called +[the identity function](http://en.wikipedia.org/wiki/Identity_function). +-} +identity : a -> a +identity x = x + +{-| Create a [constant function](http://en.wikipedia.org/wiki/Constant_function), +a function that *always* returns the same value regardless of what input you give. +It is defined as: + + always a b = a + +It totally ignores the second argument, so `always 42` is a function that always +returns 42. When you are dealing with higher-order functions, this comes in +handy more often than you might expect. For example, creating a zeroed out list +of length ten would be: + + map (always 0) [0..9] +-} +always : a -> b -> a +always a _ = a + +{-| Given a 2-tuple, returns the first value. -} +fst : (a,b) -> a +fst (a,_) = a + +{-| Given a 2-tuple, returns the second value. -} +snd : (a,b) -> b +snd (_,b) = b + +{-| Flip the order of the first two arguments to a function. -} +flip : (a -> b -> c) -> (b -> a -> c) +flip f b a = f a b + +{-| Change how arguments are passed to a function. +This splits paired arguments into two separate arguments. +-} +curry : ((a,b) -> c) -> a -> b -> c +curry f a b = f (a,b) + +{-| Change how arguments are passed to a function. +This combines two arguments into a single pair. +-} +uncurry : (a -> b -> c) -> (a,b) -> c +uncurry f (a,b) = f a b
+ src/Bitwise.elm view
@@ -0,0 +1,74 @@+module Bitwise where + +{-| Library for [bitwise operations](http://en.wikipedia.org/wiki/Bitwise_operation). + +# Basic Operations + +@docs and, or, xor, complement + +# Bit Shifts + +@docs shiftLeft, shiftRight, shiftRightLogical +-} + +import Native.Bitwise + +{-| Bitwise AND +-} +and : Int -> Int -> Int +and = Native.Bitwise.and + +{-| Bitwise OR +-} +or : Int -> Int -> Int +or = Native.Bitwise.or + +{-| Bitwise XOR +-} +xor : Int -> Int -> Int +xor = Native.Bitwise.xor + +{-| Flip each bit individually, often called bitwise NOT +-} +complement : Int -> Int +complement = Native.Bitwise.complement + +{-| Shift bits to the left by a given offset, filling new bits with zeros. +This can be used to multiply numbers by powers of two. + + 8 `shiftLeft` 1 == 16 + 8 `shiftLeft` 2 == 32 +-} +shiftLeft : Int -> Int -> Int +shiftLeft = Native.Bitwise.shiftLeft + +{-| Shift bits to the right by a given offset, filling new bits with +whatever is the topmost bit. This can be used to divide numbers by powers of two. + + 32 `shiftRight` 1 == 16 + 32 `shiftRight` 2 == 8 + -32 `shiftRight` 1 == -16 + +This is called an [arithmatic right +shift](http://en.wikipedia.org/wiki/Bitwise_operation#Arithmetic_shift), +often written (>>), and sometimes called a sign-propagating +right shift because it fills empty spots with copies of the highest bit. +-} +shiftRight : Int -> Int -> Int +shiftRight = Native.Bitwise.shiftRightArithmatic + +{-| Shift bits to the right by a given offset, filling new bits with +zeros. + + 32 `shiftRightLogical` 1 == 16 + 32 `shiftRightLogical` 2 == 8 + -32 `shiftRightLogical` 1 == 2147483632 + +This is called an [logical right +shift](http://en.wikipedia.org/wiki/Bitwise_operation#Logical_shift), often written (>>>), +and sometimes called a zero-fill right shift because it fills empty spots +with zeros. +-} +shiftRightLogical : Int -> Int -> Int +shiftRightLogical = Native.Bitwise.shiftRightLogical +
+ src/Char.elm view
@@ -0,0 +1,62 @@+ +module Char where + +{-| Functions for working with characters. Character literals are enclosed in +`'a'` pair of single quotes. + +# Classification +@docs isUpper, isLower, isDigit, isOctDigit, isHexDigit + +# Conversion +@docs toUpper, toLower, toLocaleUpper, toLocaleLower, toCode, fromCode + +-} + +import Native.Char + +{-| True for upper case letters. -} +isUpper : Char -> Bool +isUpper = Native.Char.isUpper + +{-| True for lower case letters. -} +isLower : Char -> Bool +isLower = Native.Char.isLower + +{-| True for ASCII digits `[0-9]`. -} +isDigit : Char -> Bool +isDigit = Native.Char.isDigit + +{-| True for ASCII octal digits `[0-7]`. -} +isOctDigit : Char -> Bool +isOctDigit = Native.Char.isOctDigit + +{-| True for ASCII hexadecimal digits `[0-9a-fA-F]`. -} +isHexDigit : Char -> Bool +isHexDigit = Native.Char.isHexDigit + +{-| Convert to upper case. -} +toUpper : Char -> Char +toUpper = Native.Char.toUpper + +{-| Convert to lower case. -} +toLower : Char -> Char +toLower = Native.Char.toLower + +{-| Convert to upper case, according to any locale-specific case mappings. -} +toLocaleUpper : Char -> Char +toLocaleUpper = Native.Char.toLocaleUpper + +{-| Convert to lower case, according to any locale-specific case mappings. -} +toLocaleLower : Char -> Char +toLocaleLower = Native.Char.toLocaleLower + +type alias KeyCode = Int + +{-| Convert to unicode. Used with the `Keyboard` library, which expects the +input to be uppercase. -} +toCode : Char -> KeyCode +toCode = Native.Char.toCode + +{-| Convert from unicode. -} +fromCode : KeyCode -> Char +fromCode = Native.Char.fromCode
+ src/Color.elm view
@@ -0,0 +1,273 @@+module Color where + +{-| Library for working with colors. Includes +[RGB](https://en.wikipedia.org/wiki/RGB_color_model) and +[HSL](http://en.wikipedia.org/wiki/HSL_and_HSV) creation, gradients, and +built-in names. + +# Creation +@docs rgb, rgba, hsl, hsla, greyscale, grayscale, complement + +# Gradients +@docs linear, radial + +# Extracting Colors +@docs toRgb, toHsl + +# Built-in Colors +These colors come from the [Tango +palette](http://tango.freedesktop.org/Tango_Icon_Theme_Guidelines) +which provides aesthetically reasonable defaults for colors. Each color also +comes with a light and dark version. + +### Standard +@docs red, orange, yellow, green, blue, purple, brown + +### Light +@docs lightRed, lightOrange, lightYellow, lightGreen, lightBlue, lightPurple, lightBrown + +### Dark +@docs darkRed, darkOrange, darkYellow, darkGreen, darkBlue, darkPurple, darkBrown + +### Eight Shades of Grey +These colors are a compatible series of shades of grey, fitting nicely +with the Tango palette. +@docs white, lightGrey, grey, darkGrey, lightCharcoal, charcoal, darkCharcoal, black + +These are identical to the *grey* versions. It seems the spelling is regional, but +that has never helped me remember which one I should be writing. +@docs lightGray, gray, darkGray + +-} + +import Basics (..) +import Native.Color + +type Color + = RGBA Int Int Int Float + | HSLA Float Float Float Float + +{-| Create RGB colors with an alpha component for transparency. +The alpha component is specified with numbers between 0 and 1. -} +rgba : Int -> Int -> Int -> Float -> Color +rgba = RGBA + +{-| Create RGB colors from numbers between 0 and 255 inclusive. -} +rgb : Int -> Int -> Int -> Color +rgb r g b = RGBA r g b 1 + +{-| Create [HSL colors](http://en.wikipedia.org/wiki/HSL_and_HSV) +with an alpha component for transparency. +-} +hsla : Float -> Float -> Float -> Float -> Color +hsla hue saturation lightness alpha = + HSLA (hue - turns (toFloat (floor (hue / (2*pi))))) saturation lightness alpha + +{-| Create [HSL colors](http://en.wikipedia.org/wiki/HSL_and_HSV). This gives +you access to colors more like a color wheel, where all hues are aranged in a +circle that you specify with standard Elm angles (radians). + + red = hsl (degrees 0) 1 0.5 + green = hsl (degrees 120) 1 0.5 + blue = hsl (degrees 240) 1 0.5 + + pastelRed = hsl (degrees 0) 0.7 0.7 + +To cycle through all colors, just cycle through degrees. The saturation level +is how vibrant the color is, like a dial between grey and bright colors. The +lightness level is a dial between white and black. +-} +hsl : Float -> Float -> Float -> Color +hsl hue saturation lightness = + hsla hue saturation lightness 1 + +{-| Produce a gray based on the input. 0 is white, 1 is black. -} +grayscale : Float -> Color +grayscale p = HSLA 0 0 (1-p) 1 + +greyscale : Float -> Color +greyscale p = HSLA 0 0 (1-p) 1 + +{-| Produce a “complementary color”. The two colors will +accent each other. This is the same as rotating the hue by 180°. +-} +complement : Color -> Color +complement color = + case color of + HSLA h s l a -> hsla (h + degrees 180) s l a + RGBA r g b a -> let (h,s,l) = rgbToHsl r g b + in hsla (h + degrees 180) s l a + +{-| Extract the components of a color in the HSL format. +-} +toHsl : Color -> { hue:Float, saturation:Float, lightness:Float, alpha:Float } +toHsl color = + case color of + HSLA h s l a -> { hue=h, saturation=s, lightness=l, alpha=a } + RGBA r g b a -> + let (h,s,l) = rgbToHsl r g b + in { hue=h, saturation=s, lightness=l, alpha=a } + +{-| Extract the components of a color in the RGB format. +-} +toRgb : Color -> { red:Int, green:Int, blue:Int, alpha:Float } +toRgb color = + case color of + RGBA r g b a -> { red=r, green=g, blue=b, alpha=a } + HSLA h s l a -> + let (r,g,b) = hslToRgb h s l + in { red = round (255 * r) + , green = round (255 * g) + , blue = round (255 * b) + , alpha = a + } + +fmod : Float -> Int -> Float +fmod f n = + let integer = floor f + in toFloat (integer % n) + f - toFloat integer + +rgbToHsl : Int -> Int -> Int -> (Float,Float,Float) +rgbToHsl red green blue = + let r = toFloat red / 255 + g = toFloat green / 255 + b = toFloat blue / 255 + + cMax = max (max r g) b + cMin = min (min r g) b + + c = cMax - cMin + + hue = degrees 60 * if | cMax == r -> ((g - b) / c) `fmod` 6 + | cMax == g -> ((b - r) / c) + 2 + | cMax == b -> ((r - g) / c) + 4 + + lightness = (cMax + cMin) / 2 + + saturation = + if lightness == 0 + then 0 + else c / (1 - abs (2 * lightness - 1)) + in + (hue, saturation, lightness) + +hslToRgb : Float -> Float -> Float -> (Float,Float,Float) +hslToRgb hue saturation lightness = + let chroma = (1 - abs (2 * lightness - 1)) * saturation + hue' = hue / degrees 60 + + x = chroma * (1 - abs (fmod hue' 2 - 1)) + + (r,g,b) = if | hue' < 0 -> (0, 0, 0) + | hue' < 1 -> (chroma, x, 0) + | hue' < 2 -> (x, chroma, 0) + | hue' < 3 -> (0, chroma, x) + | hue' < 4 -> (0, x, chroma) + | hue' < 5 -> (x, 0, chroma) + | hue' < 6 -> (chroma, 0, x) + | otherwise -> (0, 0, 0) + + m = lightness - chroma / 2 + in + (r + m, g + m, b + m) + +--toV3 : Color -> V3 + +--toV4 : Color -> V4 + +type Gradient + = Linear (Float,Float) (Float,Float) (List (Float,Color)) + | Radial (Float,Float) Float (Float,Float) Float (List (Float,Color)) + +{-| Create a linear gradient. Takes a start and end point and then a series of +“color stops” that indicate how to interpolate between the start and +end points. See [this example](http://elm-lang.org/edit/examples/Elements/LinearGradient.elm) for a +more visual explanation. -} +linear : (number, number) -> (number, number) -> List (Float,Color) -> Gradient +linear = Linear + +{-| Create a radial gradient. First takes a start point and inner radius. Then +takes an end point and outer radius. It then takes a series of “color +stops” that indicate how to interpolate between the inner and outer +circles. See [this example](http://elm-lang.org/edit/examples/Elements/RadialGradient.elm) for a +more visual explanation. -} +radial : (number,number) -> number -> (number,number) -> number -> List (Float,Color) -> Gradient +radial = Radial + + +-- BUILT-IN COLORS + +lightRed : Color +lightRed = RGBA 239 41 41 1 +red : Color +red = RGBA 204 0 0 1 +darkRed : Color +darkRed = RGBA 164 0 0 1 + +lightOrange : Color +lightOrange = RGBA 252 175 62 1 +orange : Color +orange = RGBA 245 121 0 1 +darkOrange : Color +darkOrange = RGBA 206 92 0 1 + +lightYellow : Color +lightYellow = RGBA 255 233 79 1 +yellow : Color +yellow = RGBA 237 212 0 1 +darkYellow : Color +darkYellow = RGBA 196 160 0 1 + +lightGreen : Color +lightGreen = RGBA 138 226 52 1 +green : Color +green = RGBA 115 210 22 1 +darkGreen : Color +darkGreen = RGBA 78 154 6 1 + +lightBlue : Color +lightBlue = RGBA 114 159 207 1 +blue : Color +blue = RGBA 52 101 164 1 +darkBlue : Color +darkBlue = RGBA 32 74 135 1 + +lightPurple : Color +lightPurple = RGBA 173 127 168 1 +purple : Color +purple = RGBA 117 80 123 1 +darkPurple : Color +darkPurple = RGBA 92 53 102 1 + +lightBrown : Color +lightBrown = RGBA 233 185 110 1 +brown : Color +brown = RGBA 193 125 17 1 +darkBrown : Color +darkBrown = RGBA 143 89 2 1 + +black : Color +black = RGBA 0 0 0 1 +white : Color +white = RGBA 255 255 255 1 + +lightGrey : Color +lightGrey = RGBA 238 238 236 1 +grey : Color +grey = RGBA 211 215 207 1 +darkGrey : Color +darkGrey = RGBA 186 189 182 1 + +lightGray : Color +lightGray = RGBA 238 238 236 1 +gray : Color +gray = RGBA 211 215 207 1 +darkGray : Color +darkGray = RGBA 186 189 182 1 + +lightCharcoal : Color +lightCharcoal = RGBA 136 138 133 1 +charcoal : Color +charcoal = RGBA 85 87 83 1 +darkCharcoal : Color +darkCharcoal = RGBA 46 52 54 1
+ src/Date.elm view
@@ -0,0 +1,77 @@+ +module Date where + +{-| Library for working with dates. Email the mailing list if you encounter +issues with internationalization or locale formatting. + +# Conversions +@docs fromString, toTime, fromTime + +# Extractions +@docs year, month, Month, day, dayOfWeek, Day, hour, minute, second + +-} + +import Native.Date +import Time (Time) +import Result (Result) + +type Date = Date + +{-| Represents the days of the week. -} +type Day = Mon | Tue | Wed | Thu | Fri | Sat | Sun + +{-| Represents the month of the year. -} +type Month + = Jan | Feb | Mar | Apr + | May | Jun | Jul | Aug + | Sep | Oct | Nov | Dec + +{-| Attempt to read a date from a string. -} +fromString : String -> Result String Date +fromString = Native.Date.read + +{-| Convert a date into a time since midnight (UTC) of 1 January 1990 (i.e. +[UNIX time](http://en.wikipedia.org/wiki/Unix_time)). Given the date 23 June +1990 at 11:45AM this returns the corresponding time. -} +toTime : Date -> Time +toTime = Native.Date.toTime + +{-| Take a UNIX time and convert it to a `Date`. -} +fromTime : Time -> Date +fromTime = Native.Date.fromTime + +{-| Extract the year of a given date. Given the date 23 June 1990 at 11:45AM +this returns the integer `1990`. -} +year : Date -> Int +year = Native.Date.year + +{-| Extract the month of a given date. Given the date 23 June 1990 at 11:45AM +this returns the Month `Jun` as defined below. -} +month : Date -> Month +month = Native.Date.month + +{-| Extract the day of a given date. Given the date 23 June 1990 at 11:45AM +this returns the integer `23`. -} +day : Date -> Int +day = Native.Date.day + +{-| Extract the day of the week for a given date. Given the date 23 June +1990 at 11:45AM this returns the Day `Thu` as defined below. -} +dayOfWeek : Date -> Day +dayOfWeek = Native.Date.dayOfWeek + +{-| Extract the hour of a given date. Given the date 23 June 1990 at 11:45AM +this returns the integer `11`. -} +hour : Date -> Int +hour = Native.Date.hour + +{-| Extract the minute of a given date. Given the date 23 June 1990 at 11:45AM +this returns the integer `45`. -} +minute : Date -> Int +minute = Native.Date.minute + +{-| Extract the second of a given date. Given the date 23 June 1990 at 11:45AM +this returns the integer `0`. -} +second : Date -> Int +second = Native.Date.second
+ src/Debug.elm view
@@ -0,0 +1,88 @@+module Debug where +{-| This library is for investigating bugs or performance problems. It should +*not* be used in production code. + +# Console Debugging +@docs log, crash + +# Time-Travel Debugging +@docs watch, watchSummary, trace +-} + +import Graphics.Collage (Form) +import Native.Debug + + +{-| Log a tagged value on the developer console, and then return the value. + + 1 + log "number" 1 -- equals 2, logs "number: 1" + length (log "start" []) -- equals 0, logs "start: []" + +Notice that `log` is not a pure function! It should *only* be used for +investigating bugs or performance problems. +-} +log : String -> a -> a +log = Native.Debug.log + +{-| Crash the program with an error message. This is an uncatchable error, +intended for code that is soon-to-be-implemented. For example, if you are +working with a large ADT and have partially completed a case expression, it may +make sense to do this: + + data Entity = Ship | Fish | Captain | Seagull + + drawEntity entity = + case entity of + Ship -> ... + Fish -> ... + _ -> Debug.crash ("drawEntity not implemented for " ++ show entity ++ " yet!") + +Note that incomplete pattern matches are *very* bad practice! They are one of +the very few ways to crash an Elm program, and they are completely avoidable. +Production code should not have incomplete pattern matches! + +**Use this if** you want to do some testing while you are partway through +writing a function. + +**Do not use this if** you want to do some typical try-catch exception handling. +Use the `Maybe` or `Either` libraries instead. +-} +crash : String -> a +crash = Native.Debug.crash + +{-| Watch a particular value in the debugger. Say we want to know the value of +a variable called `velocity` because it may not be updated correctly. Adding +`Debug.watch` allows us to name the value and show it with the debugger. + + Debug.watch "velocity" velocity == velocity + +Notice that the result of evaluating this expression is exactly the same as +not having the expression at all. That means it's easy to add to any value. +-} +watch : String -> a -> a +watch = Native.Debug.watch + +{-| Watch a summary of a particular value in the debugger. This function is +pretty much the same as `watch` but it lets you specify a way to summarize +the value you are interested in. For example, maybe you only want to see part +of a record: + + Debug.watchSummary "velocity" .velocity object + +This is the same as just writing `object`, but it creates a watch that *only* +looks at the value of `object.velocity`. You can also show summary statistics +like length of a list: + + Debug.watchSummary "Number of clicks" length clicks + +Again, this evaluates to `clicks` but we get to see how long that list is in +the debugger. +-} +watchSummary : String -> (a -> b) -> a -> a +watchSummary = Native.Debug.watchSummary + +{-| Trace all past positions of a `Form` in the debugger. Add this to a `Form` +and you will see a line tracing its entire history. +-} +trace : String -> Form -> Form +trace = Native.Debug.tracePath
+ src/Dict.elm view
@@ -0,0 +1,484 @@+module Dict + ( Dict + , empty, singleton, insert, update + , get, remove, member + , filter + , partition + , foldl, foldr, map + , union, intersect, diff + , keys, values + , toList, fromList + ) where + +{-| A dictionary mapping unique keys to values. The keys can be any comparable +type. This includes `Int`, `Float`, `Time`, `Char`, `String`, and tuples or +lists of comparable types. + +Insert, remove, and query operations all take *O(log n)* time. + +# Build +@docs empty, singleton, insert, update, remove + +# Query +@docs member, get + +# Combine +@docs union, intersect, diff + +# Lists +@docs keys, values, toList, fromList + +# Transform +@docs map, foldl, foldr, filter, partition + +-} + + +import Basics (..) +import Maybe (..) +import List +import List (..) +import Native.Debug +import String + + +-- BBlack and NBlack should only be used during the deletion +-- algorithm. Any other occurrence is a bug and should fail an assert. +type NColor + = Red + | Black + | BBlack -- Double Black, counts as 2 blacks for the invariant + | NBlack -- Negative Black, counts as -1 blacks for the invariant + + +showNColor : NColor -> String +showNColor c = + case c of + Red -> "Red" + Black -> "Black" + BBlack -> "BBlack" + NBlack -> "NBlack" + + +type LeafColor + = LBlack + | LBBlack -- Double Black, counts as 2 + + +showLColor : LeafColor -> String +showLColor color = + case color of + LBlack -> "LBlack" + LBBlack -> "LBBlack" + + +type Dict k v + = RBNode NColor k v (Dict k v) (Dict k v) + | RBEmpty LeafColor + + +{-| Create an empty dictionary. -} +empty : Dict comparable v +empty = RBEmpty LBlack + + +min : Dict k v -> (k,v) +min dict = + case dict of + RBNode _ key value (RBEmpty LBlack) _ -> + (key, value) + + RBNode _ _ _ left _ -> + min left + + RBEmpty LBlack -> + Native.Debug.crash "(min Empty) is not defined" + + +max : Dict k v -> (k, v) +max dict = + case dict of + RBNode _ key value _ (RBEmpty _) -> + (key, value) + + RBNode _ _ _ _ right -> + max right + + RBEmpty _ -> + Native.Debug.crash "(max Empty) is not defined" + + +{-| Get the value associated with a key. If the key is not found, return +`Nothing`. This is useful when you are not sure if a key will be in the +dictionary. + + animals = fromList [ ("Tom", Cat), ("Jerry", Mouse) ] + + get "Tom" animals == Just Cat + get "Mouse" animals == Just Mouse + get "Spike" animals == Nothing + +The `(?)` operator from the `Maybe` library makes it easy to give a default +value. +-} +get : comparable -> Dict comparable v -> Maybe v +get targetKey dict = + case dict of + RBEmpty LBlack -> + Nothing + + RBNode _ key value left right -> + case compare targetKey key of + LT -> get targetKey left + EQ -> Just value + GT -> get targetKey right + + +{-| Determine if a key is in a dictionary. -} +member : comparable -> Dict comparable v -> Bool +member key dict = + case get key dict of + Just _ -> True + Nothing -> False + + +ensureBlackRoot : Dict k v -> Dict k v +ensureBlackRoot dict = + case dict of + RBNode Red key value left right -> + RBNode Black key value left right + + RBNode Black _ _ _ _ -> + dict + + RBEmpty LBlack -> + dict + + +{-| Insert a key-value pair into a dictionary. Replaces value when there is +a collision. -} +insert : comparable -> v -> Dict comparable v -> Dict comparable v +insert key value dict = + update key (always (Just value)) dict + + +{-| Remove a key-value pair from a dictionary. If the key is not found, +no changes are made. -} +remove : comparable -> Dict comparable v -> Dict comparable v +remove key dict = + update key (always Nothing) dict + + +type Flag = Insert | Remove | Same + +showFlag : Flag -> String +showFlag f = case f of + Insert -> "Insert" + Remove -> "Remove" + Same -> "Same" + + +{-| Update the value of a dictionary for a specific key with a given function. -} +update : comparable -> (Maybe v -> Maybe v) -> Dict comparable v -> Dict comparable v +update k alter dict = + let up dict = + case dict of + RBEmpty LBlack -> + case alter Nothing of + Nothing -> (Same, empty) + Just v -> (Insert, RBNode Red k v empty empty) + + RBNode clr key value left right -> + case compare k key of + EQ -> + case alter (Just value) of + Nothing -> (Remove, rem clr left right) + Just newValue -> + (Same, RBNode clr key newValue left right) + + LT -> + let (flag, newLeft) = up left in + case flag of + Same -> (Same, RBNode clr key value newLeft right) + Insert -> (Insert, balance clr key value newLeft right) + Remove -> (Remove, bubble clr key value newLeft right) + + GT -> + let (flag, newRight) = up right in + case flag of + Same -> (Same, RBNode clr key value left newRight) + Insert -> (Insert, balance clr key value left newRight) + Remove -> (Remove, bubble clr key value left newRight) + + (flag, updatedDict) = up dict + in + case flag of + Same -> updatedDict + Insert -> ensureBlackRoot updatedDict + Remove -> blacken updatedDict + + +{-| Create a dictionary with one key-value pair. -} +singleton : comparable -> v -> Dict comparable v +singleton key value = + insert key value (RBEmpty LBlack) + + +isBBlack : Dict k v -> Bool +isBBlack dict = + case dict of + RBNode BBlack _ _ _ _ -> True + RBEmpty LBBlack -> True + _ -> False + + +moreBlack : NColor -> NColor +moreBlack color = + case color of + Black -> BBlack + Red -> Black + NBlack -> Red + BBlack -> Native.Debug.crash "Can't make a double black node more black!" + + +lessBlack : NColor -> NColor +lessBlack color = + case color of + BBlack -> Black + Black -> Red + Red -> NBlack + NBlack -> Native.Debug.crash "Can't make a negative black node less black!" + + +lessBlackTree : Dict k v -> Dict k v +lessBlackTree dict = + case dict of + RBNode c k v l r -> RBNode (lessBlack c) k v l r + RBEmpty LBBlack -> RBEmpty LBlack + + +reportRemBug : String -> NColor -> String -> String -> a +reportRemBug msg c lgot rgot = + Native.Debug.crash <| + String.concat + [ "Internal red-black tree invariant violated, expected " + , msg, " and got ", showNColor c, "/", lgot, "/", rgot + , "\nPlease report this bug to <https://github.com/elm-lang/Elm/issues>" + ] + + +-- Remove the top node from the tree, may leave behind BBlacks +rem : NColor -> Dict k v -> Dict k v -> Dict k v +rem c l r = + case (l, r) of + (RBEmpty _, RBEmpty _) -> + case c of + Red -> RBEmpty LBlack + Black -> RBEmpty LBBlack + + (RBEmpty cl, RBNode cr k' v' l' r') -> + case (c, cl, cr) of + (Black, LBlack, Red) -> + RBNode Black k' v' l' r' + + _ -> + reportRemBug "Black/LBlack/Red" c (showLColor cl) (showNColor cr) + + (RBNode cl k' v' l' r', RBEmpty cr) -> + case (c, cl, cr) of + (Black, Red, LBlack) -> + RBNode Black k' v' l' r' + + _ -> + reportRemBug "Black/Red/LBlack" c (showNColor cl) (showLColor cr) + + -- l and r are both RBNodes + (RBNode cl kl vl ll rl, RBNode cr kr vr lr rr) -> + let l = RBNode cl kl vl ll rl + r = RBNode cr kr vr lr rr + (k, v) = max l + l' = remove_max cl kl vl ll rl + in + bubble c k v l' r + + +-- Kills a BBlack or moves it upward, may leave behind NBlack +bubble : NColor -> k -> v -> Dict k v -> Dict k v -> Dict k v +bubble c k v l r = + if isBBlack l || isBBlack r + then balance (moreBlack c) k v (lessBlackTree l) (lessBlackTree r) + else RBNode c k v l r + + +-- Removes rightmost node, may leave root as BBlack +remove_max : NColor -> k -> v -> Dict k v -> Dict k v -> Dict k v +remove_max c k v l r = + case r of + RBEmpty _ -> + rem c l r + + RBNode cr kr vr lr rr -> + bubble c k v l (remove_max cr kr vr lr rr) + + +-- generalized tree balancing act +balance : NColor -> k -> v -> Dict k v -> Dict k v -> Dict k v +balance c k v l r = + balance_node (RBNode c k v l r) + + +blackish : Dict k v -> Bool +blackish t = + case t of + RBNode c _ _ _ _ -> c == Black || c == BBlack + RBEmpty _ -> True + + +balance_node : Dict k v -> Dict k v +balance_node t = + let assemble col xk xv yk yv zk zv a b c d = + RBNode (lessBlack col) yk yv (RBNode Black xk xv a b) (RBNode Black zk zv c d) + in + if blackish t + then case t of + RBNode col zk zv (RBNode Red yk yv (RBNode Red xk xv a b) c) d -> + assemble col xk xv yk yv zk zv a b c d + RBNode col zk zv (RBNode Red xk xv a (RBNode Red yk yv b c)) d -> + assemble col xk xv yk yv zk zv a b c d + RBNode col xk xv a (RBNode Red zk zv (RBNode Red yk yv b c) d) -> + assemble col xk xv yk yv zk zv a b c d + RBNode col xk xv a (RBNode Red yk yv b (RBNode Red zk zv c d)) -> + assemble col xk xv yk yv zk zv a b c d + + RBNode BBlack xk xv a (RBNode NBlack zk zv (RBNode Black yk yv b c) d) -> + case d of + (RBNode Black _ _ _ _) -> + RBNode Black yk yv (RBNode Black xk xv a b) (balance Black zk zv c (redden d)) + _ -> t + + RBNode BBlack zk zv (RBNode NBlack xk xv a (RBNode Black yk yv b c)) d -> + case a of + (RBNode Black _ _ _ _) -> + RBNode Black yk yv (balance Black xk xv (redden a) b) (RBNode Black zk zv c d) + _ -> t + _ -> t + else t + + +-- make the top node black +blacken : Dict k v -> Dict k v +blacken t = + case t of + RBEmpty _ -> RBEmpty LBlack + RBNode _ k v l r -> RBNode Black k v l r + + +-- make the top node red +redden : Dict k v -> Dict k v +redden t = + case t of + RBEmpty _ -> Native.Debug.crash "can't make a Leaf red" + RBNode _ k v l r -> RBNode Red k v l r + + +{-| Apply a function to all values in a dictionary. -} +map : (comparable -> a -> b) -> Dict comparable a -> Dict comparable b +map f dict = + case dict of + RBEmpty LBlack -> + RBEmpty LBlack + + RBNode clr key value left right -> + RBNode clr key (f key value) (map f left) (map f right) + + +{-| Fold over the key-value pairs in a dictionary, in order from lowest +key to highest key. -} +foldl : (comparable -> v -> b -> b) -> b -> Dict comparable v -> b +foldl f acc dict = + case dict of + RBEmpty LBlack -> acc + + RBNode _ key value left right -> + foldl f (f key value (foldl f acc left)) right + + +{-| Fold over the key-value pairs in a dictionary, in order from highest +key to lowest key. -} +foldr : (comparable -> v -> b -> b) -> b -> Dict comparable v -> b +foldr f acc t = + case t of + RBEmpty LBlack -> acc + + RBNode _ key value left right -> + foldr f (f key value (foldr f acc right)) left + + +{-| Combine two dictionaries. If there is a collision, preference is given +to the first dictionary. -} +union : Dict comparable v -> Dict comparable v -> Dict comparable v +union t1 t2 = + foldl insert t2 t1 + + +{-| Keep a key-value pair when its key appears in the second dictionary. +Preference is given to values in the first dictionary. -} +intersect : Dict comparable v -> Dict comparable v -> Dict comparable v +intersect t1 t2 = + filter (\k _ -> k `member` t2) t1 + + +{-| Keep a key-value pair when its key does not appear in the second dictionary. +Preference is given to the first dictionary. -} +diff : Dict comparable v -> Dict comparable v -> Dict comparable v +diff t1 t2 = + foldl (\k v t -> remove k t) t1 t2 + + +{-| Get all of the keys in a dictionary. -} +keys : Dict comparable v -> List comparable +keys dict = + foldr (\key value keyList -> key :: keyList) [] dict + + +{-| Get all of the values in a dictionary. -} +values : Dict comparable v -> List v +values dict = + foldr (\key value valueList -> value :: valueList) [] dict + + +{-| Convert a dictionary into an association list of key-value pairs. -} +toList : Dict comparable v -> List (comparable,v) +toList dict = + foldr (\key value list -> (key,value) :: list) [] dict + + +{-| Convert an association list into a dictionary. -} +fromList : List (comparable,v) -> Dict comparable v +fromList assocs = + List.foldl (\(key,value) dict -> insert key value dict) empty assocs + + +{-| Keep a key-value pair when it satisfies a predicate. -} +filter : (comparable -> v -> Bool) -> Dict comparable v -> Dict comparable v +filter predicate dictionary = + let add key value dict = + if predicate key value + then insert key value dict + else dict + in + foldl add empty dictionary + + +{-| Partition a dictionary according to a predicate. The first dictionary +contains all key-value pairs which satisfy the predicate, and the second +contains the rest. +-} +partition : (comparable -> v -> Bool) -> Dict comparable v -> (Dict comparable v, Dict comparable v) +partition predicate dict = + let add key value (t1, t2) = + if predicate key value + then (insert key value t1, t2) + else (t1, insert key value t2) + in + foldl add (empty, empty) dict
+ src/Graphics/Collage.elm view
@@ -0,0 +1,265 @@+module Graphics.Collage where + +{-| The collage API is for freeform graphics. You can move, rotate, scale, etc. +all sorts of forms including lines, shapes, images, and elements. + +# Unstructured Graphics +@docs collage + +# Creating Forms +@docs toForm, filled, textured, gradient, outlined, traced + +# Transforming Forms +@docs move, moveX, moveY, scale, rotate, alpha + +# Grouping Forms +Grouping forms makes it easier to write modular graphics code. You can create +a form that is a composite of many subforms. From there it is easy to transform +it as a single unit. + +@docs group, groupTransform + +# Shapes +@docs rect, oval, square, circle, ngon, polygon + +# Paths +@docs segment, path + +# Line Styles +@docs solid, dashed, dotted, LineStyle, LineCap, LineJoin, defaultLine + +-} + +import Basics (..) +import List +import Transform2D (Transform2D) +import Transform2D as T +import Native.Graphics.Collage +import Graphics.Element (Element) +import Color (Color, black, Gradient) + + +type alias Form = + { theta : Float + , scale : Float + , x : Float + , y : Float + , alpha : Float + , form : BasicForm + } + +type FillStyle + = Solid Color + | Texture String + | Grad Gradient + +{-| The shape of the ends of a line. -} +type LineCap = Flat | Round | Padded + +{-| The shape of the “joints” of a line, where each line segment +meets. `Sharp` takes an argument to limit the length of the joint. This +defaults to 10. +-} +type LineJoin = Smooth | Sharp Float | Clipped + +{-| All of the attributes of a line style. This lets you build up a line style +however you want. You can also update existing line styles with record updates. +-} +type alias LineStyle = + { color : Color + , width : Float + , cap : LineCap + , join : LineJoin + , dashing : List Int + , dashOffset : Int + } + +{-| The default line style, which is solid black with flat caps and sharp joints. +You can use record updates to build the line style you +want. For example, to make a thicker line, you could say: + + { defaultLine | width <- 10 } +-} +defaultLine : LineStyle +defaultLine = + { color = black + , width = 1 + , cap = Flat + , join = Sharp 10 + , dashing = [] + , dashOffset = 0 + } + +{-| Create a solid line style with a given color. -} +solid : Color -> LineStyle +solid clr = { defaultLine | color <- clr } + +{-| Create a dashed line style with a given color. Dashing equals `[8,4]`. -} +dashed : Color -> LineStyle +dashed clr = { defaultLine | color <- clr, dashing <- [8,4] } + +{-| Create a dotted line style with a given color. Dashing equals `[3,3]`. -} +dotted : Color -> LineStyle +dotted clr = { defaultLine | color <- clr, dashing <- [3,3] } + +type BasicForm + = FPath LineStyle Path + | FShape ShapeStyle Shape + | FImage Int Int (Int,Int) String + | FElement Element + | FGroup Transform2D (List Form) + +type ShapeStyle + = Line LineStyle + | Fill FillStyle + + +form : BasicForm -> Form +form f = { theta=0, scale=1, x=0, y=0, alpha=1, form=f } + +fill style shape = form (FShape (Fill style) shape) + +{-| Create a filled in shape. -} +filled : Color -> Shape -> Form +filled color shape = fill (Solid color) shape + +{-| Create a textured shape. The texture is described by some url and is +tiled to fill the entire shape. +-} +textured : String -> Shape -> Form +textured src shape = fill (Texture src) shape + +{-| Fill a shape with a [gradient](Color#linear). -} +gradient : Gradient -> Shape -> Form +gradient grad shape = fill (Grad grad) shape + +{-| Outline a shape with a given line style. -} +outlined : LineStyle -> Shape -> Form +outlined style shape = form (FShape (Line style) shape) + +{-| Trace a path with a given line style. -} +traced : LineStyle -> Path -> Form +traced style path = form (FPath style path) + +{-| Create a sprite from a sprite sheet. It cuts out a rectangle +at a given position. +-} +sprite : Int -> Int -> (Int,Int) -> String -> Form +sprite w h pos src = form (FImage w h pos src) + +{-| Turn any `Element` into a `Form`. This lets you use text, gifs, and video +in your collage. This means you can move, rotate, and scale +an `Element` however you want. +-} +toForm : Element -> Form +toForm e = form (FElement e) + +{-| Flatten many forms into a single `Form`. This lets you move and rotate them +as a single unit, making it possible to build small, modular components. +-} +group : List Form -> Form +group fs = form (FGroup T.identity fs) + +{-| Flatten many forms into a single `Form` and then apply a matrix +transformation. +-} +groupTransform : Transform2D -> List Form -> Form +groupTransform matrix fs = form (FGroup matrix fs) + +{-| Move a form by the given amount. This is a relative translation so +`(move (10,10) form)` would move `form` ten pixels up and ten pixels to the +right. +-} +move : (Float,Float) -> Form -> Form +move (x,y) f = { f | x <- f.x + x, y <- f.y + y } + +{-| Move a shape in the x direction. This is relative so `(moveX 10 form)` moves +`form` 10 pixels to the right. +-} +moveX : Float -> Form -> Form +moveX x f = { f | x <- f.x + x } + +{-| Move a shape in the y direction. This is relative so `(moveY 10 form)` moves +`form` upwards by 10 pixels. +-} +moveY : Float -> Form -> Form +moveY y f = { f | y <- f.y + y } + +{-| Scale a form by a given factor. Scaling by 2 doubles the size. +-} +scale : Float -> Form -> Form +scale s f = { f | scale <- f.scale * s } + +{-| Rotate a form by a given angle. Rotate takes standard Elm angles (radians) +and turns things counterclockwise. So to turn `form` 30° to the left +you would say, `(rotate (degrees 30) form)`. +-} +rotate : Float -> Form -> Form +rotate t f = { f | theta <- f.theta + t } + +{-| Set the alpha of a `Form`. The default is 1, and 0 is totally transparent. -} +alpha : Float -> Form -> Form +alpha a f = { f | alpha <- a } + +{-| A collage is a collection of 2D forms. There are no strict positioning +relationships between forms, so you are free to do all kinds of 2D graphics. +-} +collage : Int -> Int -> List Form -> Element +collage = Native.Graphics.Collage.collage + + +type alias Path = List (Float,Float) + +{-| Create a path that follows a sequence of points. -} +path : List (Float,Float) -> Path +path ps = ps + +{-| Create a path along a given line segment. -} +segment : (Float,Float) -> (Float,Float) -> Path +segment p1 p2 = [p1,p2] + +type alias Shape = List (Float,Float) + +{-| Create an arbitrary polygon by specifying its corners in order. +`polygon` will automatically close all shapes, so the given list +of points does not need to start and end with the same position. +-} +polygon : List (Float,Float) -> Shape +polygon points = points + +{-| A rectangle with a given width and height. -} +rect : Float -> Float -> Shape +rect w h = let hw = w/2 + hh = h/2 + in [ (0-hw,0-hh), (0-hw,hh), (hw,hh), (hw,0-hh) ] + +{-| A square with a given edge length. -} +square : Float -> Shape +square n = rect n n + +{-| An oval with a given width and height. -} +oval : Float -> Float -> Shape +oval w h = + let n = 50 + t = 2 * pi / n + hw = w/2 + hh = h/2 + f i = (hw * cos (t*i), hh * sin (t*i)) + in List.map f [0..n-1] + +{-| A circle with a given radius. -} +circle : Float -> Shape +circle r = oval (2*r) (2*r) + +{-| A regular polygon with N sides. The first argument specifies the number +of sides and the second is the radius. So to create a pentagon with radius +30 you would say: + + ngon 5 30 +-} +ngon : Int -> Float -> Shape +ngon n r = + let m = toFloat n + t = 2 * pi / m + f i = ( r * cos (t*i), r * sin (t*i) ) + in List.map f [0..m-1]
+ src/Graphics/Element.elm view
@@ -0,0 +1,394 @@+module Graphics.Element where +{-| Graphical elements that snap together to build complex widgets and layouts. +Each Element is a rectangle with a known width and height, making them easy to +combine and position. + +# Images +@docs image, fittedImage, croppedImage, tiledImage + +# Styling +@docs width, height, size, color, opacity, link, tag + +# Inspection +@docs widthOf, heightOf, sizeOf + +# Layout +@docs flow, up, down, left, right, inward, outward + +## Layout Aliases +There are also some convenience functions for working +with `flow` in specific cases: + +@docs layers, above, below, beside + +# Positioning +@docs empty, spacer, container + +## Specific Positions + +To create a `Position` you can use any of the built-in positions +which cover nine common positions. +@docs middle, midTop, midBottom, midLeft, midRight, topLeft, topRight, + bottomLeft, bottomRight + +If you need more precision, you can create custom positions. + +@docs absolute, relative, middleAt, midTopAt, midBottomAt, midLeftAt, + midRightAt, topLeftAt, topRightAt, bottomLeftAt, bottomRightAt +-} + +import Basics (..) +import Native.Graphics.Element +import List as List +import Color (..) +import Maybe ( Maybe(..) ) + + +type alias Properties = { + id : Int, + width : Int, + height : Int, + opacity : Float, + color : Maybe Color, + href : String, + tag : String, + hover : (), + click : () + } + + +type alias Element = + { props : Properties + , element : ElementPrim + } + + +{-| An Element that takes up no space. Good for things that appear conditionally: + + flow down [ img1, if showMore then img2 else empty ] +-} +empty : Element +empty = spacer 0 0 + + +{-| Get the width of an Element -} +widthOf : Element -> Int +widthOf e = + e.props.width + + +{-| Get the height of an Element -} +heightOf : Element -> Int +heightOf e = + e.props.height + + +{-| Get the width and height of an Element -} +sizeOf : Element -> (Int,Int) +sizeOf e = + (e.props.width, e.props.height) + + +{-| Create an `Element` with a given width. -} +width : Int -> Element -> Element +width nw e = + let p = e.props + + props = + case e.element of + Image _ w h _ -> + { p | + height <- round (toFloat h / toFloat w * toFloat nw) + } + + RawHtml -> + { p | + height <- snd (Native.Graphics.Element.htmlHeight nw e.element) + } + + _ -> p + in + { element = e.element + , props = { props | width <- nw } + } + + +{-| Create an `Element` with a given height. -} +height : Int -> Element -> Element +height nh e = + let p = e.props + props = + case e.element of + Image _ w h _ -> + { p | + width <- round (toFloat w / toFloat h * toFloat nh) + } + _ -> p + in + { element = e.element + , props = { p | height <- nh } + } + + +{-| Create an `Element` with a new width and height. -} +size : Int -> Int -> Element -> Element +size w h e = + height h (width w e) + + +{-| Create an `Element` with a given opacity. Opacity is a number between 0 and 1 +where 0 means totally clear. +-} +opacity : Float -> Element -> Element +opacity o e = + let p = e.props + in + { element = e.element + , props = { p | opacity <- o } + } + + +{-| Create an `Element` with a given background color. -} +color : Color -> Element -> Element +color c e = let p = e.props in + { element = e.element + , props = { p | color <- Just c} + } + + +{-| Create an `Element` with a tag. This lets you link directly to it. +The element `(tag "all-about-badgers" thirdParagraph)` can be reached +with a link like this: `/facts-about-animals.elm#all-about-badgers` +-} +tag : String -> Element -> Element +tag name e = + let p = e.props + in + { element = e.element + , props = { p | tag <- name } + } + + +{-| Create an `Element` that is a hyper-link. -} +link : String -> Element -> Element +link href e = + let p = e.props + in + { element = e.element + , props = { p | href <- href } + } + + +newElement w h e = + { props = Properties (Native.Graphics.Element.guid ()) w h 1 Nothing "" "" () () + , element = e + } + + +type ElementPrim + = Image ImageStyle Int Int String + | Container Position Element + | Flow Direction (List Element) + | Spacer + | RawHtml + | Custom -- for custom Elements implemented in JS, see collage for example + +type ImageStyle = Plain | Fitted | Cropped (Int,Int) | Tiled + + +{-| Create an image given a width, height, and image source. -} +image : Int -> Int -> String -> Element +image w h src = + newElement w h (Image Plain w h src) + + +{-| Create a fitted image given a width, height, and image source. +This will crop the picture to best fill the given dimensions. +-} +fittedImage : Int -> Int -> String -> Element +fittedImage w h src = + newElement w h (Image Fitted w h src) + + +{-| Create a cropped image. Take a rectangle out of the picture starting +at the given top left coordinate. If you have a 140-by-140 image, +the following will cut a 100-by-100 square out of the middle of it. + + croppedImage (20,20) 100 100 "yogi.jpg" +-} +croppedImage : (Int,Int) -> Int -> Int -> String -> Element +croppedImage pos w h src = + newElement w h (Image (Cropped pos) w h src) + +tiledImage : Int -> Int -> String -> Element +tiledImage w h src = + newElement w h (Image Tiled w h src) + + +type Three = P | Z | N + +type Pos = Absolute Int | Relative Float + +type alias Position = + { horizontal : Three + , vertical : Three + , x : Pos + , y : Pos + } + + +{-| Put an element in a container. This lets you position the element really +easily, and there are tons of ways to set the `Position`. +To center `element` exactly in a 300-by-300 square you would say: + + container 300 300 middle element + +By setting the color of the container, you can create borders. +-} +container : Int -> Int -> Position -> Element -> Element +container w h pos e = + newElement w h (Container pos e) + + +{-| Create an empty box. This is useful for getting your spacing right and +for making borders. +-} +spacer : Int -> Int -> Element +spacer w h = + newElement w h Spacer + + +type Direction = DUp | DDown | DLeft | DRight | DIn | DOut + + +{-| Have a list of elements flow in a particular direction. +The `Direction` starts from the first element in the list. + + flow right [a,b,c] + + +---+---+---+ + | a | b | c | + +---+---+---+ +-} +flow : Direction -> List Element -> Element +flow dir es = + let ws = List.map widthOf es + hs = List.map heightOf es + newFlow w h = newElement w h (Flow dir es) + in + if es == [] then empty else + case dir of + DUp -> newFlow (List.maximum ws) (List.sum hs) + DDown -> newFlow (List.maximum ws) (List.sum hs) + DLeft -> newFlow (List.sum ws) (List.maximum hs) + DRight -> newFlow (List.sum ws) (List.maximum hs) + DIn -> newFlow (List.maximum ws) (List.maximum hs) + DOut -> newFlow (List.maximum ws) (List.maximum hs) + + +{-| Stack elements vertically. +To put `a` above `b` you would say: ``a `above` b`` +-} +above : Element -> Element -> Element +above hi lo = + newElement + (max (widthOf hi) (widthOf lo)) + (heightOf hi + heightOf lo) + (Flow DDown [hi,lo]) + + +{-| Stack elements vertically. +To put `a` below `b` you would say: ``a `below` b`` +-} +below : Element -> Element -> Element +below lo hi = + newElement + (max (widthOf hi) (widthOf lo)) + (heightOf hi + heightOf lo) + (Flow DDown [hi,lo]) + + +{-| Put elements beside each other horizontally. +To put `a` beside `b` you would say: ``a `beside` b`` +-} +beside : Element -> Element -> Element +beside lft rht = + newElement + (widthOf lft + widthOf rht) + (max (heightOf lft) (heightOf rht)) + (Flow right [lft,rht]) + + +{-| Layer elements on top of each other, starting from the bottom: +`layers == flow outward` +-} +layers : List Element -> Element +layers es = + let ws = List.map widthOf es + hs = List.map heightOf es + in + newElement (List.maximum ws) (List.maximum hs) (Flow DOut es) + + +-- Repetitive things -- + +absolute : Int -> Pos +absolute = Absolute +relative : Float -> Pos +relative = Relative + +middle : Position +middle = { horizontal=Z, vertical=Z, x=Relative 0.5, y=Relative 0.5 } +topLeft : Position +topLeft = { horizontal=N, vertical=P, x=Absolute 0, y=Absolute 0 } +topRight : Position +topRight = { topLeft | horizontal <- P } +bottomLeft : Position +bottomLeft = { topLeft | vertical <- N } +bottomRight : Position +bottomRight = { bottomLeft | horizontal <- P } +midLeft : Position +midLeft = { middle | horizontal <- N, x <- Absolute 0 } +midRight : Position +midRight = { midLeft | horizontal <- P } +midTop : Position +midTop = { middle | vertical <- P, y <- Absolute 0 } +midBottom : Position +midBottom = { midTop | vertical <- N } + +middleAt : Pos -> Pos -> Position +middleAt x y = { horizontal = Z, vertical = Z, x = x, y = y } +topLeftAt : Pos -> Pos -> Position +topLeftAt x y = { horizontal = N, vertical = P, x = x, y = y } +topRightAt : Pos -> Pos -> Position +topRightAt x y = { horizontal = P, vertical = P, x = x, y = y } +bottomLeftAt : Pos -> Pos -> Position +bottomLeftAt x y = { horizontal = N, vertical = N, x = x, y = y } +bottomRightAt : Pos -> Pos -> Position +bottomRightAt x y = { horizontal = P, vertical = N, x = x, y = y } +midLeftAt : Pos -> Pos -> Position +midLeftAt x y = { horizontal = N, vertical = Z, x = x, y = y } +midRightAt : Pos -> Pos -> Position +midRightAt x y = { horizontal = P, vertical = Z, x = x, y = y } +midTopAt : Pos -> Pos -> Position +midTopAt x y = { horizontal = Z, vertical = P, x = x, y = y } +midBottomAt : Pos -> Pos -> Position +midBottomAt x y = { horizontal = Z, vertical = N, x = x, y = y } + +up : Direction +up = DUp + +down : Direction +down = DDown + +left : Direction +left = DLeft + +right : Direction +right = DRight + +inward : Direction +inward = DIn + +outward : Direction +outward = DOut
+ src/Graphics/Input.elm view
@@ -0,0 +1,152 @@+module Graphics.Input where +{-| This module is for creating input widgets such as buttons and text fields. +All functions in this library report to a [`Signal.Input`](Signal#customSignal). + +# Basic Input Elements + +To learn about text fields, see the +[`Graphics.Input.Field`](Graphics-Input-Field) library. + +@docs button, customButton, checkbox, dropDown + +# Clicks and Hovers +@docs clickable, hoverable + +-} + +import Signal +import Graphics.Element (Element) +import Native.Graphics.Input + + +{-| Create a standard button. The following example begins making a basic +calculator: + + type Keys = Number Int | Plus | Minus | Clear + + keys : Signal.Channel Keys + keys = Signal.channel Clear + + calculator : Element + calculator = + flow right + [ button (Signal.send keys (Number 1)) "1" + , button (Signal.send keys (Number 2)) "2" + , button (Signal.send keys Plus ) "+" + ] + +If the user presses the "+" button, `keys.signal` will update to `Plus`. If the +users presses "2", `keys.signal` will update to `(Number 2)`. +-} +button : Signal.Message -> String -> Element +button = + Native.Graphics.Input.button + + +{-| Same as `button` but lets you customize buttons to look however you want. + + click : Signal.Channel + click = Signal.channel () + + prettyButton : Element + prettyButton = + customButton (Signal.send click ()) + (image 100 40 "/button_up.jpg") + (image 100 40 "/button_hover.jpg") + (image 100 40 "/button_down.jpg") +-} +customButton : Signal.Message -> Element -> Element -> Element -> Element +customButton = + Native.Graphics.Input.customButton + + +{-| Create a checkbox. The following example creates three synced checkboxes: + + check : Signal.Channel Bool + check = Signal.channel False + + boxes : Bool -> Element + boxes checked = + let box = container 40 40 middle (checkbox (Signal.send check) checked) + in + flow right [ box, box, box ] + + main : Signal Element + main = boxes <~ Signal.subscribe check +-} +checkbox : (Bool -> Signal.Message) -> Bool -> Element +checkbox = + Native.Graphics.Input.checkbox + + +{-| Create a drop-down menu. The following drop-down lets you choose your +favorite British sport: + + type Sport = Football | Cricket | Snooker + + sport : Signal.Channel (Maybe Sport) + sport = Signal.channel Nothing + + sportDropDown : Element + sportDropDown = + dropDown (Signal.send sport) + [ ("" , Nothing) + , ("Football", Just Football) + , ("Cricket" , Just Cricket) + , ("Snooker" , Just Snooker) + ] + +If the user selects "Football" from the drop down menue, `Signal.subscribe sport` +will update to `Just Football`. +-} +dropDown : (a -> Signal.Message) -> List (String, a) -> Element +dropDown = + Native.Graphics.Input.dropDown + + +{-| Detect mouse hovers over a specific `Element`. In the following example, +we will create a hoverable picture called `cat`. + + hover : Signal.Channel Bool + hover = Signal.channel False + + cat : Element + cat = + image 30 30 "/cat.jpg" + |> hoverable (Signal.send hover) + +When the mouse hovers above the `cat` element, `hover.signal` will become +`True`. When the mouse leaves it, `hover.signal` will become `False`. +-} +hoverable : (Bool -> Signal.Message) -> Element -> Element +hoverable = + Native.Graphics.Input.hoverable + + +{-| Detect mouse clicks on a specific `Element`. In the following example, +we will create a clickable picture called `cat`. + + type Picture = Cat | Hat + + picture : Signal.Channel Picture + picture = Signal.channel Cat + + cat : Element + cat = + image 30 30 "/cat.jpg" + |> clickable (Signal.send picture Cat) + + hat : Element + hat = + image 30 30 "/hat.jpg" + |> clickable (Signal.send picture Hat) + +When the user clicks on the `cat` element, `picture.signal` receives +an update containing the value `Cat`. When the user clicks on the `hat` element, +`picture.signal` receives an update containing the value `Hat`. This lets you +distinguish which element was clicked. In a more complex example, they could be +distinguished with IDs or more complex data structures. +-} +clickable : Signal.Message -> Element -> Element +clickable = + Native.Graphics.Input.clickable
+ src/Graphics/Input/Field.elm view
@@ -0,0 +1,195 @@+module Graphics.Input.Field where +{-| This library provides an API for creating and updating text fields. +Text fields use exactly the same approach as [`Graphics.Input`](Graphics-Input) +for modelling user input, allowing you to keep track of new events and update +text fields programmatically. + +# Create Fields +@docs field, password, email + +# Field Content +@docs Content, Selection, Direction, noContent + +# Field Style +@docs defaultStyle, Style, Outline, noOutline, Highlight, noHighlight, Dimensions, uniformly +-} + +import Color (Color) +import Color +import Graphics.Element (Element) +import Native.Graphics.Input +import Signal +import Text + +{-| Create uniform dimensions: + + uniformly 4 == { left=4, right=4, top=4, bottom=4 } + +The following example creates an outline where the left, right, top, and bottom +edges all have width 1: + + Outline grey (uniformly 1) 4 +-} +uniformly : Int -> Dimensions +uniformly n = Dimensions n n n n + +{-| For setting dimensions of a field's padding or outline. The left, right, +top, and bottom may all have different sizes. The following example creates +dimensions such that the left and right are twice as wide as the top and bottom: + + myDimensions : Int -> Dimensions + myDimensions n = + { left = 2 * n + , right = 2 * n + , top = n + , bottom = n + } +-} +type alias Dimensions = + { left:Int + , right:Int + , top:Int + , bottom:Int + } + +{-| A field can have a outline around it. This lets you set its color, width, +and radius. The radius allows you to round the corners of your field. Set the +width to zero to make it invisible. Here is an example outline that is grey +and thin with slightly rounded corners: + + { color = grey, width = uniformly 1, radius = 4 } +-} +type alias Outline = + { color:Color + , width:Dimensions + , radius:Int + } + +{-| An outline with zero width, so you cannot see it. -} +noOutline : Outline +noOutline = Outline Color.grey (uniformly 0) 0 + +{-| When a field has focus, it has a blue highlight around it by default. The +`Highlight` lets you set the `color` and `width` of this highlight. Set the +`width` to zero to turn the highlight off. Here is an example highlight that +is blue and thin: + + { color = blue, width = 1 } +-} +type alias Highlight = + { color:Color + , width:Int + } + +{-| A highlight with zero width, so you cannot see it. -} +noHighlight : Highlight +noHighlight = Highlight Color.blue 0 + +{-| Describe the style of a text box. `style` describes the style of the text +itself using [`Text.Style`](/Text#Style). `highlight` describes the glowing blue +highlight that shows up when the field has focus. `outline` describes the line +surrounding the text field, and `padding` adds whitespace between the `outline` +and the text. + +The width and height of the text box *includes* the `padding` and `outline`. +Say we have a text box that is 40 pixels tall. It has a uniform outline of +1 pixel and a uniform padding of 5 pixels. Both of these must be subtracted +from the total height to determine how much room there is for text. The +`padding` and `outline` appear on the top and bottom, so there will be 28 +vertical pixels remaining for the text (40 - 1 - 5 - 5 - 1). +-} +type alias Style = + { padding : Dimensions + , outline : Outline + , highlight : Highlight + , style : Text.Style + } + +{-| The default style for a text field. The outline is `Color.grey` with width +1 and radius 2. The highlight is `Color.blue` with width 1, and the default +text color is black. +-} +defaultStyle : Style +defaultStyle = + { padding = uniformly 4 + , outline = Outline Color.grey (uniformly 1) 2 + , highlight = Highlight Color.blue 1 + , style = Text.defaultStyle + } + +{-| Represents the current content of a text field. For example: + + content = Content "She sells sea shells" (Selection 0 3 Backward) + +This means the user highlighted the substring `"She"` backwards. The value of +`content.string` is `"She sells sea shells"`. +-} +type alias Content = + { string:String + , selection:Selection + } + +{-| The selection within a text field. `start` is never greater than `end`: + + Selection 0 0 Forward -- cursor precedes all characters + + Selection 5 9 Backward -- highlighting characters starting after + -- the 5th and ending after the 9th +-} +type alias Selection = + { start:Int + , end:Int + , direction:Direction + } + +{-| The direction of selection. When the user highlights a selection in a text +field, they must do it in a particular direction. This determines which end of +the selection moves when they change the selection by pressing Shift-Left or +Shift-Right. +-} +type Direction = Forward | Backward + +{-| A field with no content: + + Content "" (Selection 0 0 Forward) +-} +noContent : Content +noContent = Content "" (Selection 0 0 Forward) + +{-| Create a text field. The following example creates a time-varying element +called `nameField`. As the user types their name, the field will be updated +to match what they have entered. + + name : Signal.Channel Content + name = Signal.channel noContent + + nameField : Signal Element + nameField = + field defaultStyle (Signal.send name) "Name" <~ Signal.subscribe name + +When we use the `field` function, we first give it a visual style. This is +the first argument so that it is easier to define your own custom field +(`myField = field myStyle`). The next two arguments are a `Handle` and a +handler function that processes or augments events before sending them along +to the associated `Input`. In the example above we use the `identity` function to +pass events along unchanged to the `name` `Input`. We then provide the +place-holder message to use when no input has been provided yet. Finally, +we give the current `Content` of the field. This argument is last because +it is most likely to change frequently, making function composition easier. +-} +field : Style -> (Content -> Signal.Message) -> String -> Content -> Element +field = Native.Graphics.Input.field + +{-| Same as `field` but the UI element blocks out each characters. -} +password : Style -> (Content -> Signal.Message) -> String -> Content -> Element +password = Native.Graphics.Input.password + +{-| Same as `field` but it adds an annotation that this field is for email +addresses. This is helpful for auto-complete and for mobile users who may +get a custom keyboard with an `@` and `.com` button. +-} +email : Style -> (Content -> Signal.Message) -> String -> Content -> Element +email = Native.Graphics.Input.email + +-- area : (Content -> Signal.Message) -> Handle b -> ((Int,Int) -> b) -> (Int,Int) -> String -> Content -> Element +-- area = Native.Graphics.Input.area
+ src/Http.elm view
@@ -0,0 +1,59 @@+module Http where +{-| A library for asynchronous HTTP requests. See the `WebSocket` +library if you have very strict latency requirements. + +# Sending Requests +@docs send, sendGet + +# Creating Requests +@docs get, post, request + +# Responses +@docs Response +-} + +import Signal (Signal) +import Signal +import Native.Http + +{-| The datatype for responses. Success contains only the returned message. +Failures contain both an error code and an error message. +-} +type Response a + = Success a + | Waiting + | Failure Int String + +type alias Request a = + { verb : String + , url : String + , body : a + , headers : List (String,String) + } + +{-| Create a customized request. Arguments are request type (get, post, put, +delete, etc.), target url, data, and a list of additional headers. +-} +request : String -> String -> String -> List (String,String) -> Request String +request = Request + +{-| Create a GET request to the given url. -} +get : String -> Request String +get url = Request "GET" url "" [] + +{-| Create a POST request to the given url, carrying the given data. -} +post : String -> String -> Request String +post url body = Request "POST" url body [] + +{-| Performs an HTTP request with the given requests. Produces a signal +that carries the responses. +-} +send : Signal (Request a) -> Signal (Response String) +send = Native.Http.send + +{-| Performs an HTTP GET request with the given urls. Produces a signal +that carries the responses. +-} +sendGet : Signal String -> Signal (Response String) +sendGet requestStrings = + send (Signal.map get requestStrings)
+ src/Json/Decode.elm view
@@ -0,0 +1,498 @@+module Json.Decode where +{-| A way to turn JSON strings and JS values into Elm values. + +# Run a Decoder +@docs decodeString, decodeValue + +# Primitives +@docs string, int, float, bool, null + +# Arrays +@docs list, array, + tuple1, tuple2, tuple3, tuple4, tuple5, tuple6, tuple7, tuple8 + +# Objects +@docs (:=), at, + object1, object2, object3, object4, object5, object6, object7, object8, + keyValuePairs, dict + +# Oddly Shaped Values +@docs maybe, oneOf, map, fail, succeed, andThen + +# "Creative" Values +@docs value, customDecoder +-} + + +import Native.Json +import Array (Array) +import Dict +import Dict (Dict) +import Json.Encode as JsonEncode +import List +import Maybe (Maybe) +import Result (Result) + + +type Decoder a = Decoder + +type alias Value = JsonEncode.Value + + +{-| Transform the value returned by a decoder. Most useful when paired with +the `oneOf` function. + + nullOr : Decoder a -> Decoder (Maybe a) + nullOr decoder = + oneOf + [ null Nothing + , map Just decoder + ] + + type UserID = OldID Int | NewID String + + -- 1234 or "1234abc" + userID : Decoder UserID + userID = + oneOf + [ map OldID int + , map NewID string + ] +-} +map : (a -> b) -> Decoder a -> Decoder b +map = + Native.Json.decodeObject1 + + +decodeString : Decoder a -> String -> Result String a +decodeString = + Native.Json.runDecoderString + + +-- OBJECTS + +{-| Access a nested field, making it easy to dive into big structures. This is +really a helper function so you do not need to write `(:=)` so many times. + + -- object.target.value = 'hello' + + value : Decoder String + value = + at ["target", "value"] string + + at fields decoder = + List.foldr (:=) decoder fields +-} +at : List String -> Decoder a -> Decoder a +at fields decoder = + List.foldr (:=) decoder fields + + +{-| Decode an object if it has a certain field. + + nameAndAge : Decoder (String,Int) + nameAndAge = + object2 (,) + ("name" := string) + ("age" := int) + + optionalProfession : Decoder (Maybe String) + optionalProfession = + maybe ("profession" := string) +-} +(:=) : String -> Decoder a -> Decoder a +(:=) = + Native.Json.decodeField + + +object1 : (a -> value) -> Decoder a -> Decoder value +object1 = + Native.Json.decodeObject1 + + +{-| Use two different decoders on a JS value. This is nice for extracting +multiple fields from an object. + + point : Decoder (Float,Float) + point = + object2 (,) + ("x" := float) + ("y" := float) +-} +object2 : (a -> b -> value) -> Decoder a -> Decoder b -> Decoder value +object2 = + Native.Json.decodeObject2 + + +{-| Use two different decoders on a JS value. This is nice for extracting +multiple fields from an object. + + type Task = { task : String, id : Int, completed : Bool } + + point : Decoder (Float,Float) + point = + object3 Task + ("task" := string) + ("id" := int) + ("completed" := bool) +-} +object3 : (a -> b -> c -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder value +object3 = + Native.Json.decodeObject3 + + +object4 : (a -> b -> c -> d -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder value +object4 = + Native.Json.decodeObject4 + + +object5 : (a -> b -> c -> d -> e -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder value +object5 = + Native.Json.decodeObject5 + + +object6 : (a -> b -> c -> d -> e -> f -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f -> Decoder value +object6 = + Native.Json.decodeObject6 + + +object7 : (a -> b -> c -> d -> e -> f -> g -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f -> Decoder g -> Decoder value +object7 = + Native.Json.decodeObject7 + + +object8 : (a -> b -> c -> d -> e -> f -> g -> h -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f -> Decoder g -> Decoder h -> Decoder value +object8 = + Native.Json.decodeObject8 + + +{-| Turn any object into a list of key-value pairs. + + -- { tom: 89, sue: 92, bill: 97, ... } + grades : Decoder (List (String, Int)) + grades = + keyValuePairs int +-} +keyValuePairs : Decoder a -> Decoder (List (String, a)) +keyValuePairs = + Native.Json.decodeKeyValuePairs + + +{-| Turn any object into a dictionary of key-value pairs. + + -- { mercury: 0.33, venus: 4.87, earth: 5.97, ... } + planetMasses : Decoder (Dict String Int) + planetMasses = + dict float +-} +dict : Decoder a -> Decoder (Dict String a) +dict decoder = + map Dict.fromList (keyValuePairs decoder) + + + +{-| Try out a couple different decoders. This is helpful when you are dealing +with something with a very strange shape and when `andThen` does not help +narrow things down so you can be more targeted. + + -- [ [3,4], { x:0, y:0 }, [5,12] ] + + points : Decoder (List (Float,Float)) + points = + list point + + point : (Float,Float) + point = + oneOf + [ tuple2 (,) float float + , object2 (,) ("x" := float) ("y" := float) + ] +-} +oneOf : List (Decoder a) -> Decoder a +oneOf = + Native.Json.oneOf + + +{-| Extract a string. + + -- ["John","Doe"] + + name : Decoder (String, String) + name = + tuple2 (,) string string +-} +string : Decoder String +string = + Native.Json.decodeString + + +{-| Extract a float. + + -- [ 6.022, 3.1415, 1.618 ] + + numbers : Decoder (List Float) + numbers = + list float +-} +float : Decoder Float +float = + Native.Json.decodeFloat + + +{-| Extract an integer. + + -- { ... age: 42 ... } + + age : Decoder Int + age = + "age" := int +-} +int : Decoder Int +int = + Native.Json.decodeInt + + +{-| Extract a boolean. + + -- { ... checked: true ... } + + checked : Decoder Bool + checked = + "checked" := true +-} +bool : Decoder Bool +bool = + Native.Json.decodeBool + + +{-| Extract a list from a JS array. + + -- [1,2,3,4] + + numbers : Decoder [Int] + numbers = + list int +-} +list : Decoder a -> Decoder (List a) +list = + Native.Json.decodeList + + +{-| Extract an Array from a JS array. + + -- [1,2,3,4] + + numbers : Decoder (Array Int) + numbers = + array int +-} +array : Decoder a -> Decoder (Array a) +array = + Native.Json.decodeArray + + +{-| Extract a null value. Primarily useful for creating *other* decoders. + + nullOr : Decoder a -> Decoder (Maybe a) + nullOr decoder = + oneOf + [ null Nothing + , map Just decoder + ] + + + numbers : Decoder [Int] + numbers = + list (oneOf [ int, null 0 ]) +-} +null : a -> Decoder a +null = + Native.Json.decodeNull + + +{-| Great for handling optional fields. The following code decodes JSON +objects that may not have a profession field. + + -- { name: "Tom", age: 31, profession: "plumber" } + -- { name: "Sue", age: 42 } + + type alias Person = + { name : String + , age : Int + , profession : Maybe String + } + + person : Decoder Person + person = + object3 Person + ("name" := string) + ("age" := int) + (maybe ("profession" := string)) +-} +maybe : Decoder a -> Decoder (Maybe a) +maybe = + Native.Json.decodeMaybe + + +{-| Bring in an arbitrary JSON value. Useful if you need to work with crazily +formatted data. For example, this lets you create a parser for "variadic" lists +where the first few types are different, followed by 0 or more of the same +type. + + variadic2 : (a -> b -> List c -> value) -> Decoder a -> Decoder b -> Decoder (List c) -> Decoder value + variadic2 f a b cs = + customDecoder (list value) \jsonList -> + case jsonList of + one :: two :: rest -> + Result.map3 f + (decodeValue a one) + (decodeValue b two) + (decodeValue cs rest) + + _ -> Result.Err "expecting at least two elements in the array" +-} +value : Decoder Value +value = + Native.Json.decodeValue + + +decodeValue : Decoder a -> Value -> Result String a +decodeValue = + Native.Json.runDecoderValue + + +customDecoder : Decoder a -> (a -> Result String b) -> Decoder b +customDecoder = + Native.Json.customDecoder + + +{-| Helpful when one field will determine the shape of a bunch of other fields. + + type Shape + = Rectangle Float Float + | Circle Float + + shape : Decoder Shape + shape = + ("tag" := string) `andThen` shapeInfo + + shapeInfo : String -> Decoder Shape + shapeInfo tag = + case tag of + "rectangle" -> + object2 Rectangle + ("width" := float) + ("height" := float) + + "circle" -> + object1 Circle + ("radius" := float) + + _ -> + fail (tag ++ " is not a recognized tag for shapes") +-} +andThen : Decoder a -> (a -> Decoder b) -> Decoder b +andThen = + Native.Json.andThen + + +{-| A decoder that always fails. Useful when paired with `andThen` or `oneOf` +to improve error messages when things go wrong. For example, the following +decoder is able to provide a much more specific error message when `fail` is +the last option. + + point : (Float,Float) + point = + oneOf + [ tuple2 (,) float float + , object2 (,) ("x" := float) ("y" := float) + , fail "expecting some kind of point" + ] +-} +fail : String -> Decoder a +fail = + Native.Json.fail + + +{-| A decoder that always succeeds. Useful when paired with `andThen` or +`oneOf` but everything is supposed to work out at the end. For example, +maybe you have an optional field that can have a default value when it is +missing. + + -- { x:3, y:4 } or { x:3, y:4, z:5 } + + point3D : Decoder (Float,Float,Float) + point3D = + object (,,) + ("x" := float) + ("y" := float) + (oneOf [ "z" := float, succeed 0 ]) +-} +succeed : a -> Decoder a +succeed = + Native.Json.succeed + + +-- TUPLES + +tuple1 : (a -> value) -> Decoder a -> Decoder value +tuple1 = + Native.Json.decodeTuple1 + + +{-| Handle an array with exactly two values. Useful for points and simple +pairs. + + -- [3,4] or [0,0] + point : Decoder (Float,Float) + point = + tuple2 (,) float float + + -- ["John","Doe"] or ["Hermann","Hesse"] + name : Decoder Name + name = + tuple2 Name string string + + type alias Name = { first : String, last : String } +-} +tuple2 : (a -> b -> value) -> Decoder a -> Decoder b -> Decoder value +tuple2 = + Native.Json.decodeTuple2 + + +{-| Handle an array with exactly three values. + + -- [3,4,5] or [0,0,0] + point3D : Decoder (Float,Float,Float) + point3D = + tuple3 (,,) float float float + +-} +tuple3 : (a -> b -> c -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder value +tuple3 = + Native.Json.decodeTuple3 + + +tuple4 : (a -> b -> c -> d -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder value +tuple4 = + Native.Json.decodeTuple4 + + +tuple5 : (a -> b -> c -> d -> e -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder value +tuple5 = + Native.Json.decodeTuple5 + + +tuple6 : (a -> b -> c -> d -> e -> f -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f -> Decoder value +tuple6 = + Native.Json.decodeTuple6 + + +tuple7 : (a -> b -> c -> d -> e -> f -> g -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f -> Decoder g -> Decoder value +tuple7 = + Native.Json.decodeTuple7 + + +tuple8 : (a -> b -> c -> d -> e -> f -> g -> h -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f -> Decoder g -> Decoder h -> Decoder value +tuple8 = + Native.Json.decodeTuple8
+ src/Json/Encode.elm view
@@ -0,0 +1,85 @@+ +module Json.Encode where + +{-| Library for turning Elm values into JSON values. + +# Encoding +@docs encode + +# Primatives +@docs string, int, float, bool, null + +# Arrays +@docs list, array + +# Objects +@docs object +-} + +import Array (Array) +import Native.Json + + +type Value = Value + + +{-| Convert a `Value` into a prettified string. The first argument specifies +the amount of indentation in the resulting string. + + person = + object + [ ("name", string "Tom") + , ("age", int 42) + ] + + compact = encode 0 person + -- {"name":"Tom","age":42} + + readable = encode 4 person + -- { + -- "name": "Tom", + -- "age": 42 + -- } +-} +encode : Int -> Value -> String +encode = + Native.Json.encode + + +string : String -> Value +string = + Native.Json.identity + +int : Int -> Value +int = + Native.Json.identity + + +float : Float -> Value +float = + Native.Json.identity + + +bool : Bool -> Value +bool = + Native.Json.identity + + +null : Value +null = + Native.Json.encodeNull + + +object : List (String, Value) -> Value +object = + Native.Json.encodeObject + + +array : Array Value -> Value +array = + Native.Json.encodeArray + + +list : List Value -> Value +list = + Native.Json.encodeList
+ src/Keyboard.elm view
@@ -0,0 +1,82 @@+module Keyboard where +{-| Library for working with keyboard input. + +# Representing Keys +@docs KeyCode + +# Directions +@docs arrows, wasd, directions + +# Specific Keys +The following signals are `True` when the particular key is pressed and `False` +otherwise. + +@docs enter, space, ctrl, shift, alt, meta + +# General Keypresses +@docs isDown, keysDown, lastPressed + +-} + +import Signal (Signal) +import Native.Keyboard + +{-| Type alias to make it clearer what integers are supposed to represent +in this library. Use `Char.toCode` and `Char.fromCode` to convert key codes +to characters. Use the uppercase character with `toCode`. +-} +type alias KeyCode = Int + +{-| Custom key directions to support different locales. The order is up, down, +left, right. +-} +directions : KeyCode -> KeyCode -> KeyCode -> KeyCode -> Signal { x:Int, y:Int } +directions = Native.Keyboard.directions + +{-| A signal of records indicating which arrow keys are pressed. + + * `{ x = 0, y = 0 }` when pressing no arrows. + * `{ x =-1, y = 0 }` when pressing the left arrow. + * `{ x = 1, y = 1 }` when pressing the up and right arrows. + * `{ x = 0, y =-1 }` when pressing the down, left, and right arrows. +-} +arrows : Signal { x:Int, y:Int } +arrows = directions 38 40 37 39 + +{-| Just like the arrows signal, but this uses keys w, a, s, and d, +which are common controls for many computer games. +-} +wasd : Signal { x:Int, y:Int } +wasd = directions 87 83 65 68 + +{-| Whether an arbitrary key is pressed. -} +isDown : KeyCode -> Signal Bool +isDown = Native.Keyboard.isDown + +alt : Signal Bool +alt = Native.Keyboard.alt + +ctrl : Signal Bool +ctrl = isDown 17 + +{-| The meta key is the Windows key on Windows and the Command key on Mac. +-} +meta : Signal Bool +meta = Native.Keyboard.meta + +shift : Signal Bool +shift = isDown 16 + +space : Signal Bool +space = isDown 32 + +enter : Signal Bool +enter = isDown 13 + +{-| List of keys that are currently down. -} +keysDown : Signal (List KeyCode) +keysDown = Native.Keyboard.keysDown + +{-| The latest key that has been pressed. -} +lastPressed : Signal KeyCode +lastPressed = Native.Keyboard.lastPressed
+ src/List.elm view
@@ -0,0 +1,383 @@+module List where + +{-| A library for manipulating lists of values. Every value in a +list must have the same type. + +# Basics +@docs isEmpty, length, reverse, member + +# Sub-lists +@docs head, tail, filter, take, drop + +# Putting Lists Together +@docs repeat, (::), append, concat, intersperse + +# Taking Lists Apart +@docs partition, unzip + +# Mapping +@docs map, map2, map3, map4, map5 + +If you can think of a legitimate use of `mapN` where `N` is 6 or more, please +let us know on [the list](https://groups.google.com/forum/#!forum/elm-discuss). +The current sentiment is that it is already quite error prone once you get to +4 and possibly should be approached another way. + +# Special Maps +@docs filterMap, concatMap, indexedMap + +# Folding +@docs foldr, foldl + +# Special Folds +@docs sum, product, maximum, minimum, all, any, foldr1, foldl1, scanl, scanl1 + +# Sorting +@docs sort, sortBy, sortWith + +-} + +import Basics (..) +import Maybe ( Maybe(Just,Nothing) ) +import Native.List + + +{-| Add an element to the front of a list. Pronounced *cons*. + + 1 :: [2,3] == [1,2,3] + 1 :: [] == [1] +-} +(::) : a -> List a -> List a +(::) = Native.List.cons + + +infixr 5 :: + + +{-| Extract the first element of a list. List must be non-empty. + + head [1,2,3] == 1 +-} +head : List a -> a +head = Native.List.head + + +{-| Extract the elements after the head of the list. List must be non-empty. + + tail [1,2,3] == [2,3] +-} +tail : List a -> List a +tail = Native.List.tail + + +{-| Determine if a list is empty. + + isEmpty [] == True +-} +isEmpty : List a -> Bool +isEmpty xs = + case xs of + [] -> True + _ -> False + + +member : a -> List a -> Bool +member = + Native.List.member + + +{-| Apply a function to every element of a list. + + map sqrt [1,4,9] == [1,2,3] + + map not [True,False,True] == [False,True,False] +-} +map : (a -> b) -> List a -> List b +map = Native.List.map + +{-| Same as `map` but the function is also applied to the index of each +element (starting at zero). + + indexedMap (,) ["Tom","Sue","Bob"] == [ (0,"Tom"), (1,"Sue"), (2,"Bob") ] +-} +indexedMap : (Int -> a -> b) -> List a -> List b +indexedMap f xs = + map2 f [ 0 .. length xs - 1 ] xs + +{-| Reduce a list from the left. + + foldl (::) [] [1,2,3] == [3,2,1] +-} +foldl : (a -> b -> b) -> b -> List a -> b +foldl = Native.List.foldl + +{-| Reduce a list from the right. + + foldr (+) 0 [1,2,3] == 6 +-} +foldr : (a -> b -> b) -> b -> List a -> b +foldr = Native.List.foldr + +{-| Reduce a list from the left without a base case. List must be non-empty. -} +foldl1 : (a -> a -> a) -> List a -> a +foldl1 = Native.List.foldl1 + +{-| Reduce a list from the right without a base case. List must be non-empty. -} +foldr1 : (a -> a -> a) -> List a -> a +foldr1 = Native.List.foldr1 + +{-| Reduce a list from the left, building up all of the intermediate results into a list. + + scanl (+) 0 [1,2,3,4] == [0,1,3,6,10] +-} +scanl : (a -> b -> b) -> b -> List a -> List b +scanl = Native.List.scanl + +{-| Same as scanl but it doesn't require a base case. List must be non-empty. + + scanl1 (+) [1,2,3,4] == [1,3,6,10] +-} +scanl1 : (a -> a -> a) -> List a -> List a +scanl1 = Native.List.scanl1 + +{-| Keep only elements that satisfy the predicate. + + filter isEven [1..6] == [2,4,6] +-} +filter : (a -> Bool) -> List a -> List a +filter = Native.List.filter + +{-| Apply a function that may succeed to all values in the list, but only keep +the successes. + + String.toInt : String -> Maybe Int + + filterMap String.toInt ["3", "4.0", "5", "hats"] == [3,5] +-} +filterMap : (a -> Maybe b) -> List a -> List b +filterMap f xs = foldr (maybeCons f) [] xs + +maybeCons : (a -> Maybe b) -> a -> List b -> List b +maybeCons f mx xs = + case f mx of + Just x -> x :: xs + Nothing -> xs + +{-| Determine the length of a list. + + length [1,2,3] == 3 +-} +length : List a -> Int +length = Native.List.length + +{-| Reverse a list. + + reverse [1..4] == [4,3,2,1] +-} +reverse : List a -> List a +reverse = Native.List.reverse + +{-| Determine if all elements satisfy the predicate. + + all isEven [2,4] == True + all isEven [2,3] == False + all isEven [] == True +-} +all : (a -> Bool) -> List a -> Bool +all = Native.List.all + +{-| Determine if any elements satisfy the predicate. + + any isEven [2,3] == True + any isEven [1,3] == False + any isEven [] == False +-} +any : (a -> Bool) -> List a -> Bool +any = Native.List.any + + +{-| Put two lists together. + + append [1,1,2] [3,5,8] == [1,1,2,3,5,8] + append ['a','b'] ['c'] == ['a','b','c'] +-} +append : List a -> List a -> List a +append = Native.List.append + + +{-| Concatenate a bunch of lists into a single list: + + concat [[1,2],[3],[4,5]] == [1,2,3,4,5] +-} +concat : List (List a) -> List a +concat lists = + foldr append [] lists + + +{-| Map a given function onto a list and flatten the resulting lists. + + concatMap f xs == concat (map f xs) +-} +concatMap : (a -> List b) -> List a -> List b +concatMap f list = + concat (map f list) + + +{-| Get the sum of the list elements. + + sum [1..4] == 10 +-} +sum : List number -> number +sum numbers = + foldl (+) 0 numbers + + +{-| Get the product of the list elements. + + product [1..4] == 24 +-} +product : List number -> number +product numbers = + foldl (*) 1 numbers + + +{-| Find the maximum element in a non-empty list. + + maximum [1,4,2] == 4 +-} +maximum : List comparable -> comparable +maximum = foldl1 max + + +{-| Find the minimum element in a non-empty list. + + minimum [3,2,1] == 1 +-} +minimum : List comparable -> comparable +minimum = foldl1 min + + +{-| Partition a list based on a predicate. The first list contains all values +that satisfy the predicate, and the second list contains all the value that do +not. + + partition (\x -> x < 3) [0..5] == ([0,1,2], [3,4,5]) + partition isEven [0..5] == ([0,2,4], [1,3,5]) +-} +partition : (a -> Bool) -> List a -> (List a, List a) +partition pred list = + let step x (trues, falses) = + if pred x + then (x :: trues, falses) + else (trues, x :: falses) + in + foldr step ([],[]) list + + +{-| Combine two lists, combining them with the given function. +If one list is longer, the extra elements are dropped. + + map2 (+) [1,2,3] [1,2,3,4] == [2,4,6] + + map2 (,) [1,2,3] ['a','b'] == [ (1,'a'), (2,'b') ] + + pairs : List a -> List b -> List (a,b) + pairs lefts rights = + map2 (,) lefts rights +-} +map2 : (a -> b -> result) -> List a -> List b -> List result +map2 = Native.List.map2 + +map3 : (a -> b -> c -> result) -> List a -> List b -> List c -> List result +map3 = Native.List.map3 + +map4 : (a -> b -> c -> d -> result) -> List a -> List b -> List c -> List d -> List result +map4 = Native.List.map4 + +map5 : (a -> b -> c -> d -> e -> result) -> List a -> List b -> List c -> List d -> List e -> List result +map5 = Native.List.map5 + + +{-| Decompose a list of tuples into a tuple of lists. + + unzip [(0, True), (17, False), (1337, True)] == ([0,17,1337], [True,False,True]) +-} +unzip : List (a,b) -> (List a, List b) +unzip pairs = + let step (x,y) (xs,ys) = + (x :: xs, y :: ys) + in + foldr step ([], []) pairs + + +{-| Places the given value between all members of the given list. + + intersperse "on" ["turtles","turtles","turtles"] == ["turtles","on","turtles","on","turtles"] +-} +intersperse : a -> List a -> List a +intersperse sep xs = + case xs of + [] -> [] + hd :: tl -> + let step x rest = sep :: x :: rest + spersed = foldr step [] tl + in + hd :: spersed + +{-| Take the first *n* members of a list. + + take 2 [1,2,3,4] == [1,2] +-} +take : Int -> List a -> List a +take = Native.List.take + +{-| Drop the first *n* members of a list. + + drop 2 [1,2,3,4] == [3,4] +-} +drop : Int -> List a -> List a +drop = Native.List.drop + +{-| Create a list with *n* copies of a value: + + repeat 3 (0,0) == [(0,0),(0,0),(0,0)] +-} +repeat : Int -> a -> List a +repeat = Native.List.repeat + +{-| Sort values from lowest to highest + + sort [3,1,5] == [1,3,5] +-} +sort : List comparable -> List comparable +sort = Native.List.sort + +{-| Sort values by a derived property. + + alice = { name="Alice", height=1.62 } + bob = { name="Bob" , height=1.85 } + chuck = { name="Chuck", height=1.76 } + + sortBy .name [chuck,alice,bob] == [alice,bob,chuck] + sortBy .height [chuck,alice,bob] == [alice,chuck,bob] + + sortBy String.length ["mouse","cat"] == ["cat","mouse"] +-} +sortBy : (a -> comparable) -> List a -> List a +sortBy = Native.List.sortBy + +{-| Sort values with a custom comparison function. + + sortWith flippedComparison [1..5] == [5,4,3,2,1] + + flippedComparison a b = + case compare a b of + LT -> GT + EQ -> EQ + GT -> LT + +This is also the most general sort function, allowing you +to define any other: `sort == sortWith compare` +-} +sortWith : (a -> a -> Order) -> List a -> List a +sortWith = Native.List.sortWith
+ src/Maybe.elm view
@@ -0,0 +1,112 @@+module Maybe (Maybe(Just,Nothing), andThen, map, withDefault, oneOf) where + +{-| This library fills a bunch of important niches in Elm. A `Maybe` can help +you with optional arguments, error handling, and records with optional fields. + +# Definition +@docs Maybe + +# Common Helpers +@docs map, withDefault, oneOf + +# Chaining Maybes +@docs andThen + +-} + +{-| Represent values that may or may not exist. It can be useful if you have a +record field that is only filled in sometimes. Or if a function takes a value +sometimes, but does not absolutely need it. + + -- A person, but maybe we do not know their age. + type alias Person = + { name : String + , age : Maybe Int + } + + tom = { name = "Tom", age = Just 42 } + sue = { name = "Sue", age = Nothing } +-} +type Maybe a = Just a | Nothing + + +{-| Provide a default value, turning an optional value into a normal value. +This comes in handy when paired with functions like `Dict.get` which gives back +a `Maybe`. + + withDefault 100 (Just 42) -- 42 + withDefault 100 Nothing -- 100 + + withDefault "unknown" (Dict.get "Tom" Dict.empty) -- "unknown" + +-} +withDefault : a -> Maybe a -> a +withDefault default maybe = + case maybe of + Just value -> value + Nothing -> default + + +{-| Pick the first `Maybe` that actually has a value. Useful when you want to +try a couple different things, but there is no default value. + + oneOf [ Nothing, Just 42, Just 71 ] == Just 42 + oneOf [ Nothing, Nothing, Just 71 ] == Just 71 + oneOf [ Nothing, Nothing, Nothing ] == Nothing +-} +oneOf : List (Maybe a) -> Maybe a +oneOf maybes = + case maybes of + [] -> + Nothing + + maybe :: rest -> + case maybe of + Nothing -> oneOf rest + Just _ -> maybe + + +{-| Transform an `Maybe` value with a given function: + + map sqrt (Just 9) == Just 3 + map sqrt Nothing == Nothing +-} +map : (a -> b) -> Maybe a -> Maybe b +map f maybe = + case maybe of + Just value -> Just (f value) + Nothing -> Nothing + + +{-| Chain together many computations that may fail. It is helpful to see its +definition: + + andThen : Maybe a -> (a -> Maybe b) -> Maybe b + andThen maybe callback = + case maybe of + Just value -> callback value + Nothing -> Nothing + +This means we only continue with the callback if things are going well. For +example, say you need to use (`toInt : String -> Maybe Int`) to parse a month +and make sure it is between 1 and 12: + + toValidMonth : Int -> Maybe Int + toValidMonth month = + if month >= 1 && month <= 12 + then Just month + else Nothing + + toMonth : String -> Maybe Int + toMonth rawString = + toInt rawString `andThen` toValidMonth + +If `toInt` fails and results in `Nothing` this entire chain of operations will +short-circuit and result in `Nothing`. If `toValidMonth` results in `Nothing`, +again the chain of computations will result in `Nothing`. +-} +andThen : Maybe a -> (a -> Maybe b) -> Maybe b +andThen maybeValue callback = + case maybeValue of + Just value -> callback value + Nothing -> Nothing
+ src/Mouse.elm view
@@ -0,0 +1,36 @@+ +module Mouse where + +{-| Library for working with mouse input. + +# Position +@docs position, x, y + +# Button Status +@docs isDown, clicks + +-} + +import Signal (Signal) +import Native.Mouse + +{-| The current mouse position. -} +position : Signal (Int,Int) +position = Native.Mouse.position + +{-| The current x-coordinate of the mouse. -} +x : Signal Int +x = Native.Mouse.x + +{-| The current y-coordinate of the mouse. -} +y : Signal Int +y = Native.Mouse.y + +{-| The current state of the left mouse-button. +True when the button is down, and false otherwise. -} +isDown : Signal Bool +isDown = Native.Mouse.isDown + +{-| Always equal to unit. Event triggers on every mouse click. -} +clicks : Signal () +clicks = Native.Mouse.clicks
+ src/Native/Array.js view
@@ -0,0 +1,711 @@+Elm.Native.Array = {}; +Elm.Native.Array.make = function(elm) { + elm.Native = elm.Native || {}; + elm.Native.Array = elm.Native.Array || {}; + if (elm.Native.Array.values) return elm.Native.Array.values; + if ('values' in Elm.Native.Array) + return elm.Native.Array.values = Elm.Native.Array.values; + + var List = Elm.Native.List.make(elm); + + // A RRB-Tree has two distinct data types. + // Leaf -> "height" is always 0 + // "table" is an array of elements + // Node -> "height" is always greater than 0 + // "table" is an array of child nodes + // "lengths" is an array of accumulated lengths of the child nodes + + // M is the maximal table size. 32 seems fast. E is the allowed increase + // of search steps when concatting to find an index. Lower values will + // decrease balancing, but will increase search steps. + var M = 32; + var E = 2; + + // An empty array. + var empty = { ctor:"_Array", height:0, table:new Array() }; + + function get(i, array) { + if (i < 0 || i >= length(array)) { + throw new Error("Index " + i + " is out of range. Check the length of " + + "your array first or use getMaybe or getWithDefault."); + } + return unsafeGet(i, array); + } + + function unsafeGet(i, array) { + for (var x = array.height; x > 0; x--) { + var slot = i >> (x * 5); + while (array.lengths[slot] <= i) { + slot++; + } + if (slot > 0) { + i -= array.lengths[slot - 1]; + } + array = array.table[slot]; + } + return array.table[i]; + } + + // Sets the value at the index i. Only the nodes leading to i will get + // copied and updated. + function set(i, item, array) { + if (i < 0 || length(array) <= i) { + return array; + } + return unsafeSet(i, item, array); + } + + function unsafeSet(i, item, array) { + array = nodeCopy(array); + + if (array.height == 0) { + array.table[i] = item; + } else { + var slot = getSlot(i, array); + if (slot > 0) { + i -= array.lengths[slot - 1]; + } + array.table[slot] = unsafeSet(i, item, array.table[slot]); + } + return array; + } + + function initialize(len, f) { + if (len == 0) { return empty; } + var h = Math.floor(Math.log(len)/Math.log(M)); + return initialize_(f, h, 0, len); + } + + function initialize_(f, h, from, to) { + if (h == 0) { + var table = new Array((to - from) % (M + 1)); + for (var i = 0; i < table.length; i++) { + table[i] = f(from + i); + } + return { ctor:"_Array", height:0, table:table }; + } + + var step = Math.pow(M, h); + var table = new Array(Math.ceil((to - from) / step)); + var lengths = new Array(table.length); + for (var i = 0; i < table.length; i++) { + table[i] = initialize_( f, h - 1, from + (i * step) + , Math.min(from + ((i + 1) * step), to)); + lengths[i] = length(table[i]) + (i > 0 ? lengths[i-1] : 0); + } + return { ctor:"_Array", height:h, table:table, lengths:lengths }; + } + + function fromList(list) { + if (list == List.Nil) { return empty; } + + // Allocate M sized blocks (table) and write list elements to it. + var table = new Array(M); + var nodes = new Array(); + var i = 0; + + while (list.ctor !== '[]') { + table[i] = list._0; + list = list._1; + i++; + + // table is full, so we can push a leaf containing it into the + // next node. + if (i == M) { + fromListPush({ ctor:"_Array", height:0, table:table } + , nodes); + table = new Array(M); + i = 0; + } + } + + // Maybe there is something left on the table. + if (i > 0) { + fromListPush({ ctor:"_Array", height:0, table:table.splice(0,i) } + , nodes); + } + + // Go through all of the nodes and eventually push them into higher nodes. + for (var h = 0; h < nodes.length - 1; h++) { + if (nodes[h].table.length > 0) { + fromListPush(nodes[h], nodes); + } + } + + var head = nodes[nodes.length - 1]; + if (head.height > 0 && head.table.length == 1) { + return head.table[0]; + } else { + return head; + } + } + + // Push a node into a higher node as a child. + function fromListPush(toPush, nodes) { + var h = toPush.height; + + // Maybe the node on this height does not exist. + if (nodes.length == h) { + nodes.push({ ctor:"_Array", height:h + 1 + , table:new Array() + , lengths:new Array() }); + } + + nodes[h].table.push(toPush); + var len = length(toPush); + if (nodes[h].lengths.length > 0) { + len += nodes[h].lengths[nodes[h].lengths.length - 1]; + } + nodes[h].lengths.push(len); + + if (nodes[h].table.length == M) { + fromListPush(nodes[h], nodes); + nodes[h] = { ctor:"_Array", height:h + 1 + , table:new Array() + , lengths:new Array() }; + } + } + + // Pushes an item via push_ to the bottom right of a tree. + function push(item, a) { + var pushed = push_(item, a); + if (pushed !== null) { + return pushed; + } + + var newTree = create(item, a.height); + return siblise(a, newTree); + } + + // Recursively tries to push an item to the bottom-right most + // tree possible. If there is no space left for the item, + // null will be returned. + function push_(item, a) { + // Handle resursion stop at leaf level. + if (a.height == 0) { + if (a.table.length < M) { + var newA = { ctor:"_Array", height:0, table:a.table.slice() }; + newA.table.push(item); + return newA; + } else { + return null; + } + } + + // Recursively push + var pushed = push_(item, botRight(a)); + + // There was space in the bottom right tree, so the slot will + // be updated. + if (pushed != null) { + var newA = nodeCopy(a); + newA.table[newA.table.length - 1] = pushed; + newA.lengths[newA.lengths.length - 1]++; + return newA + } + + // When there was no space left, check if there is space left + // for a new slot with a tree which contains only the item + // at the bottom. + if (a.table.length < M) { + var newSlot = create(item, a.height - 1); + var newA = nodeCopy(a); + newA.table.push(newSlot); + newA.lengths.push(newA.lengths[newA.lengths.length - 1] + length(newSlot)); + return newA + } else { + return null; + } + } + + // Converts an array into a list of elements. + function toList(a) { + return toList_(List.Nil, a); + } + + function toList_(list, a) { + for (var i = a.table.length - 1; i >= 0; i--) { + list = a.height == 0 ? List.Cons(a.table[i], list) : toList_(list, a.table[i]); + } + return list; + } + + // Maps a function over the elements of an array. + function map(f, a) { + var newA = { ctor:"_Array", height:a.height, table:new Array(a.table) }; + if (a.height > 0) { newA.lengths = a.lengths; } + for (var i = 0; i < a.table.length; i++) { + newA.table[i] = a.height == 0 ? f(a.table[i]) : map(f, a.table[i]); + } + return newA; + } + + // Maps a function over the elements with their index as first argument. + function indexedMap(f, a) { + return indexedMap_(f, a, 0); + } + + function indexedMap_(f, a, from) { + var newA = { ctor:"_Array", height:a.height, table:new Array(a.table) }; + if (a.height > 0) { newA.lengths = a.lengths; } + for (var i = 0; i < a.table.length; i++) { + newA.table[i] = a.height == 0 ? A2(f, from + i, a.table[i]) + : indexedMap_( f, a.table[i] + , i == 0 ? 0 : a.lengths[i - 1]); + } + return newA; + } + + function foldl(f, b, a) { + if (a.height == 0) { + for (var i = 0; i < a.table.length; i++) { + b = A2(f, a.table[i], b); + } + } else { + for (var i = 0; i < a.table.length; i++) { + b = foldl(f, b, a.table[i]); + } + } + return b; + } + + function foldr(f, b, a) { + if (a.height == 0) { + for (var i = a.table.length; i--; ) { + b = A2(f, a.table[i], b); + } + } else { + for (var i = a.table.length; i--; ) { + b = foldl(f, b, a.table[i]); + } + } + return b; + } + + // TODO: currently, it slices the right, then the left. This can be + // optimized. + function slice(from, to, a) { + if (from < 0) { from += length(a); } + if (to < 0) { to += length(a); } + return sliceLeft(from, sliceRight(to, a)); + } + + function sliceRight(to, a) { + if (to == length(a)) { + return a; + } + + // Handle leaf level. + if (a.height == 0) { + var newA = { ctor:"_Array", height:0 }; + newA.table = a.table.slice(0, to); + return newA; + } + + // Slice the right recursively. + var right = getSlot(to, a); + var sliced = sliceRight(to - (right > 0 ? a.lengths[right - 1] : 0), a.table[right]); + + // Maybe the a node is not even needed, as sliced contains the whole slice. + if (right == 0) { + return sliced; + } + + // Create new node. + var newA = { ctor:"_Array", height:a.height + , table:a.table.slice(0, right) + , lengths:a.lengths.slice(0, right) }; + if (sliced.table.length > 0) { + newA.table[right] = sliced; + newA.lengths[right] = length(sliced) + (right > 0 ? newA.lengths[right - 1] : 0); + } + return newA; + } + + function sliceLeft(from, a) { + if (from == 0) { + return a; + } + + // Handle leaf level. + if (a.height == 0) { + var newA = { ctor:"_Array", height:0 }; + newA.table = a.table.slice(from, a.table.length + 1); + return newA; + } + + // Slice the left recursively. + var left = getSlot(from, a); + var sliced = sliceLeft(from - (left > 0 ? a.lengths[left - 1] : 0), a.table[left]); + + // Maybe the a node is not even needed, as sliced contains the whole slice. + if (left == a.table.length - 1) { + return sliced; + } + + // Create new node. + var newA = { ctor:"_Array", height:a.height + , table:a.table.slice(left, a.table.length + 1) + , lengths:new Array(a.table.length - left) }; + newA.table[0] = sliced; + var len = 0; + for (var i = 0; i < newA.table.length; i++) { + len += length(newA.table[i]); + newA.lengths[i] = len; + } + + return newA; + } + + // Appends two trees. + function append(a,b) { + if (a.table.length === 0) { + return b; + } + if (b.table.length === 0) { + return a; + } + + var c = append_(a, b); + + // Check if both nodes can be crunshed together. + if (c[0].table.length + c[1].table.length <= M) { + if (c[0].table.length === 0) { + return c[1]; + } + if (c[1].table.length === 0) { + return c[0]; + } + + // Adjust .table and .lengths + c[0].table = c[0].table.concat(c[1].table); + if (c[0].height > 0) { + var len = length(c[0]); + for (var i = 0; i < c[1].lengths.length; i++) { + c[1].lengths[i] += len; + } + c[0].lengths = c[0].lengths.concat(c[1].lengths); + } + + return c[0]; + } + + if (c[0].height > 0) { + var toRemove = calcToRemove(a, b); + if (toRemove > E) { + c = shuffle(c[0], c[1], toRemove); + } + } + + return siblise(c[0], c[1]); + } + + // Returns an array of two nodes; right and left. One node _may_ be empty. + function append_(a, b) { + if (a.height === 0 && b.height === 0) { + return [a, b]; + } + + if (a.height !== 1 || b.height !== 1) { + if (a.height === b.height) { + a = nodeCopy(a); + b = nodeCopy(b); + var appended = append_(botRight(a), botLeft(b)); + + insertRight(a, appended[1]); + insertLeft(b, appended[0]); + } else if (a.height > b.height) { + a = nodeCopy(a); + var appended = append_(botRight(a), b); + + insertRight(a, appended[0]); + b = parentise(appended[1], appended[1].height + 1); + } else { + b = nodeCopy(b); + var appended = append_(a, botLeft(b)); + + var left = appended[0].table.length === 0 ? 0 : 1; + var right = left === 0 ? 1 : 0; + insertLeft(b, appended[left]); + a = parentise(appended[right], appended[right].height + 1); + } + } + + // Check if balancing is needed and return based on that. + if (a.table.length === 0 || b.table.length === 0) { + return [a,b]; + } + + var toRemove = calcToRemove(a, b); + if (toRemove <= E) { + return [a,b]; + } + return shuffle(a, b, toRemove); + } + + // Helperfunctions for append_. Replaces a child node at the side of the parent. + function insertRight(parent, node) { + var index = parent.table.length - 1; + parent.table[index] = node; + parent.lengths[index] = length(node) + parent.lengths[index] += index > 0 ? parent.lengths[index - 1] : 0; + } + + function insertLeft(parent, node) { + if (node.table.length > 0) { + parent.table[0] = node; + parent.lengths[0] = length(node); + + var len = length(parent.table[0]); + for (var i = 1; i < parent.lengths.length; i++) { + len += length(parent.table[i]); + parent.lengths[i] = len; + } + } else { + parent.table.shift(); + for (var i = 1; i < parent.lengths.length; i++) { + parent.lengths[i] = parent.lengths[i] - parent.lengths[0]; + } + parent.lengths.shift(); + } + } + + // Returns the extra search steps for E. Refer to the paper. + function calcToRemove(a, b) { + var subLengths = 0; + for (var i = 0; i < a.table.length; i++) { + subLengths += a.table[i].table.length; + } + for (var i = 0; i < b.table.length; i++) { + subLengths += b.table[i].table.length; + } + + var toRemove = a.table.length + b.table.length + return toRemove - (Math.floor((subLengths - 1) / M) + 1); + } + + // get2, set2 and saveSlot are helpers for accessing elements over two arrays. + function get2(a, b, index) { + return index < a.length ? a[index] : b[index - a.length]; + } + + function set2(a, b, index, value) { + if (index < a.length) { + a[index] = value; + } else { + b[index - a.length] = value; + } + } + + function saveSlot(a, b, index, slot) { + set2(a.table, b.table, index, slot); + + var l = (index == 0 || index == a.lengths.length) ? + 0 : get2(a.lengths, a.lengths, index - 1); + set2(a.lengths, b.lengths, index, l + length(slot)); + } + + // Creates a node or leaf with a given length at their arrays for perfomance. + // Is only used by shuffle. + function createNode(h, length) { + if (length < 0) { length = 0; } + var a = { ctor:"_Array", height:h, table:new Array(length) }; + if (h > 0) { + a.lengths = new Array(length); + } + return a; + } + + // Returns an array of two balanced nodes. + function shuffle(a, b, toRemove) { + var newA = createNode(a.height, Math.min(M, a.table.length + b.table.length - toRemove)); + var newB = createNode(a.height, newA.table.length - (a.table.length + b.table.length - toRemove)); + + // Skip the slots with size M. More precise: copy the slot references + // to the new node + var read = 0; + while (get2(a.table, b.table, read).table.length % M == 0) { + set2(newA.table, newB.table, read, get2(a.table, b.table, read)); + set2(newA.lengths, newB.lengths, read, get2(a.lengths, b.lengths, read)); + read++; + } + + // Pulling items from left to right, caching in a slot before writing + // it into the new nodes. + var write = read; + var slot = new createNode(a.height - 1, 0); + var from = 0; + + // If the current slot is still containing data, then there will be at + // least one more write, so we do not break this loop yet. + while (read - write - (slot.table.length > 0 ? 1 : 0) < toRemove) { + // Find out the max possible items for copying. + var source = get2(a.table, b.table, read); + var to = Math.min(M - slot.table.length, source.table.length) + + // Copy and adjust size table. + slot.table = slot.table.concat(source.table.slice(from, to)); + if (slot.height > 0) { + var len = slot.lengths.length; + for (var i = len; i < len + to - from; i++) { + slot.lengths[i] = length(slot.table[i]); + slot.lengths[i] += (i > 0 ? slot.lengths[i - 1] : 0); + } + } + + from += to; + + // Only proceed to next slots[i] if the current one was + // fully copied. + if (source.table.length <= to) { + read++; from = 0; + } + + // Only create a new slot if the current one is filled up. + if (slot.table.length == M) { + saveSlot(newA, newB, write, slot); + slot = createNode(a.height - 1,0); + write++; + } + } + + // Cleanup after the loop. Copy the last slot into the new nodes. + if (slot.table.length > 0) { + saveSlot(newA, newB, write, slot); + write++; + } + + // Shift the untouched slots to the left + while (read < a.table.length + b.table.length ) { + saveSlot(newA, newB, write, get2(a.table, b.table, read)); + read++; write++; + } + + return [newA, newB]; + } + + // Navigation functions + function botRight(a) { return a.table[a.table.length - 1]; } + function botLeft(a) { return a.table[0]; } + + // Copies a node for updating. Note that you should not use this if + // only updating only one of "table" or "lengths" for performance reasons. + function nodeCopy(a) { + var newA = { ctor:"_Array", height:a.height + , table:a.table.slice() }; + if (a.height > 0) { newA.lengths = a.lengths.slice(); } + return newA; + } + + // Returns how many items are in the tree. + function length(array) { + if (array.height == 0) { + return array.table.length; + } else { + return array.lengths[array.lengths.length - 1]; + } + } + + // Calculates in which slot of "table" the item probably is, then + // find the exact slot via forward searching in "lengths". Returns the index. + function getSlot(i, a) { + var slot = i >> (5 * a.height); + while (a.lengths[slot] <= i) { + slot++; + } + return slot; + } + + // Recursively creates a tree with a given height containing + // only the given item. + function create(item, h) { + if (h == 0) { + return { ctor:"_Array", height:0 + , table:[item] }; + } else { + return { ctor:"_Array", height:h + , table:[create(item, h - 1)] + , lengths:[1] }; + } + } + + // Recursively creates a tree that contains the given tree. + function parentise(tree, h) { + if (h == tree.height) { + return tree; + } else { + return { ctor:"_Array", height:h + , table:[parentise(tree, h - 1)] + , lengths:[length(tree)] }; + } + } + + // Emphasizes blood brotherhood beneath two trees. + function siblise(a, b) { + return { ctor:"_Array", height:a.height + 1 + , table:[a, b] + , lengths:[length(a), length(a) + length(b)] }; + } + + function toJSArray(a) { + var jsArray = new Array(length(a)); + toJSArray_(jsArray, 0, a); + return jsArray; + } + + function toJSArray_(jsArray, i, a) { + for (var t = 0; t < a.table.length; t++) { + if (a.height == 0) { + jsArray[i + t] = a.table[t]; + } else { + var inc = t == 0 ? 0 : a.lengths[t - 1]; + toJSArray_(jsArray, i + inc, a.table[t]); + } + } + } + + function fromJSArray(jsArray) { + if (jsArray.length == 0) { return empty; } + var h = Math.floor(Math.log(jsArray.length) / Math.log(M)); + return fromJSArray_(jsArray, h, 0, jsArray.length); + } + + function fromJSArray_(jsArray, h, from, to) { + if (h == 0) { + return { ctor:"_Array", height:0 + , table:jsArray.slice(from, to) }; + } + + var step = Math.pow(M, h); + var table = new Array(Math.ceil((to - from) / step)); + var lengths = new Array(table.length); + for (var i = 0; i < table.length; i++) { + table[i] = fromJSArray_( jsArray, h - 1, from + (i * step) + , Math.min(from + ((i + 1) * step), to)); + lengths[i] = length(table[i]) + (i > 0 ? lengths[i-1] : 0); + } + return { ctor:"_Array", height:h, table:table, lengths:lengths }; + } + + Elm.Native.Array.values = { + empty:empty, + fromList:fromList, + toList:toList, + initialize:F2(initialize), + append:F2(append), + push:F2(push), + slice:F3(slice), + get:F2(get), + set:F3(set), + map:F2(map), + indexedMap:F2(indexedMap), + foldl:F3(foldl), + foldr:F3(foldr), + length:length, + + toJSArray:toJSArray, + fromJSArray:fromJSArray + }; + + return elm.Native.Array.values = Elm.Native.Array.values; +}
+ src/Native/Basics.js view
@@ -0,0 +1,118 @@+ +Elm.Native.Basics = {}; +Elm.Native.Basics.make = function(elm) { + elm.Native = elm.Native || {}; + elm.Native.Basics = elm.Native.Basics || {}; + if (elm.Native.Basics.values) return elm.Native.Basics.values; + + var Utils = Elm.Native.Utils.make(elm); + + function div(a, b) { + return (a/b)|0; + } + function rem(a, b) { + return a % b; + } + function mod(a, b) { + if (b === 0) { + throw new Error("Cannot perform mod 0. Division by zero error."); + } + var r = a % b; + var m = a === 0 ? 0 : (b > 0 ? (a >= 0 ? r : r+b) : -mod(-a,-b)); + + return m === b ? 0 : m; + } + function logBase(base, n) { + return Math.log(n) / Math.log(base); + } + function negate(n) { + return -n; + } + function abs(n) { + return n < 0 ? -n : n; + } + + function min(a, b) { + return Utils.cmp(a,b) < 0 ? a : b; + } + function max(a, b) { + return Utils.cmp(a,b) > 0 ? a : b; + } + function clamp(lo, hi, n) { + return Utils.cmp(n,lo) < 0 ? lo : Utils.cmp(n,hi) > 0 ? hi : n; + } + + function xor(a, b) { + return a !== b; + } + function not(b) { + return !b; + } + function isInfinite(n) { + return n === Infinity || n === -Infinity + } + + function truncate(n) { + return n|0; + } + + function degrees(d) { + return d * Math.PI / 180; + } + function turns(t) { + return 2 * Math.PI * t; + } + function fromPolar(point) { + var r = point._0; + var t = point._1; + return Utils.Tuple2(r * Math.cos(t), r * Math.sin(t)); + } + function toPolar(point) { + var x = point._0; + var y = point._1; + return Utils.Tuple2(Math.sqrt(x * x + y * y), Math.atan2(y,x)); + } + + var basics = { + div: F2(div), + rem: F2(rem), + mod: F2(mod), + + pi: Math.PI, + e: Math.E, + cos: Math.cos, + sin: Math.sin, + tan: Math.tan, + acos: Math.acos, + asin: Math.asin, + atan: Math.atan, + atan2: F2(Math.atan2), + + degrees: degrees, + turns: turns, + fromPolar: fromPolar, + toPolar: toPolar, + + sqrt: Math.sqrt, + logBase: F2(logBase), + negate: negate, + abs: abs, + min: F2(min), + max: F2(max), + clamp: F3(clamp), + compare: Utils.compare, + + xor: F2(xor), + not: not, + + truncate: truncate, + ceiling: Math.ceil, + floor: Math.floor, + round: Math.round, + toFloat: function(x) { return x; }, + isNaN: isNaN, + isInfinite: isInfinite + }; + + return elm.Native.Basics.values = basics; +};
+ src/Native/Bitwise.js view
@@ -0,0 +1,25 @@+Elm.Native.Bitwise = {}; +Elm.Native.Bitwise.make = function(elm) { + elm.Native = elm.Native || {}; + elm.Native.Bitwise = elm.Native.Bitwise || {}; + if (elm.Native.Bitwise.values) return elm.Native.Bitwise.values; + + function and(a,b) { return a & b; } + function or (a,b) { return a | b; } + function xor(a,b) { return a ^ b; } + function not(a) { return ~a; } + function sll(a,offset) { return a << offset; } + function sra(a,offset) { return a >> offset; } + function srl(a,offset) { return a >>> offset; } + + return elm.Native.Bitwise.values = { + and: F2(and), + or : F2(or ), + xor: F2(xor), + complement: not, + shiftLeft : F2(sll), + shiftRightArithmatic: F2(sra), + shiftRightLogical : F2(srl) + }; + +};
+ src/Native/Char.js view
@@ -0,0 +1,31 @@+Elm.Native.Char = {}; +Elm.Native.Char.make = function(elm) { + elm.Native = elm.Native || {}; + elm.Native.Char = elm.Native.Char || {}; + if (elm.Native.Char.values) return elm.Native.Char.values; + + var Utils = Elm.Native.Utils.make(elm); + + function isBetween(lo,hi) { return function(chr) { + var c = chr.charCodeAt(0); + return lo <= c && c <= hi; + }; + } + var isDigit = isBetween('0'.charCodeAt(0),'9'.charCodeAt(0)); + var chk1 = isBetween('a'.charCodeAt(0),'f'.charCodeAt(0)); + var chk2 = isBetween('A'.charCodeAt(0),'F'.charCodeAt(0)); + + return elm.Native.Char.values = { + fromCode : function(c) { return String.fromCharCode(c); }, + toCode : function(c) { return c.toUpperCase().charCodeAt(0); }, + toUpper : function(c) { return Utils.chr(c.toUpperCase()); }, + toLower : function(c) { return Utils.chr(c.toLowerCase()); }, + toLocaleUpper : function(c) { return Utils.chr(c.toLocaleUpperCase()); }, + toLocaleLower : function(c) { return Utils.chr(c.toLocaleLowerCase()); }, + isLower : isBetween('a'.charCodeAt(0),'z'.charCodeAt(0)), + isUpper : isBetween('A'.charCodeAt(0),'Z'.charCodeAt(0)), + isDigit : isDigit, + isOctDigit : isBetween('0'.charCodeAt(0),'7'.charCodeAt(0)), + isHexDigit : function(c) { return isDigit(c) || chk1(c) || chk2(c); } + }; +};
+ src/Native/Color.js view
@@ -0,0 +1,30 @@+Elm.Native.Color = {}; +Elm.Native.Color.make = function(elm) { + elm.Native = elm.Native || {}; + elm.Native.Color = elm.Native.Color || {}; + if (elm.Native.Color.values) return elm.Native.Color.values; + + function toCss(c) { + var format = ''; + var colors = ''; + if (c.ctor === 'RGBA') { + format = 'rgb'; + colors = c._0 + ', ' + c._1 + ', ' + c._2; + } else { + format = 'hsl'; + colors = (c._0 * 180 / Math.PI) + ', ' + + (c._1 * 100) + '%, ' + + (c._2 * 100) + '%'; + } + if (c._3 === 1) { + return format + '(' + colors + ')'; + } else { + return format + 'a(' + colors + ', ' + c._3 + ')'; + } + } + + return elm.Native.Color.values = { + toCss:toCss + }; + +};
+ src/Native/Date.js view
@@ -0,0 +1,39 @@+Elm.Native.Date = {}; +Elm.Native.Date.make = function(elm) { + elm.Native = elm.Native || {}; + elm.Native.Date = elm.Native.Date || {}; + if (elm.Native.Date.values) return elm.Native.Date.values; + + var Result = Elm.Result.make(elm); + + function dateNow() { + return new window.Date; + } + + function readDate(str) { + var date = new window.Date(str); + return isNaN(date.getTime()) + ? Result.Err("unable to parse '" + str + "' as a date") + : Result.Ok(date); + } + + var dayTable = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + var monthTable = + ["Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + + + return elm.Native.Date.values = { + read : readDate, + year : function(d) { return d.getFullYear(); }, + month : function(d) { return { ctor:monthTable[d.getMonth()] }; }, + day : function(d) { return d.getDate(); }, + hour : function(d) { return d.getHours(); }, + minute : function(d) { return d.getMinutes(); }, + second : function(d) { return d.getSeconds(); }, + toTime : function(d) { return d.getTime(); }, + fromTime: function(t) { return new window.Date(t); }, + dayOfWeek : function(d) { return { ctor:dayTable[d.getDay()] }; } + }; + +};
+ src/Native/Debug.js view
@@ -0,0 +1,65 @@+Elm.Native.Debug = {}; +Elm.Native.Debug.make = function(elm) { + elm.Native = elm.Native || {}; + elm.Native.Debug = elm.Native.Debug || {}; + if (elm.Native.Debug.values) return elm.Native.Debug.values; + if ('values' in Elm.Native.Debug) + return elm.Native.Debug.values = Elm.Native.Debug.values; + + var toString = Elm.Native.Show.make(elm).toString; + var replace = Elm.Native.Utils.make(elm).replace; + + function log(tag, value) { + var msg = tag + ': ' + toString(value); + var process = process || {}; + if (process.stdout) { + process.stdout.write(msg); + } else { + console.log(msg); + } + return value; + } + + function crash(message) { + throw new Error(message); + } + + function tracePath(debugId, form) { + return replace([["debugTracePathId",debugId]], form); + } + + function WatchTracker() { + this.frames = [{}]; + this.clear = function() { + this.watches = {}; + }; + this.pushFrame = function() { + var lastFrame = this.frames[this.frames.length - 1]; + this.frames.push(lastFrame); + } + this.notify = function(tag, value) { + this.frames[this.frames.length - 1][tag] = value; + }; + } + var watchTracker = new WatchTracker(); + + function watch(tag, value) { + watchTracker.notify(tag, value); + return value; + } + + function watchSummary(tag, f, value) { + watchTracker.notify(tag, f(value)); + return value; + } + + Elm.Native.Debug.values = { + crash: crash, + tracePath: F2(tracePath), + log: F2(log), + watch: F2(watch), + watchSummary:F3(watchSummary), + watchTracker: watchTracker + }; + return elm.Native.Debug.values = Elm.Native.Debug.values; +};
+ src/Native/Graphics/Collage.js view
@@ -0,0 +1,415 @@+ +// setup +Elm.Native = Elm.Native || {}; +Elm.Native.Graphics = Elm.Native.Graphics || {}; +Elm.Native.Graphics.Collage = Elm.Native.Graphics.Collage || {}; + +// definition +Elm.Native.Graphics.Collage.make = function(localRuntime) { + 'use strict'; + + // attempt to short-circuit + if ('values' in Elm.Native.Graphics.Collage) { + return Elm.Native.Graphics.Collage.values; + } + + // okay, we cannot short-ciruit, so now we define everything + var Color = Elm.Native.Color.make(localRuntime); + var List = Elm.Native.List.make(localRuntime); + var Transform = Elm.Transform2D.make(localRuntime); + + var Element = Elm.Graphics.Element.make(localRuntime); + var NativeElement = Elm.Native.Graphics.Element.make(localRuntime); + + + function trace(ctx, path) { + var points = List.toArray(path); + var i = points.length - 1; + if (i <= 0) { + return; + } + ctx.moveTo(points[i]._0, points[i]._1); + while (i--) { + ctx.lineTo(points[i]._0, points[i]._1); + } + if (path.closed) { + i = points.length - 1; + ctx.lineTo(points[i]._0, points[i]._1); + } + } + + function line(ctx,style,path) { + (style.dashing.ctor === '[]') + ? trace(ctx, path) + : customLineHelp(ctx, style, path); + ctx.scale(1,-1); + ctx.stroke(); + } + + function customLineHelp(ctx, style, path) { + var points = List.toArray(path); + if (path.closed) { + points.push(points[0]); + } + var pattern = List.toArray(style.dashing); + var i = points.length - 1; + if (i <= 0) { + return; + } + var x0 = points[i]._0, y0 = points[i]._1; + var x1=0, y1=0, dx=0, dy=0, remaining=0, nx=0, ny=0; + var pindex = 0, plen = pattern.length; + var draw = true, segmentLength = pattern[0]; + ctx.moveTo(x0,y0); + while (i--) { + x1 = points[i]._0; y1 = points[i]._1; + dx = x1 - x0; dy = y1 - y0; + remaining = Math.sqrt(dx * dx + dy * dy); + while (segmentLength <= remaining) { + x0 += dx * segmentLength / remaining; + y0 += dy * segmentLength / remaining; + ctx[draw ? 'lineTo' : 'moveTo'](x0, y0); + // update starting position + dx = x1 - x0; dy = y1 - y0; + remaining = Math.sqrt(dx * dx + dy * dy); + // update pattern + draw = !draw; + pindex = (pindex + 1) % plen; + segmentLength = pattern[pindex]; + } + if (remaining > 0) { + ctx[draw ? 'lineTo' : 'moveTo'](x1, y1); + segmentLength -= remaining; + } + x0 = x1; y0 = y1; + } + } + + function drawLine(ctx, style, path) { + ctx.lineWidth = style.width; + + var cap = style.cap.ctor; + ctx.lineCap = cap === 'Flat' + ? 'butt' + : cap === 'Round' + ? 'round' + : 'square'; + + var join = style.join.ctor; + ctx.lineJoin = join === 'Smooth' + ? 'round' + : join === 'Sharp' + ? 'miter' + : 'bevel'; + + ctx.miterLimit = style.join._0 || 10; + ctx.strokeStyle = Color.toCss(style.color); + + return line(ctx, style, path); + } + + function texture(redo, ctx, src) { + var img = new Image(); + img.src = src; + img.onload = redo; + return ctx.createPattern(img, 'repeat'); + } + + function gradient(ctx, grad) { + var g; + var stops = []; + if (grad.ctor === 'Linear') { + var p0 = grad._0, p1 = grad._1; + g = ctx.createLinearGradient(p0._0, -p0._1, p1._0, -p1._1); + stops = List.toArray(grad._2); + } else { + var p0 = grad._0, p2 = grad._2; + g = ctx.createRadialGradient(p0._0, -p0._1, grad._1, p2._0, -p2._1, grad._3); + stops = List.toArray(grad._4); + } + var len = stops.length; + for (var i = 0; i < len; ++i) { + var stop = stops[i]; + g.addColorStop(stop._0, Color.toCss(stop._1)); + } + return g; + } + + function drawShape(redo, ctx, style, path) { + trace(ctx, path); + var sty = style.ctor; + ctx.fillStyle = sty === 'Solid' + ? Color.toCss(style._0) + : sty === 'Texture' + ? texture(redo, ctx, style._0) + : gradient(ctx, style._0); + + ctx.scale(1,-1); + ctx.fill(); + } + + function drawImage(redo, ctx, form) { + var img = new Image(); + img.onload = redo; + img.src = form._3; + var w = form._0, + h = form._1, + pos = form._2, + srcX = pos._0, + srcY = pos._1, + srcW = w, + srcH = h, + destX = -w/2, + destY = -h/2, + destW = w, + destH = h; + + ctx.scale(1,-1); + ctx.drawImage(img, srcX, srcY, srcW, srcH, destX, destY, destW, destH); + } + + function renderForm(redo, ctx, form) { + ctx.save(); + var x = form.x, y = form.y, theta = form.theta, scale = form.scale; + if (x !== 0 || y !== 0) ctx.translate(x, y); + if (theta !== 0) ctx.rotate(theta); + if (scale !== 1) ctx.scale(scale,scale); + if (form.alpha !== 1) ctx.globalAlpha = ctx.globalAlpha * form.alpha; + ctx.beginPath(); + var f = form.form; + switch(f.ctor) { + case 'FPath' : drawLine(ctx, f._0, f._1); break; + case 'FImage': drawImage(redo, ctx, f); break; + case 'FShape': + if (f._0.ctor === 'Line') { + f._1.closed = true; + drawLine(ctx, f._0._0, f._1); + } else { + drawShape(redo, ctx, f._0._0, f._1); + } + break; + } + ctx.restore(); + } + + function formToMatrix(form) { + var scale = form.scale; + var matrix = A6( Transform.matrix, scale, 0, 0, scale, form.x, form.y ); + + var theta = form.theta + if (theta !== 0) { + matrix = A2( Transform.multiply, matrix, Transform.rotation(theta) ); + } + + return matrix; + } + + function str(n) { + if (n < 0.00001 && n > -0.00001) return 0; + return n; + } + + function makeTransform(w, h, form, matrices) { + var props = form.form._0.props; + var m = A6( Transform.matrix, 1, 0, 0, -1, + (w - props.width ) / 2, + (h - props.height) / 2 ); + var len = matrices.length; + for (var i = 0; i < len; ++i) { + m = A2( Transform.multiply, m, matrices[i] ); + } + m = A2( Transform.multiply, m, formToMatrix(form) ); + + return 'matrix(' + str( m[0]) + ', ' + str( m[3]) + ', ' + + str(-m[1]) + ', ' + str(-m[4]) + ', ' + + str( m[2]) + ', ' + str( m[5]) + ')'; + } + + function stepperHelp(list) { + var arr = List.toArray(list); + var i = 0; + function peekNext() { + return i < arr.length ? arr[i].form.ctor : ''; + } + // assumes that there is a next element + function next() { + var out = arr[i]; + ++i; + return out; + } + return { + peekNext:peekNext, + next:next + }; + } + + function formStepper(forms) { + var ps = [stepperHelp(forms)]; + var matrices = []; + var alphas = []; + function peekNext() { + var len = ps.length; + var formType = ''; + for (var i = 0; i < len; ++i ) { + if (formType = ps[i].peekNext()) return formType; + } + return ''; + } + // assumes that there is a next element + function next(ctx) { + while (!ps[0].peekNext()) { + ps.shift(); + matrices.pop(); + alphas.shift(); + if (ctx) { ctx.restore(); } + } + var out = ps[0].next(); + var f = out.form; + if (f.ctor === 'FGroup') { + ps.unshift(stepperHelp(f._1)); + var m = A2(Transform.multiply, f._0, formToMatrix(out)); + ctx.save(); + ctx.transform(m[0], m[3], m[1], m[4], m[2], m[5]); + matrices.push(m); + + var alpha = (alphas[0] || 1) * out.alpha; + alphas.unshift(alpha); + ctx.globalAlpha = alpha; + } + return out; + } + function transforms() { return matrices; } + function alpha() { return alphas[0] || 1; } + return { + peekNext:peekNext, + next:next, + transforms:transforms, + alpha:alpha + }; + } + + function makeCanvas(w,h) { + var canvas = NativeElement.createNode('canvas'); + canvas.style.width = w + 'px'; + canvas.style.height = h + 'px'; + canvas.style.display = "block"; + canvas.style.position = "absolute"; + canvas.width = w; + canvas.height = h; + return canvas; + } + + function render(model) { + var div = NativeElement.createNode('div'); + div.style.overflow = 'hidden'; + div.style.position = 'relative'; + update(div, model, model); + return div; + } + + function nodeStepper(w,h,div) { + var kids = div.childNodes; + var i = 0; + function transform(transforms, ctx) { + ctx.translate(w/2, h/2); + ctx.scale(1,-1); + var len = transforms.length; + for (var i = 0; i < len; ++i) { + var m = transforms[i]; + ctx.save(); + ctx.transform(m[0], m[3], m[1], m[4], m[2], m[5]); + } + return ctx; + } + function nextContext(transforms) { + while (i < kids.length) { + var node = kids[i]; + if (node.getContext) { + node.width = w; + node.height = h; + node.style.width = w + 'px'; + node.style.height = h + 'px'; + ++i; + return transform(transforms, node.getContext('2d')); + } + div.removeChild(node); + } + var canvas = makeCanvas(w,h); + div.appendChild(canvas); + // we have added a new node, so we must step our position + ++i; + return transform(transforms, canvas.getContext('2d')); + } + function addElement(matrices, alpha, form) { + var kid = kids[i]; + var elem = form.form._0; + + var node = (!kid || kid.getContext) + ? NativeElement.render(elem) + : NativeElement.update(kid, kid.oldElement, elem); + + node.style.position = 'absolute'; + node.style.opacity = alpha * form.alpha * elem.props.opacity; + NativeElement.addTransform(node.style, makeTransform(w, h, form, matrices)); + node.oldElement = elem; + ++i; + if (!kid) { + div.appendChild(node); + } else if (kid.getContext) { + div.insertBefore(node, kid); + } + } + function clearRest() { + while (i < kids.length) { + div.removeChild(kids[i]); + } + } + return { nextContext:nextContext, addElement:addElement, clearRest:clearRest }; + } + + + function update(div, _, model) { + var w = model.w; + var h = model.h; + + var forms = formStepper(model.forms); + var nodes = nodeStepper(w,h,div); + var ctx = null; + var formType = ''; + + while (formType = forms.peekNext()) { + // make sure we have context if we need it + if (ctx === null && formType !== 'FElement') { + ctx = nodes.nextContext(forms.transforms()); + ctx.globalAlpha = forms.alpha(); + } + + var form = forms.next(ctx); + // if it is FGroup, all updates are made within formStepper when next is called. + if (formType === 'FElement') { + // update or insert an element, get a new context + nodes.addElement(forms.transforms(), forms.alpha(), form); + ctx = null; + } else if (formType !== 'FGroup') { + renderForm(function() { update(div, model, model); }, ctx, form); + } + } + nodes.clearRest(); + return div; + } + + + function collage(w,h,forms) { + return A3(Element.newElement, w, h, { + ctor: 'Custom', + type: 'Collage', + render: render, + update: update, + model: {w:w, h:h, forms:forms} + }); + } + + return Elm.Native.Graphics.Collage.values = { + collage:F3(collage) + }; +};
+ src/Native/Graphics/Element.js view
@@ -0,0 +1,523 @@+ +// setup +Elm.Native = Elm.Native || {}; +Elm.Native.Graphics = Elm.Native.Graphics || {}; +Elm.Native.Graphics.Element = Elm.Native.Graphics.Element || {}; + +// definition +Elm.Native.Graphics.Element.make = function(localRuntime) { + 'use strict'; + + // attempt to short-circuit + if ('values' in Elm.Native.Graphics.Element) { + return Elm.Native.Graphics.Element.values; + } + + var Color = Elm.Native.Color.make(localRuntime); + var List = Elm.Native.List.make(localRuntime); + var Utils = Elm.Native.Utils.make(localRuntime); + + + function createNode(elementType) { + var node = document.createElement(elementType); + node.style.padding = "0"; + node.style.margin = "0"; + return node; + } + + function setProps(elem, node) { + var props = elem.props; + + var element = elem.element; + var width = props.width - (element.adjustWidth || 0); + var height = props.height - (element.adjustHeight || 0); + node.style.width = (width |0) + 'px'; + node.style.height = (height|0) + 'px'; + + if (props.opacity !== 1) { + node.style.opacity = props.opacity; + } + + if (props.color.ctor === 'Just') { + node.style.backgroundColor = Color.toCss(props.color._0); + } + + if (props.tag !== '') { + node.id = props.tag; + } + + if (props.hover.ctor !== '_Tuple0') { + addHover(node, props.hover); + } + + if (props.click.ctor !== '_Tuple0') { + addClick(node, props.click); + } + + if (props.href !== '') { + var anchor = createNode('a'); + anchor.href = props.href; + anchor.style.display = 'block'; + anchor.style.pointerEvents = 'auto'; + anchor.appendChild(node); + node = anchor; + } + + return node; + } + + function addClick(e, handler) { + e.style.pointerEvents = 'auto'; + e.elm_click_handler = handler; + function trigger(ev) { + e.elm_click_handler(Utils.Tuple0); + ev.stopPropagation(); + } + e.elm_click_trigger = trigger; + e.addEventListener('click', trigger); + } + + function removeClick(e, handler) { + if (e.elm_click_trigger) { + e.removeEventListener('click', e.elm_click_trigger); + e.elm_click_trigger = null; + e.elm_click_handler = null; + } + } + + function addHover(e, handler) { + e.style.pointerEvents = 'auto'; + e.elm_hover_handler = handler; + e.elm_hover_count = 0; + + function over(evt) { + if (e.elm_hover_count++ > 0) return; + e.elm_hover_handler(true); + evt.stopPropagation(); + } + function out(evt) { + if (e.contains(evt.toElement || evt.relatedTarget)) return; + e.elm_hover_count = 0; + e.elm_hover_handler(false); + evt.stopPropagation(); + } + e.elm_hover_over = over; + e.elm_hover_out = out; + e.addEventListener('mouseover', over); + e.addEventListener('mouseout', out); + } + + function removeHover(e) { + e.elm_hover_handler = null; + if (e.elm_hover_over) { + e.removeEventListener('mouseover', e.elm_hover_over); + e.elm_hover_over = null; + } + if (e.elm_hover_out) { + e.removeEventListener('mouseout', e.elm_hover_out); + e.elm_hover_out = null; + } + } + + function image(props, img) { + switch (img._0.ctor) { + case 'Plain': return plainImage(img._3); + case 'Fitted': return fittedImage(props.width, props.height, img._3); + case 'Cropped': return croppedImage(img,props.width,props.height,img._3); + case 'Tiled': return tiledImage(img._3); + } + } + + function plainImage(src) { + var img = createNode('img'); + img.src = src; + img.name = src; + img.style.display = "block"; + return img; + } + + function tiledImage(src) { + var div = createNode('div'); + div.style.backgroundImage = 'url(' + src + ')'; + return div; + } + + function fittedImage(w, h, src) { + var div = createNode('div'); + div.style.background = 'url(' + src + ') no-repeat center'; + div.style.webkitBackgroundSize = 'cover'; + div.style.MozBackgroundSize = 'cover'; + div.style.OBackgroundSize = 'cover'; + div.style.backgroundSize = 'cover'; + return div; + } + + function croppedImage(elem, w, h, src) { + var pos = elem._0._0; + var e = createNode('div'); + e.style.overflow = "hidden"; + + var img = createNode('img'); + img.onload = function() { + var sw = w / elem._1, sh = h / elem._2; + img.style.width = ((this.width * sw)|0) + 'px'; + img.style.height = ((this.height * sh)|0) + 'px'; + img.style.marginLeft = ((- pos._0 * sw)|0) + 'px'; + img.style.marginTop = ((- pos._1 * sh)|0) + 'px'; + }; + img.src = src; + img.name = src; + e.appendChild(img); + return e; + } + + function goOut(node) { + node.style.position = 'absolute'; + return node; + } + function goDown(node) { + return node; + } + function goRight(node) { + node.style.styleFloat = 'left'; + node.style.cssFloat = 'left'; + return node; + } + + var directionTable = { + DUp : goDown, + DDown : goDown, + DLeft : goRight, + DRight : goRight, + DIn : goOut, + DOut : goOut + }; + function needsReversal(dir) { + return dir == 'DUp' || dir == 'DLeft' || dir == 'DIn'; + } + + function flow(dir,elist) { + var array = List.toArray(elist); + var container = createNode('div'); + var goDir = directionTable[dir]; + if (goDir == goOut) { + container.style.pointerEvents = 'none'; + } + if (needsReversal(dir)) { + array.reverse(); + } + var len = array.length; + for (var i = 0; i < len; ++i) { + container.appendChild(goDir(render(array[i]))); + } + return container; + } + + function toPos(pos) { + switch(pos.ctor) { + case "Absolute": return pos._0 + "px"; + case "Relative": return (pos._0 * 100) + "%"; + } + } + + // must clear right, left, top, bottom, and transform + // before calling this function + function setPos(pos,elem,e) { + var element = elem.element; + var props = elem.props; + var w = props.width + (element.adjustWidth ? element.adjustWidth : 0); + var h = props.height + (element.adjustHeight ? element.adjustHeight : 0); + + e.style.position = 'absolute'; + e.style.margin = 'auto'; + var transform = ''; + switch(pos.horizontal.ctor) { + case 'P': e.style.right = toPos(pos.x); e.style.removeProperty('left'); break; + case 'Z': transform = 'translateX(' + ((-w/2)|0) + 'px) '; + case 'N': e.style.left = toPos(pos.x); e.style.removeProperty('right'); break; + } + switch(pos.vertical.ctor) { + case 'N': e.style.bottom = toPos(pos.y); e.style.removeProperty('top'); break; + case 'Z': transform += 'translateY(' + ((-h/2)|0) + 'px)'; + case 'P': e.style.top = toPos(pos.y); e.style.removeProperty('bottom'); break; + } + if (transform !== '') { + addTransform(e.style, transform); + } + return e; + } + + function addTransform(style, transform) { + style.transform = transform; + style.msTransform = transform; + style.MozTransform = transform; + style.webkitTransform = transform; + style.OTransform = transform; + } + + function container(pos,elem) { + var e = render(elem); + setPos(pos, elem, e); + var div = createNode('div'); + div.style.position = 'relative'; + div.style.overflow = 'hidden'; + div.appendChild(e); + return div; + } + + function rawHtml(elem) { + var html = elem.html; + var guid = elem.guid; + var align = elem.align; + + var div = createNode('div'); + div.innerHTML = html; + div.style.visibility = "hidden"; + if (align) { + div.style.textAlign = align; + } + div.style.visibility = 'visible'; + div.style.pointerEvents = 'auto'; + return div; + } + + function render(elem) { + return setProps(elem, makeElement(elem)); + } + function makeElement(e) { + var elem = e.element; + switch(elem.ctor) { + case 'Image': return image(e.props, elem); + case 'Flow': return flow(elem._0.ctor, elem._1); + case 'Container': return container(elem._0, elem._1); + case 'Spacer': return createNode('div'); + case 'RawHtml': return rawHtml(elem); + case 'Custom': return elem.render(elem.model); + } + } + + function updateAndReplace(node, curr, next) { + var newNode = update(node, curr, next); + if (newNode !== node) { + node.parentNode.replaceChild(newNode, node); + } + return newNode; + } + + function update(node, curr, next) { + var rootNode = node; + if (node.tagName === 'A') { + node = node.firstChild; + } + if (curr.props.id === next.props.id) { + updateProps(node, curr, next); + return rootNode; + } + if (curr.element.ctor !== next.element.ctor) { + return render(next); + } + var nextE = next.element; + var currE = curr.element; + switch(nextE.ctor) { + case "Spacer": + updateProps(node, curr, next); + return rootNode; + + case "RawHtml": + // only markdown blocks have guids, so this must be a text block + if (nextE.guid === null) { + if(currE.html.valueOf() !== nextE.html.valueOf()) { + node.innerHTML = nextE.html; + } + updateProps(node, curr, next); + return rootNode; + } + if (nextE.guid !== currE.guid) { + return render(next); + } + updateProps(node, curr, next); + return rootNode; + + case "Image": + if (nextE._0.ctor === 'Plain') { + if (nextE._3 !== currE._3) { + node.src = nextE._3; + } + } else if (!Utils.eq(nextE,currE) || + next.props.width !== curr.props.width || + next.props.height !== curr.props.height) { + return render(next); + } + updateProps(node, curr, next); + return rootNode; + + case "Flow": + var arr = List.toArray(nextE._1); + for (var i = arr.length; i--; ) { + arr[i] = arr[i].element.ctor; + } + if (nextE._0.ctor !== currE._0.ctor) { + return render(next); + } + var nexts = List.toArray(nextE._1); + var kids = node.childNodes; + if (nexts.length !== kids.length) { + return render(next); + } + var currs = List.toArray(currE._1); + var dir = nextE._0.ctor; + var goDir = directionTable[dir]; + var toReverse = needsReversal(dir); + var len = kids.length; + for (var i = len; i-- ;) { + var subNode = kids[toReverse ? len - i - 1 : i]; + goDir(updateAndReplace(subNode, currs[i], nexts[i])); + } + updateProps(node, curr, next); + return rootNode; + + case "Container": + var subNode = node.firstChild; + var newSubNode = updateAndReplace(subNode, currE._1, nextE._1); + setPos(nextE._0, nextE._1, newSubNode); + updateProps(node, curr, next); + return rootNode; + + case "Custom": + if (currE.type === nextE.type) { + var updatedNode = nextE.update(node, currE.model, nextE.model); + updateProps(updatedNode, curr, next); + return updatedNode; + } else { + return render(next); + } + } + } + + function updateProps(node, curr, next) { + var nextProps = next.props; + var currProps = curr.props; + + var element = next.element; + var width = nextProps.width - (element.adjustWidth || 0); + var height = nextProps.height - (element.adjustHeight || 0); + if (width !== currProps.width) { + node.style.width = (width|0) + 'px'; + } + if (height !== currProps.height) { + node.style.height = (height|0) + 'px'; + } + + if (nextProps.opacity !== currProps.opacity) { + node.style.opacity = nextProps.opacity; + } + + var nextColor = nextProps.color.ctor === 'Just' + ? Color.toCss(nextProps.color._0) + : ''; + if (node.style.backgroundColor !== nextColor) { + node.style.backgroundColor = nextColor; + } + + if (nextProps.tag !== currProps.tag) { + node.id = nextProps.tag; + } + + if (nextProps.href !== currProps.href) { + if (currProps.href === '') { + // add a surrounding href + var anchor = createNode('a'); + anchor.href = nextProps.href; + anchor.style.display = 'block'; + anchor.style.pointerEvents = 'auto'; + + node.parentNode.replaceChild(anchor, node); + anchor.appendChild(node); + } else if (nextProps.href === '') { + // remove the surrounding href + var anchor = node.parentNode; + anchor.parentNode.replaceChild(node, anchor); + } else { + // just update the link + node.parentNode.href = nextProps.href; + } + } + + // update click and hover handlers + var removed = false; + + // update hover handlers + if (currProps.hover.ctor === '_Tuple0') { + if (nextProps.hover.ctor !== '_Tuple0') { + addHover(node, nextProps.hover); + } + } + else { + if (nextProps.hover.ctor === '_Tuple0') { + removed = true; + removeHover(node); + } + else { + node.elm_hover_handler = nextProps.hover; + } + } + + // update click handlers + if (currProps.click.ctor === '_Tuple0') { + if (nextProps.click.ctor !== '_Tuple0') { + addClick(node, nextProps.click); + } + } + else { + if (nextProps.click.ctor === '_Tuple0') { + removed = true; + removeClick(node); + } else { + node.elm_click_handler = nextProps.click; + } + } + + // stop capturing clicks if + if (removed + && nextProps.hover.ctor === '_Tuple0' + && nextProps.click.ctor === '_Tuple0') + { + node.style.pointerEvents = 'none'; + } + } + + + function htmlHeight(width, rawHtml) { + // create dummy node + var temp = document.createElement('div'); + temp.innerHTML = rawHtml.html; + if (width > 0) { + temp.style.width = width + "px"; + } + temp.style.visibility = "hidden"; + temp.style.styleFloat = "left"; + temp.style.cssFloat = "left"; + + document.body.appendChild(temp); + + // get dimensions + var style = window.getComputedStyle(temp, null); + var w = Math.ceil(style.getPropertyValue("width").slice(0,-2) - 0); + var h = Math.ceil(style.getPropertyValue("height").slice(0,-2) - 0); + document.body.removeChild(temp); + return Utils.Tuple2(w,h); + } + + + return Elm.Native.Graphics.Element.values = { + render: render, + update: update, + updateAndReplace: updateAndReplace, + + createNode: createNode, + addTransform: addTransform, + htmlHeight: F2(htmlHeight), + guid: Utils.guid + }; + +};
+ src/Native/Graphics/Input.js view
@@ -0,0 +1,415 @@+// setup +Elm.Native = Elm.Native || {}; +Elm.Native.Graphics = Elm.Native.Graphics || {}; +Elm.Native.Graphics.Input = Elm.Native.Graphics.Input || {}; + +// definition +Elm.Native.Graphics.Input.make = function(localRuntime) { + 'use strict'; + + // attempt to short-circuit + if ('values' in Elm.Native.Graphics.Input) { + return Elm.Native.Graphics.Input.values; + } + + var Color = Elm.Native.Color.make(localRuntime); + var List = Elm.Native.List.make(localRuntime); + var Text = Elm.Native.Text.make(localRuntime); + var Utils = Elm.Native.Utils.make(localRuntime); + + var Element = Elm.Graphics.Element.make(localRuntime); + var NativeElement = Elm.Native.Graphics.Element.make(localRuntime); + + + function renderDropDown(model) { + var drop = NativeElement.createNode('select'); + drop.style.border = '0 solid'; + drop.style.pointerEvents = 'auto'; + drop.style.display = 'block'; + + drop.elm_values = List.toArray(model.values); + drop.elm_handler = model.handler; + var values = drop.elm_values; + + for (var i = 0; i < values.length; ++i) { + var option = NativeElement.createNode('option'); + var name = values[i]._0; + option.value = name; + option.innerHTML = name; + drop.appendChild(option); + } + drop.addEventListener('change', function() { + drop.elm_handler(drop.elm_values[drop.selectedIndex]._1)(); + }); + + return drop; + } + + function updateDropDown(node, oldModel, newModel) { + node.elm_values = List.toArray(newModel.values); + node.elm_handler = newModel.handler; + + var values = node.elm_values; + var kids = node.childNodes; + var kidsLength = kids.length; + + var i = 0; + for (; i < kidsLength && i < values.length; ++i) { + var option = kids[i]; + var name = values[i]._0; + option.value = name; + option.innerHTML = name; + } + for (; i < kidsLength; ++i) { + node.removeChild(node.lastChild); + } + for (; i < values.length; ++i) { + var option = NativeElement.createNode('option'); + var name = values[i]._0; + option.value = name; + option.innerHTML = name; + node.appendChild(option); + } + return node; + } + + function dropDown(handler, values) { + return A3(Element.newElement, 100, 24, { + ctor: 'Custom', + type: 'DropDown', + render: renderDropDown, + update: updateDropDown, + model: { + values: values, + handler: handler + } + }); + } + + function renderButton(model) { + var node = NativeElement.createNode('button'); + node.style.display = 'block'; + node.style.pointerEvents = 'auto'; + node.elm_message = model.message; + function click() { + node.elm_message(); + } + node.addEventListener('click', click); + node.innerHTML = model.text; + return node; + } + + function updateButton(node, oldModel, newModel) { + node.elm_message = newModel.message; + var txt = newModel.text; + if (oldModel.text !== txt) { + node.innerHTML = txt; + } + return node; + } + + function button(message, text) { + return A3(Element.newElement, 100, 40, { + ctor: 'Custom', + type: 'Button', + render: renderButton, + update: updateButton, + model: { + message: message, + text:text + } + }); + } + + function renderCustomButton(model) { + var btn = NativeElement.createNode('div'); + btn.style.pointerEvents = 'auto'; + btn.elm_message = model.message; + + btn.elm_up = NativeElement.render(model.up); + btn.elm_hover = NativeElement.render(model.hover); + btn.elm_down = NativeElement.render(model.down); + + btn.elm_up.style.display = 'block'; + btn.elm_hover.style.display = 'none'; + btn.elm_down.style.display = 'none'; + + btn.appendChild(btn.elm_up); + btn.appendChild(btn.elm_hover); + btn.appendChild(btn.elm_down); + + function swap(visibleNode, hiddenNode1, hiddenNode2) { + visibleNode.style.display = 'block'; + hiddenNode1.style.display = 'none'; + hiddenNode2.style.display = 'none'; + } + + var overCount = 0; + function over(e) { + if (overCount++ > 0) return; + swap(btn.elm_hover, btn.elm_down, btn.elm_up); + } + function out(e) { + if (btn.contains(e.toElement || e.relatedTarget)) return; + overCount = 0; + swap(btn.elm_up, btn.elm_down, btn.elm_hover); + } + function up() { + swap(btn.elm_hover, btn.elm_down, btn.elm_up); + btn.elm_message(); + } + function down() { + swap(btn.elm_down, btn.elm_hover, btn.elm_up); + } + + btn.addEventListener('mouseover', over); + btn.addEventListener('mouseout' , out); + btn.addEventListener('mousedown', down); + btn.addEventListener('mouseup' , up); + + return btn; + } + + function updateCustomButton(node, oldModel, newModel) { + node.elm_message = newModel.message; + + var kids = node.childNodes; + var styleUp = kids[0].style.display; + var styleHover = kids[1].style.display; + var styleDown = kids[2].style.display; + + NativeElement.updateAndReplace(kids[0], oldModel.up, newModel.up); + NativeElement.updateAndReplace(kids[1], oldModel.hover, newModel.hover); + NativeElement.updateAndReplace(kids[2], oldModel.down, newModel.down); + + var kids = node.childNodes; + kids[0].style.display = styleUp; + kids[1].style.display = styleHover; + kids[2].style.display = styleDown; + + return node; + } + + function max3(a,b,c) { + var ab = a > b ? a : b; + return ab > c ? ab : c; + } + + function customButton(message, up, hover, down) { + return A3(Element.newElement, + max3(up.props.width, hover.props.width, down.props.width), + max3(up.props.height, hover.props.height, down.props.height), + { ctor: 'Custom', + type: 'CustomButton', + render: renderCustomButton, + update: updateCustomButton, + model: { + message: message, + up: up, + hover: hover, + down: down + } + }); + } + + function renderCheckbox(model) { + var node = NativeElement.createNode('input'); + node.type = 'checkbox'; + node.checked = model.checked; + node.style.display = 'block'; + node.style.pointerEvents = 'auto'; + node.elm_handler = model.handler; + function change() { + node.elm_handler(node.checked)(); + } + node.addEventListener('change', change); + return node; + } + + function updateCheckbox(node, oldModel, newModel) { + node.elm_handler = newModel.handler; + node.checked = newModel.checked; + return node; + } + + function checkbox(handler, checked) { + return A3(Element.newElement, 13, 13, { + ctor: 'Custom', + type: 'CheckBox', + render: renderCheckbox, + update: updateCheckbox, + model: { handler:handler, checked:checked } + }); + } + + function setRange(node, start, end, dir) { + if (node.parentNode) { + node.setSelectionRange(start, end, dir); + } else { + setTimeout(function(){node.setSelectionRange(start, end, dir);}, 0); + } + } + + function updateIfNeeded(css, attribute, latestAttribute) { + if (css[attribute] !== latestAttribute) { + css[attribute] = latestAttribute; + } + } + function cssDimensions(dimensions) { + return dimensions.top + 'px ' + + dimensions.right + 'px ' + + dimensions.bottom + 'px ' + + dimensions.left + 'px'; + } + function updateFieldStyle(css, style) { + updateIfNeeded(css, 'padding', cssDimensions(style.padding)); + + var outline = style.outline; + updateIfNeeded(css, 'border-width', cssDimensions(outline.width)); + updateIfNeeded(css, 'border-color', Color.toCss(outline.color)); + updateIfNeeded(css, 'border-radius', outline.radius + 'px'); + + var highlight = style.highlight; + if (highlight.width === 0) { + css.outline = 'none'; + } else { + updateIfNeeded(css, 'outline-width', highlight.width + 'px'); + updateIfNeeded(css, 'outline-color', Color.toCss(highlight.color)); + } + + var textStyle = style.style; + updateIfNeeded(css, 'color', Color.toCss(textStyle.color)); + if (textStyle.typeface.ctor !== '[]') { + updateIfNeeded(css, 'font-family', Text.toTypefaces(textStyle.typeface)); + } + if (textStyle.height.ctor !== "Nothing") { + updateIfNeeded(css, 'font-size', textStyle.height._0 + 'px'); + } + updateIfNeeded(css, 'font-weight', textStyle.bold ? 'bold' : 'normal'); + updateIfNeeded(css, 'font-style', textStyle.italic ? 'italic' : 'normal'); + if (textStyle.line.ctor !== 'Nothing') { + updateIfNeeded(css, 'text-decoration', Text.toLine(textStyle.line._0)); + } + } + + function renderField(model) { + var field = NativeElement.createNode('input'); + updateFieldStyle(field.style, model.style); + field.style.borderStyle = 'solid'; + field.style.pointerEvents = 'auto'; + + field.type = model.type; + field.placeholder = model.placeHolder; + field.value = model.content.string; + + field.elm_handler = model.handler; + field.elm_old_value = field.value; + + function inputUpdate(event) { + var curr = field.elm_old_value; + var next = field.value; + if (curr === next) { + return; + } + + var direction = field.selectionDirection === 'forward' ? 'Forward' : 'Backward'; + var start = field.selectionStart; + var end = field.selectionEnd; + field.value = field.elm_old_value; + + field.elm_handler({ + _:{}, + string: next, + selection: { + _:{}, + start: start, + end: end, + direction: { ctor: direction } + } + })(); + } + + field.addEventListener('input', inputUpdate); + field.addEventListener('focus', function() { + field.elm_hasFocus = true; + }); + field.addEventListener('blur', function() { + field.elm_hasFocus = false; + }); + + return field; + } + + function updateField(field, oldModel, newModel) { + if (oldModel.style !== newModel.style) { + updateFieldStyle(field.style, newModel.style); + } + field.elm_handler = newModel.handler; + + field.type = newModel.type; + field.placeholder = newModel.placeHolder; + var value = newModel.content.string; + field.value = value; + field.elm_old_value = value; + if (field.elm_hasFocus) { + var selection = newModel.content.selection; + var direction = selection.direction.ctor === 'Forward' ? 'forward' : 'backward'; + setRange(field, selection.start, selection.end, direction); + } + return field; + } + + function mkField(type) { + function field(style, handler, placeHolder, content) { + var padding = style.padding; + var outline = style.outline.width; + var adjustWidth = padding.left + padding.right + outline.left + outline.right; + var adjustHeight = padding.top + padding.bottom + outline.top + outline.bottom; + return A3(Element.newElement, 200, 30, { + ctor: 'Custom', + type: type + 'Field', + adjustWidth: adjustWidth, + adjustHeight: adjustHeight, + render: renderField, + update: updateField, + model: { + handler:handler, + placeHolder:placeHolder, + content:content, + style:style, + type:type + } + }); + } + return F4(field); + } + + function hoverable(handler, elem) { + function onHover(bool) { + handler(bool)(); + } + var props = Utils.replace([['hover',onHover]], elem.props); + return { props:props, element:elem.element }; + } + + function clickable(message, elem) { + function onClick() { + message(); + } + var props = Utils.replace([['click',onClick]], elem.props); + return { props:props, element:elem.element }; + } + + return Elm.Native.Graphics.Input.values = { + button: F2(button), + customButton: F4(customButton), + checkbox: F2(checkbox), + dropDown: F2(dropDown), + field: mkField('text'), + email: mkField('email'), + password: mkField('password'), + hoverable: F2(hoverable), + clickable: F2(clickable) + }; + +};
+ src/Native/Http.js view
@@ -0,0 +1,63 @@+Elm.Native.Http = {}; +Elm.Native.Http.make = function(elm) { + + elm.Native = elm.Native || {}; + elm.Native.Http = elm.Native.Http || {}; + if (elm.Native.Http.values) return elm.Native.Http.values; + + var List = Elm.List.make(elm); + var Signal = Elm.Signal.make(elm); + + function registerReq(queue,responses) { + return function(req) { + if (req.url.length > 0) { + sendReq(queue,responses,req); + } + }; + } + + function updateQueue(queue,responses) { + if (queue.length > 0) { + elm.notify(responses.id, queue[0].value); + if (queue[0].value.ctor !== 'Waiting') { + queue.shift(); + setTimeout(function() { updateQueue(queue,responses); }, 0); + } + } + } + + function sendReq(queue,responses,req) { + var response = { value: { ctor:'Waiting' } }; + queue.push(response); + + var request = (window.ActiveXObject + ? new ActiveXObject("Microsoft.XMLHTTP") + : new XMLHttpRequest()); + + request.onreadystatechange = function(e) { + if (request.readyState === 4) { + response.value = (request.status >= 200 && request.status < 300 ? + { ctor:'Success', _0:request.responseText } : + { ctor:'Failure', _0:request.status, _1:request.statusText }); + setTimeout(function() { updateQueue(queue,responses); }, 0); + } + }; + request.open(req.verb, req.url, true); + function setHeader(pair) { + request.setRequestHeader( pair._0, pair._1 ); + } + A2( List.map, setHeader, req.headers ); + request.send(req.body); + } + + function send(requests) { + var responses = Signal.constant(elm.Http.values.Waiting); + var sender = A2( Signal.map, registerReq([],responses), requests ); + function f(x) { return function(y) { return x; } } + return A3( Signal.map2, f, responses, sender ); + } + + return elm.Native.Http.values = { + send:send + }; +};
+ src/Native/Json.js view
@@ -0,0 +1,485 @@+Elm.Native.Json = {}; +Elm.Native.Json.make = function(localRuntime) { + localRuntime.Native = localRuntime.Native || {}; + localRuntime.Native.Json = localRuntime.Native.Json || {}; + if (localRuntime.Native.Json.values) { + return localRuntime.Native.Json.values; + } + + var ElmArray = Elm.Native.Array.make(localRuntime); + var List = Elm.Native.List.make(localRuntime); + var Maybe = Elm.Maybe.make(localRuntime); + var Result = Elm.Result.make(localRuntime); + var Utils = Elm.Native.Utils.make(localRuntime); + + + function crash(expected, actual) { + throw new Error( + 'expecting ' + expected + ' but got ' + JSON.stringify(actual) + ); + } + + + // PRIMITIVE VALUES + + function decodeNull(successValue) { + return function(value) { + if (value === null) { + return successValue; + } + crash('null', value); + }; + } + + + function decodeString(value) { + if (typeof value === 'string' || value instanceof String) { + return value; + } + crash('a String', value); + } + + + function decodeFloat(value) { + if (typeof value === 'number') { + return value; + } + crash('a Float', value); + } + + + function decodeInt(value) { + if (typeof value === 'number' && (value|0) === value) { + return value; + } + crash('an Int', value); + } + + + function decodeBool(value) { + if (typeof value === 'boolean') { + return value; + } + crash('a Bool', value); + } + + + // ARRAY + + function decodeArray(decoder) { + return function(value) { + if (value instanceof Array) { + var len = value.length; + var array = new Array(len); + for (var i = len; i-- ; ) { + array[i] = decoder(value[i]); + } + return ElmArray.fromJSArray(array); + } + crash('an Array', value); + }; + } + + + // LIST + + function decodeList(decoder) { + return function(value) { + if (value instanceof Array) { + var len = value.length; + var list = List.Nil; + for (var i = len; i-- ; ) { + list = List.Cons( decoder(value[i]), list ); + } + return list; + } + crash('a List', value); + }; + } + + + // MAYBE + + function decodeMaybe(decoder) { + return function(value) { + try { + return Maybe.Just(decoder(value)); + } catch(e) { + return Maybe.Nothing; + } + }; + } + + + // FIELDS + + function decodeField(field, decoder) { + return function(value) { + var subValue = value[field]; + if (subValue !== undefined) { + return decoder(subValue); + } + crash("an object with field '" + field + "'", value); + }; + } + + + // OBJECTS + + function decodeKeyValuePairs(decoder) { + return function(value) { + var isObject = + typeof value === 'object' + && value !== null + && !(value instanceof Array); + + if (isObject) { + var keyValuePairs = List.Nil; + for (var key in value) { + var elmValue = decoder(value[key]); + var pair = Utils.Tuple2(key, elmValue); + keyValuePairs = List.Cons(pair, keyValuePairs); + } + return keyValuePairs; + } + + crash("an object", value); + }; + } + + function decodeObject1(f, d1) { + return function(value) { + return f(d1(value)); + }; + } + + function decodeObject2(f, d1, d2) { + return function(value) { + return A2( f, d1(value), d2(value) ); + }; + } + + function decodeObject3(f, d1, d2, d3) { + return function(value) { + return A3( f, d1(value), d2(value), d3(value) ); + }; + } + + function decodeObject4(f, d1, d2, d3, d4) { + return function(value) { + return A4( f, d1(value), d2(value), d3(value), d4(value) ); + }; + } + + function decodeObject5(f, d1, d2, d3, d4, d5) { + return function(value) { + return A5( f, d1(value), d2(value), d3(value), d4(value), d5(value) ); + }; + } + + function decodeObject6(f, d1, d2, d3, d4, d5, d6) { + return function(value) { + return A6( f, + d1(value), + d2(value), + d3(value), + d4(value), + d5(value), + d6(value) + ); + }; + } + + function decodeObject7(f, d1, d2, d3, d4, d5, d6, d7) { + return function(value) { + return A7( f, + d1(value), + d2(value), + d3(value), + d4(value), + d5(value), + d6(value), + d7(value) + ); + }; + } + + function decodeObject8(f, d1, d2, d3, d4, d5, d6, d7, d8) { + return function(value) { + return A8( f, + d1(value), + d2(value), + d3(value), + d4(value), + d5(value), + d6(value), + d7(value), + d8(value) + ); + }; + } + + + // TUPLES + + function decodeTuple1(f, d1) { + return function(value) { + if ( !(value instanceof Array) || value.length !== 1 ) { + crash('a Tuple of length 1', value); + } + return f( d1(value[0]) ); + }; + } + + function decodeTuple2(f, d1, d2) { + return function(value) { + if ( !(value instanceof Array) || value.length !== 2 ) { + crash('a Tuple of length 2', value); + } + return A2( f, d1(value[0]), d2(value[1]) ); + }; + } + + function decodeTuple3(f, d1, d2, d3) { + return function(value) { + if ( !(value instanceof Array) || value.length !== 3 ) { + crash('a Tuple of length 3', value); + } + return A3( f, d1(value[0]), d2(value[1]), d3(value[2]) ); + }; + } + + + function decodeTuple4(f, d1, d2, d3, d4) { + return function(value) { + if ( !(value instanceof Array) || value.length !== 4 ) { + crash('a Tuple of length 4', value); + } + return A4( f, d1(value[0]), d2(value[1]), d3(value[2]), d4(value[3]) ); + }; + } + + + function decodeTuple5(f, d1, d2, d3, d4, d5) { + return function(value) { + if ( !(value instanceof Array) || value.length !== 5 ) { + crash('a Tuple of length 5', value); + } + return A5( f, + d1(value[0]), + d2(value[1]), + d3(value[2]), + d4(value[3]), + d5(value[4]) + ); + }; + } + + + function decodeTuple6(f, d1, d2, d3, d4, d5, d6) { + return function(value) { + if ( !(value instanceof Array) || value.length !== 6 ) { + crash('a Tuple of length 6', value); + } + return A6( f, + d1(value[0]), + d2(value[1]), + d3(value[2]), + d4(value[3]), + d5(value[4]), + d6(value[5]) + ); + }; + } + + function decodeTuple7(f, d1, d2, d3, d4, d5, d6, d7) { + return function(value) { + if ( !(value instanceof Array) || value.length !== 7 ) { + crash('a Tuple of length 7', value); + } + return A7( f, + d1(value[0]), + d2(value[1]), + d3(value[2]), + d4(value[3]), + d5(value[4]), + d6(value[5]), + d7(value[6]) + ); + }; + } + + + function decodeTuple8(f, d1, d2, d3, d4, d5, d6, d7, d8) { + return function(value) { + if ( !(value instanceof Array) || value.length !== 8 ) { + crash('a Tuple of length 8', value); + } + return A8( f, + d1(value[0]), + d2(value[1]), + d3(value[2]), + d4(value[3]), + d5(value[4]), + d6(value[5]), + d7(value[6]), + d8(value[7]) + ); + }; + } + + + // CUSTOM DECODERS + + function decodeValue(value) { + return value; + } + + function runDecoderValue(decoder, value) { + try { + return Result.Ok(decoder(value)); + } catch(e) { + return Result.Err(e.message); + } + } + + function customDecoder(decoder, callback) { + return function(value) { + var result = callback(decoder(value)); + if (result.ctor === 'Err') { + throw new Error('custom decoder failed: ' + result._0); + } + return result._0; + } + } + + function andThen(decode, callback) { + return function(value) { + var result = decode(value); + return callback(result)(value); + } + } + + function fail(msg) { + return function(value) { + throw new Error(msg); + } + } + + function succeed(successValue) { + return function(value) { + return successValue; + } + } + + + // ONE OF MANY + + function oneOf(decoders) { + return function(value) { + var errors = []; + var temp = decoders; + while (temp.ctor !== '[]') { + try { + return temp._0(value); + } catch(e) { + errors.push(e.message); + } + temp = temp._1; + } + throw new Error('expecting one of the following:\n ' + errors.join('\n ')); + } + } + + function get(decoder, value) { + try { + return Result.Ok(decoder(value)); + } catch(e) { + return Result.Err(e.message); + } + } + + + // ENCODE / DECODE + + function runDecoderString(decoder, string) { + try { + return Result.Ok(decoder(JSON.parse(string))); + } catch(e) { + return Result.Err(e.message); + } + } + + function encode(indentLevel, value) { + return JSON.stringify(value, null, indentLevel); + } + + function identity(value) { + return value; + } + + function encodeObject(keyValuePairs) { + var obj = {}; + while (keyValuePairs.ctor !== '[]') { + var pair = keyValuePairs._0; + obj[pair._0] = pair._1; + keyValuePairs = keyValuePairs._1; + } + return obj; + } + + return localRuntime.Native.Json.values = { + encode: F2(encode), + runDecoderString: F2(runDecoderString), + runDecoderValue: F2(runDecoderValue), + + get: F2(get), + oneOf: oneOf, + + decodeNull: decodeNull, + decodeInt: decodeInt, + decodeFloat: decodeFloat, + decodeString: decodeString, + decodeBool: decodeBool, + + decodeMaybe: decodeMaybe, + + decodeList: decodeList, + decodeArray: decodeArray, + + decodeField: F2(decodeField), + + decodeObject1: F2(decodeObject1), + decodeObject2: F3(decodeObject2), + decodeObject3: F4(decodeObject3), + decodeObject4: F5(decodeObject4), + decodeObject5: F6(decodeObject5), + decodeObject6: F7(decodeObject6), + decodeObject7: F8(decodeObject7), + decodeObject8: F9(decodeObject8), + decodeKeyValuePairs: decodeKeyValuePairs, + + decodeTuple1: F2(decodeTuple1), + decodeTuple2: F3(decodeTuple2), + decodeTuple3: F4(decodeTuple3), + decodeTuple4: F5(decodeTuple4), + decodeTuple5: F6(decodeTuple5), + decodeTuple6: F7(decodeTuple6), + decodeTuple7: F8(decodeTuple7), + decodeTuple8: F9(decodeTuple8), + + andThen: F2(andThen), + decodeValue: decodeValue, + customDecoder: F2(customDecoder), + fail: fail, + succeed: succeed, + + identity: identity, + encodeNull: null, + encodeArray: ElmArray.toJSArray, + encodeList: List.toArray, + encodeObject: encodeObject + + }; + +};
+ src/Native/Keyboard.js view
@@ -0,0 +1,154 @@+Elm.Native.Keyboard = {}; +Elm.Native.Keyboard.make = function(elm) { + + elm.Native = elm.Native || {}; + elm.Native.Keyboard = elm.Native.Keyboard || {}; + if (elm.Native.Keyboard.values) return elm.Native.Keyboard.values; + + // Duplicated from Native.Signal + function send(node, timestep, changed) { + var kids = node.kids; + for (var i = kids.length; i--; ) { + kids[i].recv(timestep, changed, node.id); + } + } + + var Signal = Elm.Signal.make(elm); + var NList = Elm.Native.List.make(elm); + var Utils = Elm.Native.Utils.make(elm); + + var downEvents = Signal.constant(null); + var upEvents = Signal.constant(null); + var blurEvents = Signal.constant(null); + + elm.addListener([downEvents.id], document, 'keydown', function down(e) { + elm.notify(downEvents.id, e); + }); + + elm.addListener([upEvents.id], document, 'keyup', function up(e) { + elm.notify(upEvents.id, e); + }); + + elm.addListener([blurEvents.id], window, 'blur', function blur(e) { + elm.notify(blurEvents.id, null); + }); + + function state(alt, meta, keyCodes) { + return { + alt: alt, + meta: meta, + keyCodes: keyCodes + }; + } + var emptyState = state(false, false, NList.Nil); + + function KeyMerge(down, up, blur) { + var args = [down,up,blur]; + this.id = Utils.guid(); + // Ignore starting values here + this.value = emptyState; + this.kids = []; + + var n = args.length; + var count = 0; + var isChanged = false; + + this.recv = function(timestep, changed, parentID) { + ++count; + if (changed) { + // We know this a change must only be one of the following cases + if (parentID === down.id && !A2(NList.member, down.value.keyCode, this.value.keyCodes)) { + isChanged = true; + var v = down.value; + var newCodes = NList.Cons(v.keyCode, this.value.keyCodes); + this.value = state(v.altKey, v.metaKey, newCodes); + } + else if (parentID === up.id) { + isChanged = true; + var v = up.value; + var notEq = function(kc) { return kc !== v.keyCode }; + var newCodes = A2(NList.filter, notEq, this.value.keyCodes); + this.value = state(v.altKey, v.metaKey, newCodes); + } + else if (parentID === blur.id) { + isChanged = true; + this.value = emptyState; + } + } + if (count == n) { + send(this, timestep, isChanged); + isChanged = false; + count = 0; + } + }; + + for (var i = n; i--; ) { + args[i].kids.push(this); + args[i].defaultNumberOfKids += 1; + } + } + + var keyMerge = new KeyMerge(downEvents,upEvents,blurEvents); + + // select a part of a keyMerge and dropRepeats the result + function keySignal(f) { + var signal = A2(Signal.map, f, keyMerge); + // must set the default number of kids to make it possible to filter + // these signals if they are not actually used. + keyMerge.defaultNumberOfKids += 1; + signal.defaultNumberOfKids = 1; + var filtered = Signal.dropRepeats(signal); + filtered.defaultNumberOfKids = 0; + return filtered; + } + + // break keyMerge into parts + var keysDown = keySignal(function getKeyCodes(v) { + return v.keyCodes; + }); + var alt = keySignal(function getKeyCodes(v) { + return v.alt; + }); + var meta = keySignal(function getKeyCodes(v) { + return v.meta; + }); + + function dir(up, down, left, right) { + function toDirections(state) { + var keyCodes = state.keyCodes; + var x = 0, y = 0; + while (keyCodes.ctor === "::") { + switch (keyCodes._0) { + case left : --x; break; + case right: ++x; break; + case up : ++y; break; + case down : --y; break; + } + keyCodes = keyCodes._1; + } + return { _:{}, x:x, y:y }; + } + return keySignal(toDirections); + } + + function is(key) { + return keySignal(function(v) { + return A2( NList.member, key, v.keyCodes ); + }); + } + + var lastPressed = A2(Signal.map, function(e) { + return e ? e.keyCode : 0; + }, downEvents); + downEvents.defaultNumberOfKids += 1; + + return elm.Native.Keyboard.values = { + isDown:is, + alt: alt, + meta: meta, + directions:F4(dir), + keysDown:keysDown, + lastPressed:lastPressed + }; + +};
+ src/Native/List.js view
@@ -0,0 +1,326 @@+Elm.Native.List = {}; +Elm.Native.List.make = function(elm) { + elm.Native = elm.Native || {}; + elm.Native.List = elm.Native.List || {}; + if (elm.Native.List.values) return elm.Native.List.values; + if ('values' in Elm.Native.List) + return elm.Native.List.values = Elm.Native.List.values; + + var Utils = Elm.Native.Utils.make(elm); + + var Nil = Utils.Nil; + var Cons = Utils.Cons; + + function throwError(f) { + throw new Error("Function '" + f + "' expects a non-empty list!"); + } + + function toArray(xs) { + var out = []; + while (xs.ctor !== '[]') { + out.push(xs._0); + xs = xs._1; + } + return out; + } + + function fromArray(arr) { + var out = Nil; + for (var i = arr.length; i--; ) { + out = Cons(arr[i], out); + } + return out; + } + + function range(lo,hi) { + var lst = Nil; + if (lo <= hi) { + do { lst = Cons(hi,lst) } while (hi-->lo); + } + return lst + } + + function head(v) { + return v.ctor === '[]' ? throwError('head') : v._0; + } + function tail(v) { + return v.ctor === '[]' ? throwError('tail') : v._1; + } + + function last(xs) { + if (xs.ctor === '[]') { throwError('last'); } + var out = xs._0; + while (xs.ctor !== '[]') { + out = xs._0; + xs = xs._1; + } + return out; + } + + function map(f, xs) { + var arr = []; + while (xs.ctor !== '[]') { + arr.push(f(xs._0)); + xs = xs._1; + } + return fromArray(arr); + } + + // f defined similarly for both foldl and foldr (NB: different from Haskell) + // ie, foldl : (a -> b -> b) -> b -> [a] -> b + function foldl(f, b, xs) { + var acc = b; + while (xs.ctor !== '[]') { + acc = A2(f, xs._0, acc); + xs = xs._1; + } + return acc; + } + + function foldr(f, b, xs) { + var arr = toArray(xs); + var acc = b; + for (var i = arr.length; i--; ) { + acc = A2(f, arr[i], acc); + } + return acc; + } + + function foldl1(f, xs) { + return xs.ctor === '[]' ? throwError('foldl1') : foldl(f, xs._0, xs._1); + } + + function foldr1(f, xs) { + if (xs.ctor === '[]') { throwError('foldr1'); } + var arr = toArray(xs); + var acc = arr.pop(); + for (var i = arr.length; i--; ) { + acc = A2(f, arr[i], acc); + } + return acc; + } + + function scanl(f, b, xs) { + var arr = toArray(xs); + arr.unshift(b); + var len = arr.length; + for (var i = 1; i < len; ++i) { + arr[i] = A2(f, arr[i], arr[i-1]); + } + return fromArray(arr); + } + + function scanl1(f, xs) { + return xs.ctor === '[]' ? throwError('scanl1') : scanl(f, xs._0, xs._1); + } + + function filter(pred, xs) { + var arr = []; + while (xs.ctor !== '[]') { + if (pred(xs._0)) { arr.push(xs._0); } + xs = xs._1; + } + return fromArray(arr); + } + + function length(xs) { + var out = 0; + while (xs.ctor !== '[]') { + out += 1; + xs = xs._1; + } + return out; + } + + function member(x, xs) { + while (xs.ctor !== '[]') { + if (Utils.eq(x,xs._0)) return true; + xs = xs._1; + } + return false; + } + + function reverse(xs) { + return fromArray(toArray(xs).reverse()); + } + + function append(xs, ys) { + if (xs.ctor === '[]') { + return ys; + } + var root = Cons(xs._0, Nil); + var curr = root; + xs = xs._1; + while (xs.ctor !== '[]') { + curr._1 = Cons(xs._0, Nil); + xs = xs._1; + curr = curr._1; + } + curr._1 = ys; + return root; + } + + function all(pred, xs) { + while (xs.ctor !== '[]') { + if (!pred(xs._0)) return false; + xs = xs._1; + } + return true; + } + + function any(pred, xs) { + while (xs.ctor !== '[]') { + if (pred(xs._0)) return true; + xs = xs._1; + } + return false; + } + + function map2(f, xs, ys) { + var arr = []; + while (xs.ctor !== '[]' && ys.ctor !== '[]') { + arr.push(A2(f, xs._0, ys._0)); + xs = xs._1; + ys = ys._1; + } + return fromArray(arr); + } + + function map3(f, xs, ys, zs) { + var arr = []; + while (xs.ctor !== '[]' && ys.ctor !== '[]' && zs.ctor !== '[]') { + arr.push(A3(f, xs._0, ys._0, zs._0)); + xs = xs._1; + ys = ys._1; + zs = zs._1; + } + return fromArray(arr); + } + + function map4(f, ws, xs, ys, zs) { + var arr = []; + while ( ws.ctor !== '[]' + && xs.ctor !== '[]' + && ys.ctor !== '[]' + && zs.ctor !== '[]') + { + arr.push(A4(f, ws._0, xs._0, ys._0, zs._0)); + ws = ws._1; + xs = xs._1; + ys = ys._1; + zs = zs._1; + } + return fromArray(arr); + } + + function map5(f, vs, ws, xs, ys, zs) { + var arr = []; + while ( vs.ctor !== '[]' + && ws.ctor !== '[]' + && xs.ctor !== '[]' + && ys.ctor !== '[]' + && zs.ctor !== '[]') + { + arr.push(A5(f, vs._0, ws._0, xs._0, ys._0, zs._0)); + vs = vs._1; + ws = ws._1; + xs = xs._1; + ys = ys._1; + zs = zs._1; + } + return fromArray(arr); + } + + function sort(xs) { + return fromArray(toArray(xs).sort(Utils.cmp)); + } + + function sortBy(f, xs) { + return fromArray(toArray(xs).sort(function(a,b){ + return Utils.cmp(f(a), f(b)); + })); + } + + function sortWith(f, xs) { + return fromArray(toArray(xs).sort(function(a,b){ + var ord = f(a)(b).ctor; + return ord === 'EQ' ? 0 : ord === 'LT' ? -1 : 1; + })); + } + + function nth(xs, n) { + return toArray(xs)[n]; + } + + function take(n, xs) { + var arr = []; + while (xs.ctor !== '[]' && n > 0) { + arr.push(xs._0); + xs = xs._1; + --n; + } + return fromArray(arr); + } + + function drop(n, xs) { + while (xs.ctor !== '[]' && n > 0) { + xs = xs._1; + --n; + } + return xs; + } + + function repeat(n, x) { + var arr = []; + var pattern = [x]; + while (n > 0) { + if (n & 1) arr = arr.concat(pattern); + n >>= 1, pattern = pattern.concat(pattern); + } + return fromArray(arr); + } + + + Elm.Native.List.values = { + Nil:Nil, + Cons:Cons, + cons:F2(Cons), + toArray:toArray, + fromArray:fromArray, + range:range, + append: F2(append), + + head:head, + tail:tail, + last:last, + + map:F2(map), + foldl:F3(foldl), + foldr:F3(foldr), + + foldl1:F2(foldl1), + foldr1:F2(foldr1), + scanl:F3(scanl), + scanl1:F2(scanl1), + filter:F2(filter), + length:length, + member:F2(member), + reverse:reverse, + + all:F2(all), + any:F2(any), + map2:F3(map2), + map3:F4(map3), + map4:F5(map4), + map5:F6(map5), + sort:sort, + sortBy:F2(sortBy), + sortWith:F2(sortWith), + nth:F2(nth), + take:F2(take), + drop:F2(drop), + repeat:F2(repeat) + }; + return elm.Native.List.values = Elm.Native.List.values; + +};
+ src/Native/Mouse.js view
@@ -0,0 +1,60 @@+Elm.Native = Elm.Native || {}; +Elm.Native.Mouse = {}; +Elm.Native.Mouse.make = function(localRuntime) { + + localRuntime.Native = localRuntime.Native || {}; + localRuntime.Native.Mouse = localRuntime.Native.Mouse || {}; + if (localRuntime.Native.Mouse.values) { + return localRuntime.Native.Mouse.values; + } + + var Signal = Elm.Signal.make(localRuntime); + var Utils = Elm.Native.Utils.make(localRuntime); + + var position = Signal.constant(Utils.Tuple2(0,0)); + position.defaultNumberOfKids = 2; + + // do not move x and y into Elm. By setting their default number + // of kids, it is possible to detatch the mouse listeners if + // they are not needed. + function fst(pair) { + return pair._0; + } + function snd(pair) { + return pair._0; + } + + var x = A2( Signal.map, fst, position ); + x.defaultNumberOfKids = 0; + + var y = A2( Signal.map, snd, position ); + y.defaultNumberOfKids = 0; + + var isDown = Signal.constant(false); + var clicks = Signal.constant(Utils.Tuple0); + + var node = localRuntime.isFullscreen() + ? document + : localRuntime.node; + + localRuntime.addListener([clicks.id], node, 'click', function click() { + localRuntime.notify(clicks.id, Utils.Tuple0); + }); + localRuntime.addListener([isDown.id], node, 'mousedown', function down() { + localRuntime.notify(isDown.id, true); + }); + localRuntime.addListener([isDown.id], node, 'mouseup', function up() { + localRuntime.notify(isDown.id, false); + }); + localRuntime.addListener([position.id], node, 'mousemove', function move(e) { + localRuntime.notify(position.id, Utils.getXY(e)); + }); + + return localRuntime.Native.Mouse.values = { + position: position, + x: x, + y: y, + isDown: isDown, + clicks: clicks + }; +};
+ src/Native/Ports.js view
@@ -0,0 +1,92 @@+Elm.Native.Ports = {}; +Elm.Native.Ports.make = function(localRuntime) { + localRuntime.Native = localRuntime.Native || {}; + localRuntime.Native.Ports = localRuntime.Native.Ports || {}; + if (localRuntime.Native.Ports.values) { + return localRuntime.Native.Ports.values; + } + + var Signal; + + function incomingSignal(converter) { + converter.isSignal = true; + return converter; + } + + function outgoingSignal(converter) { + if (!Signal) { + Signal = Elm.Signal.make(localRuntime); + } + return function(signal) { + var subscribers = [] + function subscribe(handler) { + subscribers.push(handler); + } + function unsubscribe(handler) { + subscribers.pop(subscribers.indexOf(handler)); + } + A2( Signal.map, function(value) { + var val = converter(value); + var len = subscribers.length; + for (var i = 0; i < len; ++i) { + subscribers[i](val); + } + }, signal); + return { subscribe:subscribe, unsubscribe:unsubscribe }; + } + } + + function portIn(name, converter) { + var jsValue = localRuntime.ports.incoming[name]; + if (jsValue === undefined) { + throw new Error("Initialization Error: port '" + name + + "' was not given an input!"); + } + localRuntime.ports.uses[name] += 1; + try { + var elmValue = converter(jsValue); + } catch(e) { + throw new Error("Initialization Error on port '" + name + "': \n" + e.message); + } + + // just return a static value if it is not a signal + if (!converter.isSignal) { + return elmValue; + } + + // create a signal if necessary + if (!Signal) { + Signal = Elm.Signal.make(localRuntime); + } + var signal = Signal.constant(elmValue); + function send(jsValue) { + try { + var elmValue = converter(jsValue); + } catch(e) { + throw new Error("Error sending to port '" + name + "': \n" + e.message); + } + setTimeout(function() { + localRuntime.notify(signal.id, elmValue); + }, 0); + } + localRuntime.ports.outgoing[name] = { send:send }; + return signal; + } + + function portOut(name, converter, value) { + try { + localRuntime.ports.outgoing[name] = converter(value); + } catch(e) { + throw new Error("Initialization Error on port '" + name + "': \n" + e.message); + } + return value; + } + + return localRuntime.Native.Ports.values = { + incomingSignal: incomingSignal, + outgoingSignal: outgoingSignal, + portOut: portOut, + portIn: portIn + }; +}; +
+ src/Native/Regex.js view
@@ -0,0 +1,103 @@+Elm.Native.Regex = {}; +Elm.Native.Regex.make = function(elm) { + elm.Native = elm.Native || {}; + elm.Native.Regex = elm.Native.Regex || {}; + if (elm.Native.Regex.values) return elm.Native.Regex.values; + if ('values' in Elm.Native.Regex) + return elm.Native.Regex.values = Elm.Native.Regex.values; + + var List = Elm.Native.List.make(elm); + var Maybe = Elm.Maybe.make(elm); + + function escape(str) { + return str.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + } + function caseInsensitive(re) { + return new RegExp(re.source, 'gi'); + } + function regex(raw) { + return new RegExp(raw, 'g'); + } + + function contains(re, string) { + return string.match(re) !== null; + } + + function find(n, re, str) { + n = n.ctor === "All" ? Infinity : n._0; + var out = []; + var number = 0; + var string = str; + var result; + while (number++ < n && (result = re.exec(string))) { + var i = result.length - 1; + var subs = new Array(i); + while (i > 0) { + var submatch = result[i]; + subs[--i] = submatch === undefined + ? Maybe.Nothing + : Maybe.Just(submatch); + } + out.push({ + _:{}, + match: result[0], + submatches: List.fromArray(subs), + index: result.index, + number: number + }); + } + return List.fromArray(out); + } + + function replace(n, re, replacer, string) { + n = n.ctor === "All" ? Infinity : n._0; + var count = 0; + function jsReplacer(match) { + if (count++ > n) return match; + var i = arguments.length-3; + var submatches = new Array(i); + while (i > 0) { + var submatch = arguments[i]; + submatches[--i] = submatch === undefined + ? Maybe.Nothing + : Maybe.Just(submatch); + } + return replacer({ + _:{}, + match:match, + submatches:List.fromArray(submatches), + index:arguments[i-1], + number:count + }); + } + return string.replace(re, jsReplacer); + } + + function split(n, re, str) { + if (n === Infinity) { + return List.fromArray(string.split(re)); + } + var string = str; + var result; + var out = []; + var start = re.lastIndex; + while (n--) { + if (!(result = re.exec(string))) break; + out.push(string.slice(start, result.index)); + start = re.lastIndex; + } + out.push(string.slice(start)); + return List.fromArray(out); + } + + return Elm.Native.Regex.values = { + regex: regex, + caseInsensitive: caseInsensitive, + escape: escape, + + contains: F2(contains), + find: F3(find), + replace: F4(replace), + split: F3(split) + }; +};
+ src/Native/Runtime.js view
@@ -0,0 +1,560 @@+ +if (!Elm.fullscreen) { + + (function() { + 'use strict'; + + var Display = { FULLSCREEN: 0, COMPONENT: 1, NONE: 2 }; + + Elm.fullscreen = function(module, ports) { + var container = document.createElement('div'); + document.body.appendChild(container); + return init(Display.FULLSCREEN, container, module, ports || {}); + }; + + Elm.embed = function(module, container, ports) { + var tag = container.tagName; + if (tag !== 'DIV') { + throw new Error('Elm.node must be given a DIV, not a ' + tag + '.'); + } else if (container.hasChildNodes()) { + throw new Error('Elm.node must be given an empty DIV. No children allowed!'); + } + return init(Display.COMPONENT, container, module, ports || {}); + }; + + Elm.worker = function(module, ports) { + return init(Display.NONE, {}, module, ports || {}); + }; + + function init(display, container, module, ports, moduleToReplace) { + // defining state needed for an instance of the Elm RTS + var inputs = []; + + /* OFFSET + * Elm's time traveling debugger lets you interrupt the smooth flow of time + * by pausing and continuing program execution. To ensure the user sees a + * program that moves smoothly through the pause/continue time gap, + * we need to adjsut the value of Date.now(). + */ + var timer = function() { + var inducedDelay = 0; + + var now = function() { + return Date.now() - inducedDelay; + }; + + var addDelay = function(d) { + inducedDelay += d; + return inducedDelay; + }; + + return { + now : now, + addDelay : addDelay + } + }(); + + var updateInProgress = false; + function notify(id, v) { + if (updateInProgress) { + throw new Error( + 'The notify function has been called synchronously!\n' + + 'This can lead to frames being dropped.\n' + + 'Definitely report this to <https://github.com/elm-lang/Elm/issues>\n'); + } + updateInProgress = true; + var timestep = timer.now(); + for (var i = inputs.length; i--; ) { + inputs[i].recv(timestep, id, v); + } + updateInProgress = false; + } + function setTimeout(func, delay) { + window.setTimeout(func, delay); + } + + var listeners = []; + function addListener(relevantInputs, domNode, eventName, func) { + domNode.addEventListener(eventName, func); + var listener = { + relevantInputs: relevantInputs, + domNode: domNode, + eventName: eventName, + func: func + }; + listeners.push(listener); + } + + var portUses = {} + for (var key in ports) { + portUses[key] = 0; + } + // create the actual RTS. Any impure modules will attach themselves to this + // object. This permits many Elm programs to be embedded per document. + var elm = { + notify: notify, + setTimeout: setTimeout, + node: container, + addListener: addListener, + inputs: inputs, + timer: timer, + ports: { incoming:ports, outgoing:{}, uses:portUses }, + + isFullscreen: function() { return display === Display.FULLSCREEN; }, + isEmbed: function() { return display === Display.COMPONENT; }, + isWorker: function() { return display === Display.NONE; } + }; + + function swap(newModule) { + removeListeners(listeners); + var div = document.createElement('div'); + var newElm = init(display, div, newModule, ports, elm); + inputs = []; + // elm.swap = newElm.swap; + return newElm; + } + + function dispose() { + removeListeners(listeners); + inputs = []; + } + + var Module = {}; + try { + Module = module.make(elm); + checkPorts(elm); + } catch(e) { + var code = document.createElement('code'); + + var lines = e.message.split('\n'); + code.appendChild(document.createTextNode(lines[0])); + code.appendChild(document.createElement('br')); + code.appendChild(document.createElement('br')); + for (var i = 1; i < lines.length; ++i) { + code.appendChild(document.createTextNode('\u00A0 \u00A0 ' + lines[i])); + code.appendChild(document.createElement('br')); + } + code.appendChild(document.createElement('br')); + code.appendChild(document.createTextNode("Open the developer console for more details.")); + + container.appendChild(code); + throw e; + } + inputs = filterDeadInputs(inputs); + filterListeners(inputs, listeners); + addReceivers(elm.ports.outgoing); + if (display !== Display.NONE) { + var graphicsNode = initGraphics(elm, Module); + } + if (typeof moduleToReplace !== 'undefined') { + hotSwap(moduleToReplace, elm); + + // rerender scene if graphics are enabled. + if (typeof graphicsNode !== 'undefined') { + graphicsNode.recv(0, true, 0); + } + } + + return { + swap:swap, + ports:elm.ports.outgoing, + dispose:dispose + }; + }; + + function checkPorts(elm) { + var portUses = elm.ports.uses; + for (var key in portUses) { + var uses = portUses[key] + if (uses === 0) { + throw new Error( + "Initialization Error: provided port '" + key + + "' to a module that does not take it as in input.\n" + + "Remove '" + key + "' from the module initialization code."); + } else if (uses > 1) { + throw new Error( + "Initialization Error: port '" + key + + "' has been declared multiple times in the Elm code.\n" + + "Remove declarations until there is exactly one."); + } + } + } + + + //// FILTER SIGNALS //// + + // TODO: move this code into the signal module and create a function + // Signal.initializeGraph that actually instantiates everything. + + function filterListeners(inputs, listeners) { + loop: + for (var i = listeners.length; i--; ) { + var listener = listeners[i]; + for (var j = inputs.length; j--; ) { + if (listener.relevantInputs.indexOf(inputs[j].id) >= 0) { + continue loop; + } + } + listener.domNode.removeEventListener(listener.eventName, listener.func); + } + } + + function removeListeners(listeners) { + for (var i = listeners.length; i--; ) { + var listener = listeners[i]; + listener.domNode.removeEventListener(listener.eventName, listener.func); + } + } + + // add receivers for built-in ports if they are defined + function addReceivers(ports) { + if ('log' in ports) { + ports.log.subscribe(function(v) { console.log(v) }); + } + if ('stdout' in ports) { + var process = process || {}; + var handler = process.stdout + ? function(v) { process.stdout.write(v); } + : function(v) { console.log(v); }; + ports.stdout.subscribe(handler); + } + if ('stderr' in ports) { + var process = process || {}; + var handler = process.stderr + ? function(v) { process.stderr.write(v); } + : function(v) { console.log('Error:' + v); }; + ports.stderr.subscribe(handler); + } + if ('title' in ports) { + if (typeof ports.title === 'string') { + document.title = ports.title; + } else { + ports.title.subscribe(function(v) { document.title = v; }); + } + } + if ('redirect' in ports) { + ports.redirect.subscribe(function(v) { + if (v.length > 0) window.location = v; + }); + } + if ('favicon' in ports) { + if (typeof ports.favicon === 'string') { + changeFavicon(ports.favicon); + } else { + ports.favicon.subscribe(changeFavicon); + } + } + function changeFavicon(src) { + var link = document.createElement('link'); + var oldLink = document.getElementById('elm-favicon'); + link.id = 'elm-favicon'; + link.rel = 'shortcut icon'; + link.href = src; + if (oldLink) { + document.head.removeChild(oldLink); + } + document.head.appendChild(link); + } + } + + + function filterDeadInputs(inputs) { + var temp = []; + for (var i = inputs.length; i--; ) { + if (isAlive(inputs[i])) temp.push(inputs[i]); + } + return temp; + } + + + function isAlive(input) { + if (!('defaultNumberOfKids' in input)) return true; + var len = input.kids.length; + if (len === 0) return false; + if (len > input.defaultNumberOfKids) return true; + var alive = false; + for (var i = len; i--; ) { + alive = alive || isAlive(input.kids[i]); + } + return alive; + } + + + //// RENDERING //// + + function initGraphics(elm, Module) { + if (!('main' in Module)) { + throw new Error("'main' is missing! What do I display?!"); + } + + var signalGraph = Module.main; + + // make sure the signal graph is actually a signal & extract the visual model + var Signal = Elm.Signal.make(elm); + if (!('recv' in signalGraph)) { + signalGraph = Signal.constant(signalGraph); + } + var initialScene = signalGraph.value; + + // Add the initialScene to the DOM + var Element = Elm.Native.Graphics.Element.make(elm); + elm.node.appendChild(Element.render(initialScene)); + + var _requestAnimationFrame = + typeof requestAnimationFrame !== 'undefined' + ? requestAnimationFrame + : function(cb) { setTimeout(cb, 1000/60); } + ; + + // domUpdate is called whenever the main Signal changes. + // + // domUpdate and drawCallback implement a small state machine in order + // to schedule only 1 draw per animation frame. This enforces that + // once draw has been called, it will not be called again until the + // next frame. + // + // drawCallback is scheduled whenever + // 1. The state transitions from PENDING_REQUEST to EXTRA_REQUEST, or + // 2. The state transitions from NO_REQUEST to PENDING_REQUEST + // + // Invariants: + // 1. In the NO_REQUEST state, there is never a scheduled drawCallback. + // 2. In the PENDING_REQUEST and EXTRA_REQUEST states, there is always exactly 1 + // scheduled drawCallback. + var NO_REQUEST = 0; + var PENDING_REQUEST = 1; + var EXTRA_REQUEST = 2; + var state = NO_REQUEST; + var savedScene = initialScene; + var scheduledScene = initialScene; + + function domUpdate(newScene) { + scheduledScene = newScene; + + switch (state) { + case NO_REQUEST: + _requestAnimationFrame(drawCallback); + state = PENDING_REQUEST; + return; + case PENDING_REQUEST: + state = PENDING_REQUEST; + return; + case EXTRA_REQUEST: + state = PENDING_REQUEST; + return; + } + } + + function drawCallback() { + switch (state) { + case NO_REQUEST: + // This state should not be possible. How can there be no + // request, yet somehow we are actively fulfilling a + // request? + throw new Error( + "Unexpected draw callback.\n" + + "Please report this to <https://github.com/elm-lang/core/issues>." + ); + + case PENDING_REQUEST: + // At this point, we do not *know* that another frame is + // needed, but we make an extra request to rAF just in + // case. It's possible to drop a frame if rAF is called + // too late, so we just do it preemptively. + _requestAnimationFrame(drawCallback); + state = EXTRA_REQUEST; + + // There's also stuff we definitely need to draw. + draw(); + return; + + case EXTRA_REQUEST: + // Turns out the extra request was not needed, so we will + // stop calling rAF. No reason to call it all the time if + // no one needs it. + state = NO_REQUEST; + return; + } + } + + function draw() { + Element.updateAndReplace(elm.node.firstChild, savedScene, scheduledScene); + if (elm.Native.Window) { + elm.Native.Window.values.resizeIfNeeded(); + } + savedScene = scheduledScene; + } + + var renderer = A2(Signal.map, domUpdate, signalGraph); + + // must check for resize after 'renderer' is created so + // that changes show up. + if (elm.Native.Window) { + elm.Native.Window.values.resizeIfNeeded(); + } + + return renderer; + } + + //// HOT SWAPPING //// + + // Returns boolean indicating if the swap was successful. + // Requires that the two signal graphs have exactly the same + // structure. + function hotSwap(from, to) { + function similar(nodeOld,nodeNew) { + var idOkay = nodeOld.id === nodeNew.id; + var lengthOkay = nodeOld.kids.length === nodeNew.kids.length; + return idOkay && lengthOkay; + } + function swap(nodeOld,nodeNew) { + nodeNew.value = nodeOld.value; + return true; + } + var canSwap = depthFirstTraversals(similar, from.inputs, to.inputs); + if (canSwap) { + depthFirstTraversals(swap, from.inputs, to.inputs); + } + from.node.parentNode.replaceChild(to.node, from.node); + + return canSwap; + } + + // Returns false if the node operation f ever fails. + function depthFirstTraversals(f, queueOld, queueNew) { + if (queueOld.length !== queueNew.length) return false; + queueOld = queueOld.slice(0); + queueNew = queueNew.slice(0); + + var seen = []; + while (queueOld.length > 0 && queueNew.length > 0) { + var nodeOld = queueOld.pop(); + var nodeNew = queueNew.pop(); + if (seen.indexOf(nodeOld.id) < 0) { + if (!f(nodeOld, nodeNew)) return false; + queueOld = queueOld.concat(nodeOld.kids); + queueNew = queueNew.concat(nodeNew.kids); + seen.push(nodeOld.id); + } + } + return true; + } + }()); + + function F2(fun) { + function wrapper(a) { return function(b) { return fun(a,b) } } + wrapper.arity = 2; + wrapper.func = fun; + return wrapper; + } + + function F3(fun) { + function wrapper(a) { + return function(b) { return function(c) { return fun(a,b,c) }} + } + wrapper.arity = 3; + wrapper.func = fun; + return wrapper; + } + + function F4(fun) { + function wrapper(a) { return function(b) { return function(c) { + return function(d) { return fun(a,b,c,d) }}} + } + wrapper.arity = 4; + wrapper.func = fun; + return wrapper; + } + + function F5(fun) { + function wrapper(a) { return function(b) { return function(c) { + return function(d) { return function(e) { return fun(a,b,c,d,e) }}}} + } + wrapper.arity = 5; + wrapper.func = fun; + return wrapper; + } + + function F6(fun) { + function wrapper(a) { return function(b) { return function(c) { + return function(d) { return function(e) { return function(f) { + return fun(a,b,c,d,e,f) }}}}} + } + wrapper.arity = 6; + wrapper.func = fun; + return wrapper; + } + + function F7(fun) { + function wrapper(a) { return function(b) { return function(c) { + return function(d) { return function(e) { return function(f) { + return function(g) { return fun(a,b,c,d,e,f,g) }}}}}} + } + wrapper.arity = 7; + wrapper.func = fun; + return wrapper; + } + + function F8(fun) { + function wrapper(a) { return function(b) { return function(c) { + return function(d) { return function(e) { return function(f) { + return function(g) { return function(h) { + return fun(a,b,c,d,e,f,g,h)}}}}}}} + } + wrapper.arity = 8; + wrapper.func = fun; + return wrapper; + } + + function F9(fun) { + function wrapper(a) { return function(b) { return function(c) { + return function(d) { return function(e) { return function(f) { + return function(g) { return function(h) { return function(i) { + return fun(a,b,c,d,e,f,g,h,i) }}}}}}}} + } + wrapper.arity = 9; + wrapper.func = fun; + return wrapper; + } + + function A2(fun,a,b) { + return fun.arity === 2 + ? fun.func(a,b) + : fun(a)(b); + } + function A3(fun,a,b,c) { + return fun.arity === 3 + ? fun.func(a,b,c) + : fun(a)(b)(c); + } + function A4(fun,a,b,c,d) { + return fun.arity === 4 + ? fun.func(a,b,c,d) + : fun(a)(b)(c)(d); + } + function A5(fun,a,b,c,d,e) { + return fun.arity === 5 + ? fun.func(a,b,c,d,e) + : fun(a)(b)(c)(d)(e); + } + function A6(fun,a,b,c,d,e,f) { + return fun.arity === 6 + ? fun.func(a,b,c,d,e,f) + : fun(a)(b)(c)(d)(e)(f); + } + function A7(fun,a,b,c,d,e,f,g) { + return fun.arity === 7 + ? fun.func(a,b,c,d,e,f,g) + : fun(a)(b)(c)(d)(e)(f)(g); + } + function A8(fun,a,b,c,d,e,f,g,h) { + return fun.arity === 8 + ? fun.func(a,b,c,d,e,f,g,h) + : fun(a)(b)(c)(d)(e)(f)(g)(h); + } + function A9(fun,a,b,c,d,e,f,g,h,i) { + return fun.arity === 9 + ? fun.func(a,b,c,d,e,f,g,h,i) + : fun(a)(b)(c)(d)(e)(f)(g)(h)(i); + } +}
+ src/Native/Show.js view
@@ -0,0 +1,150 @@+Elm.Native.Show = {}; +Elm.Native.Show.make = function(elm) { + elm.Native = elm.Native || {}; + elm.Native.Show = elm.Native.Show || {}; + if (elm.Native.Show.values) return elm.Native.Show.values; + + var _Array; + var Dict; + var List; + var Utils = Elm.Native.Utils.make(elm); + + var toString = function(v) { + var type = typeof v; + if (type === "function") { + var name = v.func ? v.func.name : v.name; + return '<function' + (name === '' ? '' : ': ') + name + '>'; + } + else if (type === "boolean") { + return v ? "True" : "False"; + } + else if (type === "number") { + return v + ""; + } + else if ((v instanceof String) && v.isChar) { + return "'" + addSlashes(v) + "'"; + } + else if (type === "string") { + return '"' + addSlashes(v) + '"'; + } + else if (type === "object" && '_' in v && probablyPublic(v)) { + var output = []; + for (var k in v._) { + for (var i = v._[k].length; i--; ) { + output.push(k + " = " + toString(v._[k][i])); + } + } + for (var k in v) { + if (k === '_') continue; + output.push(k + " = " + toString(v[k])); + } + if (output.length === 0) { + return "{}"; + } + return "{ " + output.join(", ") + " }"; + } + else if (type === "object" && 'ctor' in v) { + if (v.ctor.substring(0,6) === "_Tuple") { + var output = []; + for (var k in v) { + if (k === 'ctor') continue; + output.push(toString(v[k])); + } + return "(" + output.join(",") + ")"; + } + else if (v.ctor === "_Array") { + if (!_Array) { + _Array = Elm.Array.make(elm); + } + var list = _Array.toList(v); + return "Array.fromList " + toString(list); + } + else if (v.ctor === "::") { + var output = '[' + toString(v._0); + v = v._1; + while (v.ctor === "::") { + output += "," + toString(v._0); + v = v._1; + } + return output + ']'; + } + else if (v.ctor === "[]") { + return "[]"; + } + else if (v.ctor === "RBNode" || v.ctor === "RBEmpty") { + if (!Dict) { + Dict = Elm.Dict.make(elm); + } + if (!List) { + List = Elm.List.make(elm); + } + var list = Dict.toList(v); + var name = "Dict"; + if (list.ctor === "::" && list._0._1.ctor === "_Tuple0") { + name = "Set"; + list = A2(List.map, function(x){return x._0}, list); + } + return name + ".fromList " + toString(list); + } + else { + var output = ""; + for (var i in v) { + if (i === 'ctor') continue; + var str = toString(v[i]); + var parenless = str[0] === '{' || str[0] === '<' || str.indexOf(' ') < 0; + output += ' ' + (parenless ? str : '(' + str + ')'); + } + return v.ctor + output; + } + } + if (type === 'object' && 'recv' in v) { + return '<signal>'; + } + return "<internal structure>"; + }; + + function addSlashes(str) { + return str.replace(/\\/g, '\\\\') + .replace(/\n/g, '\\n') + .replace(/\t/g, '\\t') + .replace(/\r/g, '\\r') + .replace(/\v/g, '\\v') + .replace(/\0/g, '\\0') + .replace(/\'/g, "\\'") + .replace(/\"/g, '\\"'); + } + + function probablyPublic(v) { + var keys = Object.keys(v); + var len = keys.length; + if (len === 3 + && 'props' in v + && 'element' in v) + { + return false; + } + else if (len === 5 + && 'horizontal' in v + && 'vertical' in v + && 'x' in v + && 'y' in v) + { + return false; + } + else if (len === 7 + && 'theta' in v + && 'scale' in v + && 'x' in v + && 'y' in v + && 'alpha' in v + && 'form' in v) + { + return false; + } + return true; + } + + return elm.Native.Show.values = { + toString: toString + }; +};
+ src/Native/Signal.js view
@@ -0,0 +1,235 @@+ +Elm.Native.Signal = {}; +Elm.Native.Signal.make = function(localRuntime) { + + localRuntime.Native = localRuntime.Native || {}; + localRuntime.Native.Signal = localRuntime.Native.Signal || {}; + if (localRuntime.Native.Signal.values) { + return localRuntime.Native.Signal.values; + } + + var Utils = Elm.Native.Utils.make(localRuntime); + + function broadcastToKids(node, timestep, changed) { + var kids = node.kids; + for (var i = kids.length; i--; ) { + kids[i].recv(timestep, changed, node.id); + } + } + + function Input(base) { + this.id = Utils.guid(); + this.value = base; + this.kids = []; + this.defaultNumberOfKids = 0; + this.recv = function(timestep, eid, v) { + var changed = eid === this.id; + if (changed) { + this.value = v; + } + broadcastToKids(this, timestep, changed); + return changed; + }; + localRuntime.inputs.push(this); + } + + function LiftN(update, args) { + this.id = Utils.guid(); + this.value = update(); + this.kids = []; + + var n = args.length; + var count = 0; + var isChanged = false; + + this.recv = function(timestep, changed, parentID) { + ++count; + if (changed) { isChanged = true; } + if (count == n) { + if (isChanged) { this.value = update(); } + broadcastToKids(this, timestep, isChanged); + isChanged = false; + count = 0; + } + }; + for (var i = n; i--; ) { args[i].kids.push(this); } + } + + function map(func, a) { + function update() { return func(a.value); } + return new LiftN(update, [a]); + } + function map2(func, a, b) { + function update() { return A2( func, a.value, b.value ); } + return new LiftN(update, [a,b]); + } + function map3(func, a, b, c) { + function update() { return A3( func, a.value, b.value, c.value ); } + return new LiftN(update, [a,b,c]); + } + function map4(func, a, b, c, d) { + function update() { return A4( func, a.value, b.value, c.value, d.value ); } + return new LiftN(update, [a,b,c,d]); + } + function map5(func, a, b, c, d, e) { + function update() { return A5( func, a.value, b.value, c.value, d.value, e.value ); } + return new LiftN(update, [a,b,c,d,e]); + } + + function Foldp(step, state, input) { + this.id = Utils.guid(); + this.value = state; + this.kids = []; + + this.recv = function(timestep, changed, parentID) { + if (changed) { + this.value = A2( step, input.value, this.value ); + } + broadcastToKids(this, timestep, changed); + }; + input.kids.push(this); + } + + function foldp(step, state, input) { + return new Foldp(step, state, input); + } + + function DropIf(pred,base,input) { + this.id = Utils.guid(); + this.value = pred(input.value) ? base : input.value; + this.kids = []; + this.recv = function(timestep, changed, parentID) { + var chng = changed && !pred(input.value); + if (chng) { this.value = input.value; } + broadcastToKids(this, timestep, chng); + }; + input.kids.push(this); + } + + function DropRepeats(input) { + this.id = Utils.guid(); + this.value = input.value; + this.kids = []; + this.recv = function(timestep, changed, parentID) { + var chng = changed && !Utils.eq(this.value,input.value); + if (chng) { this.value = input.value; } + broadcastToKids(this, timestep, chng); + }; + input.kids.push(this); + } + + function timestamp(a) { + function update() { return Utils.Tuple2(localRuntime.timer.now(), a.value); } + return new LiftN(update, [a]); + } + + function SampleOn(s1,s2) { + this.id = Utils.guid(); + this.value = s2.value; + this.kids = []; + + var count = 0; + var isChanged = false; + + this.recv = function(timestep, changed, parentID) { + if (parentID === s1.id) isChanged = changed; + ++count; + if (count == 2) { + if (isChanged) { this.value = s2.value; } + broadcastToKids(this, timestep, isChanged); + count = 0; + isChanged = false; + } + }; + s1.kids.push(this); + s2.kids.push(this); + } + + function sampleOn(s1,s2) { return new SampleOn(s1,s2); } + + function delay(t,s) { + var delayed = new Input(s.value); + var firstEvent = true; + function update(v) { + if (firstEvent) { + firstEvent = false; return; + } + setTimeout(function() { + localRuntime.notify(delayed.id, v); + }, t); + } + function first(a,b) { return a; } + return new SampleOn(delayed, map2(F2(first), delayed, map(update,s))); + } + + function Merge(s1,s2) { + this.id = Utils.guid(); + this.value = s1.value; + this.kids = []; + + var next = null; + var count = 0; + var isChanged = false; + + this.recv = function(timestep, changed, parentID) { + ++count; + if (changed) { + isChanged = true; + if (parentID == s2.id && next === null) { next = s2.value; } + if (parentID == s1.id) { next = s1.value; } + } + + if (count == 2) { + if (isChanged) { this.value = next; next = null; } + broadcastToKids(this, timestep, isChanged); + isChanged = false; + count = 0; + } + }; + s1.kids.push(this); + s2.kids.push(this); + } + + function merge(s1,s2) { + return new Merge(s1,s2); + } + + + // SIGNAL INPUTS + + function input(initialValue) { + return new Input(initialValue); + } + + function send(input, value) { + return function() { + localRuntime.notify(input.id, value); + }; + } + + function subscribe(input) { + return input; + } + + + return localRuntime.Native.Signal.values = { + constant : function(v) { return new Input(v); }, + map : F2(map ), + map2 : F3(map2), + map3 : F4(map3), + map4 : F5(map4), + map5 : F6(map5), + foldp : F3(foldp), + delay : F2(delay), + merge : F2(merge), + keepIf : F3(function(pred,base,sig) { + return new DropIf(function(x) {return !pred(x);},base,sig); }), + dropIf : F3(function(pred,base,sig) { return new DropIf(pred,base,sig); }), + dropRepeats : function(s) { return new DropRepeats(s);}, + sampleOn : F2(sampleOn), + timestamp : timestamp, + input: input, + send: F2(send), + subscribe: subscribe + }; +};
+ src/Native/String.js view
@@ -0,0 +1,269 @@+Elm.Native.String = {}; +Elm.Native.String.make = function(elm) { + elm.Native = elm.Native || {}; + elm.Native.String = elm.Native.String || {}; + if (elm.Native.String.values) return elm.Native.String.values; + if ('values' in Elm.Native.String) { + return elm.Native.String.values = Elm.Native.String.values; + } + + var Char = Elm.Char.make(elm); + var List = Elm.Native.List.make(elm); + var Maybe = Elm.Maybe.make(elm); + var Result = Elm.Result.make(elm); + var Utils = Elm.Native.Utils.make(elm); + + function isEmpty(str) { + return str.length === 0; + } + function cons(chr,str) { + return chr + str; + } + function uncons(str) { + var hd; + return (hd = str[0]) + ? Maybe.Just(Utils.Tuple2(Utils.chr(hd), str.slice(1))) + : Maybe.Nothing; + } + function append(a,b) { + return a + b; + } + function concat(strs) { + return List.toArray(strs).join(''); + } + function length(str) { + return str.length; + } + function map(f,str) { + var out = str.split(''); + for (var i = out.length; i--; ) { + out[i] = f(Utils.chr(out[i])); + } + return out.join(''); + } + function filter(pred,str) { + return str.split('').map(Utils.chr).filter(pred).join(''); + } + function reverse(str) { + return str.split('').reverse().join(''); + } + function foldl(f,b,str) { + var len = str.length; + for (var i = 0; i < len; ++i) { + b = A2(f, Utils.chr(str[i]), b); + } + return b; + } + function foldr(f,b,str) { + for (var i = str.length; i--; ) { + b = A2(f, Utils.chr(str[i]), b); + } + return b; + } + + function split(sep, str) { + return List.fromArray(str.split(sep)); + } + function join(sep, strs) { + return List.toArray(strs).join(sep); + } + function repeat(n, str) { + var result = ''; + while (n > 0) { + if (n & 1) result += str; + n >>= 1, str += str; + } + return result; + } + + function slice(start, end, str) { + return str.slice(start,end); + } + function left(n, str) { + return n < 1 ? "" : str.slice(0,n); + } + function right(n, str) { + return n < 1 ? "" : str.slice(-n); + } + function dropLeft(n, str) { + return n < 1 ? str : str.slice(n); + } + function dropRight(n, str) { + return n < 1 ? str : str.slice(0,-n); + } + + function pad(n,chr,str) { + var half = (n - str.length) / 2; + return repeat(Math.ceil(half),chr) + str + repeat(half|0,chr); + } + function padRight(n,chr,str) { + return str + repeat(n - str.length, chr); + } + function padLeft(n,chr,str) { + return repeat(n - str.length, chr) + str; + } + + function trim(str) { + return str.trim(); + } + function trimLeft(str) { + return str.trimLeft(); + } + function trimRight(str) { + return str.trimRight(); + } + + function words(str) { + return List.fromArray(str.trim().split(/\s+/g)); + } + function lines(str) { + return List.fromArray(str.split(/\r\n|\r|\n/g)); + } + + function toUpper(str) { + return str.toUpperCase(); + } + function toLower(str) { + return str.toLowerCase(); + } + + function any(pred, str) { + for (var i = str.length; i--; ) { + if (pred(Utils.chr(str[i]))) return true; + } + return false; + } + function all(pred, str) { + for (var i = str.length; i--; ) { + if (!pred(Utils.chr(str[i]))) return false; + } + return true; + } + + function contains(sub, str) { + return str.indexOf(sub) > -1; + } + function startsWith(sub, str) { + return str.indexOf(sub) === 0; + } + function endsWith(sub, str) { + return str.length >= sub.length && + str.lastIndexOf(sub) === str.length - sub.length; + } + function indexes(sub, str) { + var subLen = sub.length; + var i = 0; + var is = []; + while ((i = str.indexOf(sub, i)) > -1) { + is.push(i); + i = i + subLen; + } + return List.fromArray(is); + } + + function toInt(s) { + var len = s.length; + if (len === 0) { + return Result.Err("could not convert string '" + s + "' to an Int" ); + } + var start = 0; + if (s[0] == '-') { + if (len === 1) { + return Result.Err("could not convert string '" + s + "' to an Int" ); + } + start = 1; + } + for (var i = start; i < len; ++i) { + if (!Char.isDigit(s[i])) { + return Result.Err("could not convert string '" + s + "' to an Int" ); + } + } + return Result.Ok(parseInt(s, 10)); + } + + function toFloat(s) { + var len = s.length; + if (len === 0) { + return Result.Err("could not convert string '" + s + "' to a Float" ); + } + var start = 0; + if (s[0] == '-') { + if (len === 1) { + return Result.Err("could not convert string '" + s + "' to a Float" ); + } + start = 1; + } + var dotCount = 0; + for (var i = start; i < len; ++i) { + if (Char.isDigit(s[i])) { + continue; + } + if (s[i] === '.') { + dotCount += 1; + if (dotCount <= 1) { + continue; + } + } + return Result.Err("could not convert string '" + s + "' to a Float" ); + } + return Result.Ok(parseFloat(s)); + } + + function toList(str) { + return List.fromArray(str.split('').map(Utils.chr)); + } + function fromList(chars) { + return List.toArray(chars).join(''); + } + + return Elm.Native.String.values = { + isEmpty: isEmpty, + cons: F2(cons), + uncons: uncons, + append: F2(append), + concat: concat, + length: length, + map: F2(map), + filter: F2(filter), + reverse: reverse, + foldl: F3(foldl), + foldr: F3(foldr), + + split: F2(split), + join: F2(join), + repeat: F2(repeat), + + slice: F3(slice), + left: F2(left), + right: F2(right), + dropLeft: F2(dropLeft), + dropRight: F2(dropRight), + + pad: F3(pad), + padLeft: F3(padLeft), + padRight: F3(padRight), + + trim: trim, + trimLeft: trimLeft, + trimRight: trimRight, + + words: words, + lines: lines, + + toUpper: toUpper, + toLower: toLower, + + any: F2(any), + all: F2(all), + + contains: F2(contains), + startsWith: F2(startsWith), + endsWith: F2(endsWith), + indexes: F2(indexes), + + toInt: toInt, + toFloat: toFloat, + toList: toList, + fromList: fromList + }; +};
+ src/Native/Text.js view
@@ -0,0 +1,173 @@+Elm.Native.Text = {}; +Elm.Native.Text.make = function(elm) { + elm.Native = elm.Native || {}; + elm.Native.Text = elm.Native.Text || {}; + if (elm.Native.Text.values) return elm.Native.Text.values; + + var toCss = Elm.Native.Color.make(elm).toCss; + var Element = Elm.Graphics.Element.make(elm); + var NativeElement = Elm.Native.Graphics.Element.make(elm); + var List = Elm.Native.List.make(elm); + var Utils = Elm.Native.Utils.make(elm); + + function makeSpaces(s) { + if (s.length == 0) { return s; } + var arr = s.split(''); + if (arr[0] == ' ') { arr[0] = " " } + for (var i = arr.length; --i; ) { + if (arr[i][0] == ' ' && arr[i-1] == ' ') { + arr[i-1] = arr[i-1] + arr[i]; + arr[i] = ''; + } + } + for (var i = arr.length; i--; ) { + if (arr[i].length > 1 && arr[i][0] == ' ') { + var spaces = arr[i].split(''); + for (var j = spaces.length - 2; j >= 0; j -= 2) { + spaces[j] = ' '; + } + arr[i] = spaces.join(''); + } + } + arr = arr.join(''); + if (arr[arr.length-1] === " ") { + return arr.slice(0,-1) + ' '; + } + return arr; + } + + function properEscape(str) { + if (str.length == 0) return str; + str = str //.replace(/&/g, "&") + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/\n/g, "<br/>"); + var arr = str.split('<br/>'); + for (var i = arr.length; i--; ) { + arr[i] = makeSpaces(arr[i]); + } + return arr.join('<br/>'); + } + + function fromString(str) { + return Utils.txt(properEscape(str)); + } + + function append(xs, ys) { + return Utils.txt(Utils.makeText(xs) + Utils.makeText(ys)); + } + + // conversions from Elm values to CSS + function toTypefaces(list) { + var typefaces = List.toArray(list); + for (var i = typefaces.length; i--; ) { + var typeface = typefaces[i]; + if (typeface.indexOf(' ') > -1) { + typefaces[i] = "'" + typeface + "'"; + } + } + return typefaces.join(','); + } + function toLine(line) { + var ctor = line.ctor; + return ctor === 'Under' ? 'underline' : + ctor === 'Over' ? 'overline' : 'line-through'; + } + + // setting styles of Text + function style(style, text) { + var newText = '<span style="color:' + toCss(style.color) + ';' + if (style.typeface.ctor !== '[]') { + newText += 'font-family:' + toTypefaces(style.typeface) + ';' + } + if (style.height.ctor !== "Nothing") { + newText += 'font-size:' + style.height._0 + 'px;'; + } + if (style.bold) { + newText += 'font-weight:bold;'; + } + if (style.italic) { + newText += 'font-style:italic;'; + } + if (style.line.ctor !== 'Nothing') { + newText += 'text-decoration:' + toLine(style.line._0) + ';'; + } + newText += '">' + Utils.makeText(text) + '</span>' + return Utils.txt(newText); + } + function height(px, text) { + return { style: 'font-size:' + px + 'px;', text:text } + } + function typeface(names, text) { + return { style: 'font-family:' + toTypefaces(names) + ';', text:text } + } + function monospace(text) { + return { style: 'font-family:monospace;', text:text } + } + function italic(text) { + return { style: 'font-style:italic;', text:text } + } + function bold(text) { + return { style: 'font-weight:bold;', text:text } + } + function link(href, text) { + return { href: fromString(href), text:text }; + } + function line(line, text) { + return { style: 'text-decoration:' + toLine(line) + ';', text:text }; + } + + function color(color, text) { + return { style: 'color:' + toCss(color) + ';', text:text }; + } + + function block(align) { + return function(text) { + var raw = { + ctor :'RawHtml', + html : Utils.makeText(text), + align: align, + guid : null + }; + var pos = A2(NativeElement.htmlHeight, 0, raw); + return A3(Element.newElement, pos._0, pos._1, raw); + } + } + + function markdown(text, guid) { + var raw = { + ctor:'RawHtml', + html: text, + align: null, + guid: guid + }; + var pos = A2(NativeElement.htmlHeight, 0, raw); + return A3(Element.newElement, pos._0, pos._1, raw); + } + + return elm.Native.Text.values = { + fromString: fromString, + append: F2(append), + + height : F2(height), + italic : italic, + bold : bold, + line : F2(line), + monospace : monospace, + typeface : F2(typeface), + color : F2(color), + link : F2(link), + style : F2(style), + + leftAligned : block('left'), + rightAligned : block('right'), + centered : block('center'), + justified : block('justify'), + markdown : markdown, + + toTypefaces:toTypefaces, + toLine:toLine + }; +};
+ src/Native/Time.js view
@@ -0,0 +1,64 @@+Elm.Native.Time = {}; +Elm.Native.Time.make = function(elm) { + + elm.Native = elm.Native || {}; + elm.Native.Time = elm.Native.Time || {}; + if (elm.Native.Time.values) return elm.Native.Time.values; + + var Signal = Elm.Signal.make(elm); + var NS = Elm.Native.Signal.make(elm); + var Maybe = Elm.Maybe.make(elm); + var Utils = Elm.Native.Utils.make(elm); + + function fpsWhen(desiredFPS, isOn) { + var msPerFrame = 1000 / desiredFPS; + var prev = elm.timer.now(), curr = prev, diff = 0, wasOn = true; + var ticker = NS.input(diff); + function tick(zero) { + return function() { + curr = elm.timer.now(); + diff = zero ? 0 : curr - prev; + if (prev > curr) { + diff = 0; + } + prev = curr; + elm.notify(ticker.id, diff); + }; + } + var timeoutID = 0; + function f(isOn, t) { + if (isOn) { + timeoutID = elm.setTimeout(tick(!wasOn && isOn), msPerFrame); + } else if (wasOn) { + clearTimeout(timeoutID); + } + wasOn = isOn; + return t; + } + return A3( Signal.map2, F2(f), isOn, ticker ); + } + + function every(t) { + var clock = NS.input(elm.timer.now()); + function tellTime() { + elm.notify(clock.id, elm.timer.now()); + } + setInterval(tellTime, t); + return clock; + } + + function read(s) { + var t = Date.parse(s); + return isNaN(t) ? Maybe.Nothing : Maybe.Just(t); + } + return elm.Native.Time.values = { + fpsWhen : F2(fpsWhen), + fps : function(t) { return fpsWhen(t, Signal.constant(true)); }, + every : every, + delay : NS.delay, + timestamp : NS.timestamp, + toDate : function(t) { return new window.Date(t); }, + read : read + }; + +};
+ src/Native/Touch.js view
@@ -0,0 +1,171 @@+Elm.Native = Elm.Native || {}; +Elm.Native.Touch = {}; +Elm.Native.Touch.make = function(localRuntime) { + + localRuntime.Native = localRuntime.Native || {}; + localRuntime.Native.Touch = localRuntime.Native.Touch || {}; + if (localRuntime.Native.Touch.values) { + return localRuntime.Native.Touch.values; + } + + var Signal = Elm.Signal.make(localRuntime); + var List = Elm.Native.List.make(localRuntime); + var Utils = Elm.Native.Utils.make(localRuntime); + + function Dict() { + this.keys = []; + this.values = []; + + this.insert = function(key,value) { + this.keys.push(key); + this.values.push(value); + }; + this.lookup = function(key) { + var i = this.keys.indexOf(key) + return i >= 0 ? this.values[i] : {x:0,y:0,t:0}; + }; + this.remove = function(key) { + var i = this.keys.indexOf(key); + if (i < 0) return; + var t = this.values[i]; + this.keys.splice(i,1); + this.values.splice(i,1); + return t; + }; + this.clear = function() { + this.keys = []; + this.values = []; + }; + } + + var root = Signal.constant([]), + tapTime = 500, + hasTap = false, + tap = {_:{},x:0,y:0}, + dict = new Dict(); + + function touch(t) { + var r = dict.lookup(t.identifier); + var point = Utils.getXY(t); + return { + _ : {}, + id: t.identifier, + x : point._0, + y : point._1, + x0: r.x, + y0: r.y, + t0: r.t + }; + } + + var node = localRuntime.isFullscreen() + ? document + : localRuntime.node; + + function start(e) { + var point = Utils.getXY(e); + dict.insert(e.identifier, { + x: point._0, + y: point._1, + t: localRuntime.timer.now() + }); + } + function end(e) { + var t = dict.remove(e.identifier); + if (localRuntime.timer.now() - t.t < tapTime) { + hasTap = true; + tap = { + _: {}, + x: t.x, + y: t.y + }; + } + } + + function listen(name, f) { + function update(e) { + for (var i = e.changedTouches.length; i--; ) { + f(e.changedTouches[i]); + } + var ts = new Array(e.touches.length); + for (var i = e.touches.length; i--; ) { + ts[i] = touch(e.touches[i]); + } + localRuntime.notify(root.id, ts); + e.preventDefault(); + } + localRuntime.addListener([root.id], node, name, update); + } + + listen("touchstart", start); + listen("touchmove", function(_){}); + listen("touchend", end); + listen("touchcancel", end); + listen("touchleave", end); + + var mouseID = -1; + function move(e) { + var point = Utils.getXY(e); + for (var i = root.value.length; i--; ) { + if (root.value[i].id === mouseID) { + root.value[i].x = point._0; + root.value[i].y = point._1; + localRuntime.notify(root.id, root.value); + break; + } + } + } + localRuntime.addListener([root.id], node, "mousedown", function down(e) { + node.addEventListener("mousemove", move); + e.identifier = mouseID; + start(e); + root.value.push(touch(e)); + localRuntime.notify(root.id, root.value); + }); + localRuntime.addListener([root.id], document, "mouseup", function up(e) { + node.removeEventListener("mousemove", move); + e.identifier = mouseID; + end(e); + for (var i = root.value.length; i--; ) { + if (root.value[i].id === mouseID) { + root.value.splice(i, 1); + --mouseID; + break; + } + } + localRuntime.notify(root.id, root.value); + }); + localRuntime.addListener([root.id], node, "blur", function blur(e) { + node.removeEventListener("mousemove", move); + if (root.value.length > 0) { + localRuntime.notify(root.id, []); + --mouseID; + } + dict.clear(); + }); + + function dependency(f) { + var sig = A2( Signal.map, f, root ); + root.defaultNumberOfKids += 1; + sig.defaultNumberOfKids = 0; + return sig; + } + + var touches = dependency(List.fromArray); + + var taps = function() { + var sig = dependency(function(_) { return tap; }); + sig.defaultNumberOfKids = 1; + function pred(_) { + var b = hasTap; + hasTap = false; + return b; + } + var sig2 = A3( Signal.keepIf, pred, {_:{},x:0,y:0}, sig); + sig2.defaultNumberOfKids = 0; + return sig2; + }(); + + return localRuntime.Native.Touch.values = { touches: touches, taps: taps }; + +};
+ src/Native/Trampoline.js view
@@ -0,0 +1,24 @@+Elm.Native.Trampoline = {}; +Elm.Native.Trampoline.make = function(elm) { + elm.Native = elm.Native || {}; + elm.Native.Trampoline = elm.Native.Trampoline || {}; + if (elm.Native.Trampoline.values) return elm.Native.Trampoline.values; + + // trampoline : Trampoline a -> a + function trampoline(t) { + var tramp = t; + while(true) { + switch(tramp.ctor) { + case "Done": + return tramp._0; + case "Continue": + tramp = tramp._0({ctor: "_Tuple0"}); + continue; + } + } + } + + return elm.Native.Trampoline.values = { + trampoline:trampoline + }; +};
+ src/Native/Transform2D.js view
@@ -0,0 +1,99 @@+Elm.Native.Transform2D = {}; +Elm.Native.Transform2D.make = function(elm) { + + elm.Native = elm.Native || {}; + elm.Native.Transform2D = elm.Native.Transform2D || {}; + if (elm.Native.Transform2D.values) return elm.Native.Transform2D.values; + + var A; + if (typeof Float32Array === 'undefined') { + A = function(arr) { + this.length = arr.length; + this[0] = arr[0]; + this[1] = arr[1]; + this[2] = arr[2]; + this[3] = arr[3]; + this[4] = arr[4]; + this[5] = arr[5]; + }; + } else { + A = Float32Array; + } + + // layout of matrix in an array is + // + // | m11 m12 dx | + // | m21 m22 dy | + // | 0 0 1 | + // + // new A([ m11, m12, dx, m21, m22, dy ]) + + var identity = new A([1,0,0,0,1,0]); + function matrix(m11, m12, m21, m22, dx, dy) { + return new A([m11, m12, dx, m21, m22, dy]); + } + function rotation(t) { + var c = Math.cos(t); + var s = Math.sin(t); + return new A([c, -s, 0, s, c, 0]); + } + function rotate(t,m) { + var c = Math.cos(t); + var s = Math.sin(t); + var m11 = m[0], m12 = m[1], m21 = m[3], m22 = m[4]; + return new A([m11*c + m12*s, -m11*s + m12*c, m[2], + m21*c + m22*s, -m21*s + m22*c, m[5]]); + } + /* + function move(xy,m) { + var x = xy._0; + var y = xy._1; + var m11 = m[0], m12 = m[1], m21 = m[3], m22 = m[4]; + return new A([m11, m12, m11*x + m12*y + m[2], + m21, m22, m21*x + m22*y + m[5]]); + } + function scale(s,m) { return new A([m[0]*s, m[1]*s, m[2], m[3]*s, m[4]*s, m[5]]); } + function scaleX(x,m) { return new A([m[0]*x, m[1], m[2], m[3]*x, m[4], m[5]]); } + function scaleY(y,m) { return new A([m[0], m[1]*y, m[2], m[3], m[4]*y, m[5]]); } + function reflectX(m) { return new A([-m[0], m[1], m[2], -m[3], m[4], m[5]]); } + function reflectY(m) { return new A([m[0], -m[1], m[2], m[3], -m[4], m[5]]); } + + function transform(m11, m21, m12, m22, mdx, mdy, n) { + var n11 = n[0], n12 = n[1], n21 = n[3], n22 = n[4], ndx = n[2], ndy = n[5]; + return new A([m11*n11 + m12*n21, + m11*n12 + m12*n22, + m11*ndx + m12*ndy + mdx, + m21*n11 + m22*n21, + m21*n12 + m22*n22, + m21*ndx + m22*ndy + mdy]); + } + */ + function multiply(m, n) { + var m11 = m[0], m12 = m[1], m21 = m[3], m22 = m[4], mdx = m[2], mdy = m[5]; + var n11 = n[0], n12 = n[1], n21 = n[3], n22 = n[4], ndx = n[2], ndy = n[5]; + return new A([m11*n11 + m12*n21, + m11*n12 + m12*n22, + m11*ndx + m12*ndy + mdx, + m21*n11 + m22*n21, + m21*n12 + m22*n22, + m21*ndx + m22*ndy + mdy]); + } + + return elm.Native.Transform2D.values = { + identity:identity, + matrix:F6(matrix), + rotation:rotation, + multiply:F2(multiply) + /* + transform:F7(transform), + rotate:F2(rotate), + move:F2(move), + scale:F2(scale), + scaleX:F2(scaleX), + scaleY:F2(scaleY), + reflectX:reflectX, + reflectY:reflectY + */ + }; + +};
+ src/Native/Utils.js view
@@ -0,0 +1,305 @@+Elm.Native = Elm.Native || {}; +Elm.Native.Utils = {}; +Elm.Native.Utils.make = function(localRuntime) { + + localRuntime.Native = localRuntime.Native || {}; + localRuntime.Native.Utils = localRuntime.Native.Utils || {}; + if (localRuntime.Native.Utils.values) { + return localRuntime.Native.Utils.values; + } + + function eq(l,r) { + var stack = [{'x': l, 'y': r}] + while (stack.length > 0) { + var front = stack.pop(); + var x = front.x; + var y = front.y; + if (x === y) continue; + if (typeof x === "object") { + var c = 0; + for (var i in x) { + ++c; + if (i in y) { + if (i !== 'ctor') { + stack.push({ 'x': x[i], 'y': y[i] }); + } + } else { + return false; + } + } + if ('ctor' in x) { + stack.push({'x': x.ctor, 'y': y.ctor}); + } + if (c !== Object.keys(y).length) { + return false; + }; + } else if (typeof x === 'function') { + throw new Error('Equality error: general function equality is ' + + 'undecidable, and therefore, unsupported'); + } else { + return false; + } + } + return true; + } + + // code in Generate/JavaScript.hs depends on the particular + // integer values assigned to LT, EQ, and GT + var LT = -1, EQ = 0, GT = 1, ord = ['LT','EQ','GT']; + function compare(x,y) { return { ctor: ord[cmp(x,y)+1] } } + function cmp(x,y) { + var ord; + if (typeof x !== 'object'){ + return x === y ? EQ : x < y ? LT : GT; + } + else if (x.isChar){ + var a = x.toString(); + var b = y.toString(); + return a === b ? EQ : a < b ? LT : GT; + } + else if (x.ctor === "::" || x.ctor === "[]") { + while (true) { + if (x.ctor === "[]" && y.ctor === "[]") return EQ; + if (x.ctor !== y.ctor) return x.ctor === '[]' ? LT : GT; + ord = cmp(x._0, y._0); + if (ord !== EQ) return ord; + x = x._1; + y = y._1; + } + } + else if (x.ctor.slice(0,6) === '_Tuple') { + var n = x.ctor.slice(6) - 0; + var err = 'cannot compare tuples with more than 6 elements.'; + if (n === 0) return EQ; + if (n >= 1) { ord = cmp(x._0, y._0); if (ord !== EQ) return ord; + if (n >= 2) { ord = cmp(x._1, y._1); if (ord !== EQ) return ord; + if (n >= 3) { ord = cmp(x._2, y._2); if (ord !== EQ) return ord; + if (n >= 4) { ord = cmp(x._3, y._3); if (ord !== EQ) return ord; + if (n >= 5) { ord = cmp(x._4, y._4); if (ord !== EQ) return ord; + if (n >= 6) { ord = cmp(x._5, y._5); if (ord !== EQ) return ord; + if (n >= 7) throw new Error('Comparison error: ' + err); } } } } } } + return EQ; + } + else { + throw new Error('Comparison error: comparison is only defined on ints, ' + + 'floats, times, chars, strings, lists of comparable values, ' + + 'and tuples of comparable values.'); + } + } + + + var Tuple0 = { ctor: "_Tuple0" }; + function Tuple2(x,y) { + return { + ctor: "_Tuple2", + _0: x, + _1: y + }; + } + + function chr(c) { + var x = new String(c); + x.isChar = true; + return x; + } + + function txt(str) { + var t = new String(str); + t.text = true; + return t; + } + + function makeText(text) { + var style = ''; + var href = ''; + while (true) { + if (text.style) { + style += text.style; + text = text.text; + continue; + } + if (text.href) { + href = text.href; + text = text.text; + continue; + } + if (href) { + text = '<a href="' + href + '">' + text + '</a>'; + } + if (style) { + text = '<span style="' + style + '">' + text + '</span>'; + } + return text; + } + } + + var count = 0; + function guid(_) { + return count++ + } + + function copy(oldRecord) { + var newRecord = {}; + for (var key in oldRecord) { + var value = key === '_' + ? copy(oldRecord._) + : oldRecord[key] + ; + newRecord[key] = value; + } + return newRecord; + } + + function remove(key, oldRecord) { + var record = copy(oldRecord); + if (key in record._) { + record[key] = record._[key][0]; + record._[key] = record._[key].slice(1); + if (record._[key].length === 0) { + delete record._[key]; + } + } else { + delete record[key]; + } + return record; + } + + function replace(keyValuePairs, oldRecord) { + var record = copy(oldRecord); + for (var i = keyValuePairs.length; i--; ) { + var pair = keyValuePairs[i]; + record[pair[0]] = pair[1]; + } + return record; + } + + function insert(key, value, oldRecord) { + var newRecord = copy(oldRecord); + if (key in newRecord) { + var values = newRecord._[key]; + var copiedValues = values ? values.slice(0) : []; + newRecord._[key] = [newRecord[key]].concat(copiedValues); + } + newRecord[key] = value; + return newRecord; + } + + function getXY(e) { + var posx = 0; + var posy = 0; + if (e.pageX || e.pageY) { + posx = e.pageX; + posy = e.pageY; + } else if (e.clientX || e.clientY) { + posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; + posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; + } + + if (localRuntime.isEmbed()) { + var rect = localRuntime.node.getBoundingClientRect(); + var relx = rect.left + document.body.scrollLeft + document.documentElement.scrollLeft; + var rely = rect.top + document.body.scrollTop + document.documentElement.scrollTop; + // TODO: figure out if there is a way to avoid rounding here + posx = posx - Math.round(relx) - localRuntime.node.clientLeft; + posy = posy - Math.round(rely) - localRuntime.node.clientTop; + } + return Tuple2(posx, posy); + } + + + //// LIST STUFF //// + + var Nil = { ctor:'[]' }; + + function Cons(hd,tl) { + return { + ctor: "::", + _0: hd, + _1: tl + }; + } + + function append(xs,ys) { + // append Text + if (xs.text || ys.text) { + return txt(makeText(xs) + makeText(ys)); + } + + // append Strings + if (typeof xs === "string") { + return xs + ys; + } + + // append Lists + if (xs.ctor === '[]') { + return ys; + } + var root = Cons(xs._0, Nil); + var curr = root; + xs = xs._1; + while (xs.ctor !== '[]') { + curr._1 = Cons(xs._0, Nil); + xs = xs._1; + curr = curr._1; + } + curr._1 = ys; + return root; + } + + //// RUNTIME ERRORS //// + + function indent(lines) { + return '\n' + lines.join('\n'); + } + + function badCase(moduleName, span) { + var msg = indent([ + 'Non-exhaustive pattern match in case-expression.', + 'Make sure your patterns cover every case!' + ]); + throw new Error('Runtime error in module ' + moduleName + ' (' + span + ')' + msg); + } + + function badIf(moduleName, span) { + var msg = indent([ + 'Non-exhaustive pattern match in multi-way-if expression.', + 'It is best to use \'otherwise\' as the last branch of multi-way-if.' + ]); + throw new Error('Runtime error in module ' + moduleName + ' (' + span + ')' + msg); + } + + + function badPort(expected, received) { + var msg = indent([ + 'Expecting ' + expected + ' but was given ', + JSON.stringify(received) + ]); + throw new Error('Runtime error when sending values through a port.' + msg); + } + + + return localRuntime.Native.Utils.values = { + eq:eq, + cmp:cmp, + compare:F2(compare), + Tuple0:Tuple0, + Tuple2:Tuple2, + chr:chr, + txt:txt, + makeText:makeText, + copy: copy, + remove: remove, + replace: replace, + insert: insert, + guid: guid, + getXY: getXY, + + Nil: Nil, + Cons: Cons, + append: F2(append), + + badCase: badCase, + badIf: badIf, + badPort: badPort + }; +};
+ src/Native/WebSocket.js view
@@ -0,0 +1,36 @@+Elm.Native.WebSocket = {}; +Elm.Native.WebSocket.make = function(elm) { + + elm.Native = elm.Native || {}; + elm.Native.WebSocket = elm.Native.WebSocket || {}; + if (elm.Native.WebSocket.values) return elm.Native.WebSocket.values; + + var Signal = Elm.Signal.make(elm); + var List = Elm.Native.List.make(elm); + + function open(url, outgoing) { + var incoming = Signal.constant(""); + var ws = new WebSocket(url); + + var pending = []; + var ready = false; + + ws.onopen = function(e) { + var len = pending.length; + for (var i = 0; i < len; ++i) { ws.send(pending[i]); } + ready = true; + }; + ws.onmessage = function(event) { + elm.notify(incoming.id, event.data); + }; + + function send(msg) { + ready ? ws.send(msg) : pending.push(msg); + } + + function take1(x,y) { return x } + return A3(Signal.map2, F2(take1), incoming, A2(Signal.map, send, outgoing)); + } + + return elm.Native.WebSocket.values = { connect: F2(open) }; +};
+ src/Native/Window.js view
@@ -0,0 +1,62 @@+Elm.Native = Elm.Native || {}; +Elm.Native.Window = {}; +Elm.Native.Window.make = function(localRuntime) { + + localRuntime.Native = localRuntime.Native || {}; + localRuntime.Native.Window = localRuntime.Native.Window || {}; + if (localRuntime.Native.Window.values) { + return localRuntime.Native.Window.values; + } + + var Signal = Elm.Signal.make(localRuntime); + var NS = Elm.Native.Signal.make(localRuntime); + var Tuple2 = Elm.Native.Utils.make(localRuntime).Tuple2; + + function getWidth() { + return localRuntime.node.clientWidth; + } + function getHeight() { + if (localRuntime.isFullscreen()) { + return window.innerHeight; + } + return localRuntime.node.clientHeight; + } + + var dimensions = NS.input(Tuple2(getWidth(), getHeight())); + dimensions.defaultNumberOfKids = 2; + + // Do not move width and height into Elm. By setting the default number of kids, + // the resize listener can be detached. + var width = A2(Signal.map, function(p){return p._0;}, dimensions); + width.defaultNumberOfKids = 0; + + var height = A2(Signal.map, function(p){return p._1;}, dimensions); + height.defaultNumberOfKids = 0; + + function resizeIfNeeded() { + // Do not trigger event if the dimensions have not changed. + // This should be most of the time. + var w = getWidth(); + var h = getHeight(); + if (dimensions.value._0 === w && dimensions.value._1 === h) return; + + setTimeout(function () { + // Check again to see if the dimensions have changed. + // It is conceivable that the dimensions have changed + // again while some other event was being processed. + var w = getWidth(); + var h = getHeight(); + if (dimensions.value._0 === w && dimensions.value._1 === h) return; + localRuntime.notify(dimensions.id, Tuple2(w,h)); + }, 0); + } + localRuntime.addListener([dimensions.id], window, 'resize', resizeIfNeeded); + + return localRuntime.Native.Window.values = { + dimensions: dimensions, + width: width, + height: height, + resizeIfNeeded: resizeIfNeeded + }; + +};
+ src/Random.elm view
@@ -0,0 +1,314 @@+module Random + ( Generator, Seed + , int, float + , list, pair + , minInt, maxInt + , generate, initialSeed + , customGenerator + ) + where + +{-| This library helps you generate pseudo-random values. + +The general pattern is to define a `Generator` which can produce certain kinds +of random values. You actually produce random values by feeding a fresh `Seed` +to your `Generator`. + +Since you need a fresh `Seed` to produce more random values, you should +probably store a `Seed` in your application's state. This will allow you to +keep updating it as you generate random values and fresh seeds. + +The following example models a bunch of bad guys that randomly appear. The +`possiblyAddBadGuy` function uses the random seed to see if we should add a bad +guy, and if so, it places a bad guy at a randomly generated point. + + type alias Model = + { badGuys : List (Float,Float) + , seed : Seed + } + + possiblyAddBadGuy : Model -> Model + possiblyAddBadGuy model = + let (addProbability, seed') = + generate (float 0 1) model.seed + in + if addProbability < 0.9 + then + { model | + seed <- seed' + } + else + let (position, seed'') = + generate (pair (float 0 100) (float 0 100)) seed' + in + { model | + badGuys <- position :: model.badGuys + seed <- seed'' + } + +Details: This is an implemenation of the Portable Combined Generator of +L'Ecuyer for 32-bit computers. It is almost a direct translation from the +[System.Random](http://hackage.haskell.org/package/random-1.0.1.1/docs/System-Random.html) +module. It has a period of roughly 2.30584e18. + +# Generators + +@docs int, float, pair, list + +# Running a Generator + +@docs generate, initialSeed + +# Constants + +@docs maxInt, minInt + +# Custom Generators + +@docs customGenerator + +-} + +import Basics (..) +import List +import List ((::)) + + +{-| Create a generator that produces 32-bit integers in a given range. This +function *can* produce values outside of the range [minInt, maxInt] but +sufficient randomness is not guaranteed. + + int 0 10 -- an integer between zero and ten + int -5 5 -- an integer between -5 and 5 + + int minInt maxInt -- an integer in the widest range feasible +-} +int : Int -> Int -> Generator Int +int a b = + Generator <| \seed -> + let (lo,hi) = if a < b then (a,b) else (b,a) + + k = hi - lo + 1 + -- 2^31 - 87 + base = 2147483561 + n = iLogBase base k + + f n acc state = + case n of + 0 -> (acc, state) + _ -> let (x, state') = seed.next state + in f (n - 1) (x + acc * base) state' + + (v, state') = f n 1 seed.state + in + (lo + v % k, { seed | state <- state' }) + + +iLogBase : Int -> Int -> Int +iLogBase b i = + if i < b then 1 else 1 + iLogBase b (i // b) + + +{-| The maximum value for randomly generated 32-bit ints. -} +maxInt : Int +maxInt = 2147483647 + + +{-| The minimum value for randomly generated 32-bit ints. -} +minInt : Int +minInt = -2147483648 + + +{-| Create a generator that produces floats in a given range. + + probability : Generator Float + probability = + float 0 1 + + -- generate probability seed0 ==> (0.51, seed1) + -- generate probability seed1 ==> (0.04, seed2) +-} +float : Float -> Float -> Generator Float +float a b = + Generator <| \seed -> + let (lo, hi) = if a < b then (a,b) else (b,a) + + (number, seed') = + generate (int minInt maxInt) seed + + negativeOneToOne = + toFloat number / toFloat (maxInt - minInt) + + scaled = (lo+hi)/2 + ((hi-lo) * negativeOneToOne) + in + (scaled, seed') + + +-- DATA STRUCTURES + +{-| Create a pair of random values. A common use of this might be to generate +a point in a certain 2D space. Imagine we have a collage that is 400 pixels +wide and 200 pixels tall. + + randomPoint : Generator (Int,Int) + randomPoint = + pair (int -200 200) (int -100 100) + +-} +pair : Generator a -> Generator b -> Generator (a,b) +pair (Generator genLeft) (Generator genRight) = + Generator <| \seed -> + let (left , seed' ) = genLeft seed + (right, seed'') = genRight seed' + in + ((left,right), seed'') + + +{-| Create a list of random values. + + floatList : Generator (List Float) + floatList = + list 10 (float 0 1) + + intList : Generator (List Int) + intList = + list 5 (int 0 100) + + intPairs : Generator (List (Int, Int)) + intPairs = + list 10 (pair int int) +-} +list : Int -> Generator a -> Generator (List a) +list n (Generator generate) = + Generator <| \seed -> + listHelp [] n generate seed + + +listHelp : List a -> Int -> (Seed -> (a,Seed)) -> Seed -> (List a, Seed) +listHelp list n generate seed = + if n < 1 + then (List.reverse list, seed) + else + let (value, seed') = generate seed + in listHelp (value :: list) (n-1) generate seed' + + +{-| Create a custom generator. You provide a function that takes a seed, and +returns a random value and a new seed. You can use this to create custom +generators not covered by the basic functions in this library. + + pairOf : Generator a -> Generator (a,a) + pairOf generator = + customGenerator <| \seed -> + let (left , seed' ) = generate generator seed + (right, seed'') = generate generator seed' + in + ((left,right), seed'') + +-} +customGenerator : (Seed -> (a, Seed)) -> Generator a +customGenerator generate = + Generator generate + + +{-| A `Generator` is a function that takes a seed, and then returns a random +value and a new seed. The new seed is used to generate new random values. +-} +type Generator a = + Generator (Seed -> (a, Seed)) + +type State = State Int Int + +type alias Seed = + { state : State + , next : State -> (Int, State) + , split : State -> (State, State) + , range : State -> (Int,Int) + } + +{-| Run a random value generator with a given seed. It will give you back a +random value and a new seed. + + seed0 = initialSeed 31415 + + -- generate (int 0 100) seed0 ==> (42, seed1) + -- generate (int 0 100) seed1 ==> (31, seed2) + -- generate (int 0 100) seed2 ==> (99, seed3) + +Notice that we use different seeds on each line. This is important! If you use +the same seed, you get the same results. + + -- generate (int 0 100) seed0 ==> (42, seed1) + -- generate (int 0 100) seed0 ==> (42, seed1) + -- generate (int 0 100) seed0 ==> (42, seed1) +-} +generate : Generator a -> Seed -> (a, Seed) +generate (Generator generator) seed = + generator seed + + +{-| Create a “seed” of randomness which makes it possible to +generate random values. If you use the same seed many times, it will result +in the same thing every time! A good way to get an unexpected seed is to use +the current time. +-} +initialSeed : Int -> Seed +initialSeed n = + Seed (initState n) next split range + + +{-| Produce the initial generator state. Distinct arguments should be likely +to produce distinct generator states. +-} +initState : Int -> State +initState s' = + let s = max s' -s' + q = s // (magicNum6-1) + s1 = s % (magicNum6-1) + s2 = q % (magicNum7-1) + in + State (s1+1) (s2+1) + + +magicNum0 = 40014 +magicNum1 = 53668 +magicNum2 = 12211 +magicNum3 = 52774 +magicNum4 = 40692 +magicNum5 = 3791 +magicNum6 = 2147483563 +magicNum7 = 2137383399 +magicNum8 = 2147483562 + + +next : State -> (Int, State) +next (State s1 s2) = + -- Div always rounds down and so random numbers are biased + -- ideally we would use division that rounds towards zero so + -- that in the negative case it rounds up and in the positive case + -- it rounds down. Thus half the time it rounds up and half the time it + -- rounds down + let k = s1 // magicNum1 + s1' = magicNum0 * (s1 - k * magicNum1) - k * magicNum2 + s1'' = if s1' < 0 then s1' + magicNum6 else s1' + k' = s2 // magicNum3 + s2' = magicNum4 * (s2 - k' * magicNum3) - k' * magicNum5 + s2'' = if s2' < 0 then s2' + magicNum7 else s2' + z = s1'' - s2'' + z' = if z < 1 then z + magicNum8 else z + in + (z', State s1'' s2'') + + +split : State -> (State, State) +split (State s1 s2 as std) = + let new_s1 = if s1 == magicNum6-1 then 1 else s1 + 1 + new_s2 = if s2 == 1 then magicNum7-1 else s2 - 1 + (State t1 t2) = snd (next std) + in + (State new_s1 t2, State t1 new_s2) + + +range : State -> (Int,Int) +range _ = + (0, magicNum8)
+ src/Regex.elm view
@@ -0,0 +1,123 @@+module Regex where +{-| A library for working with regular expressions. It uses [the +same kind of regular expressions accepted by JavaScript](https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions). + +# Create +@docs regex, escape, caseInsensitive + +# Helpful Data Structures + +These data structures are needed to help define functions like [`find`](#find) +and [`replace`](#replace). + +@docs HowMany, Match + +# Use +@docs contains, find, replace, split + +-} + +import Maybe (Maybe) +import Native.Regex + +type Regex = Regex + +{-| Escape strings to be regular expressions, making all special characters +safe. So `regex (escape "^a+")` will match exactly `"^a+"` instead of a series +of `a`’s that start at the beginning of the line. +-} +escape : String -> String +escape = Native.Regex.escape + +{-| Create a Regex that matches patterns [as specified in JavaScript](https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions#Writing_a_Regular_Expression_Pattern). + +Be careful to escape backslashes properly! For example, `"\w"` is escaping the +letter `w` which is probably not what you want. You probably want `"\\w"` +instead, which escapes the backslash. +-} +regex : String -> Regex +regex = Native.Regex.regex + + +{-| Make a regex case insensitive -} +caseInsensitive : Regex -> Regex +caseInsensitive = Native.Regex.caseInsensitive + +{-| Check to see if a Regex is contained in a string. + + contains (regex "123") "12345" == True + contains (regex "b+") "aabbcc" == True + + contains (regex "789") "12345" == False + contains (regex "z+") "aabbcc" == False +-} +contains : Regex -> String -> Bool +contains = Native.Regex.contains + +{-| A `Match` represents all of the details about a particular match in a string. +Here are details on each field: + + * `match` — the full string of the match. + * `submatches` — a regex might have [subpatterns, surrounded by + parentheses](https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions#Using_Parenthesized_Substring_Matches). + If there are N subpatterns, there will be N elements in the `submatches` list. + Each submatch in this list is a `Maybe` because not all subpatterns may trigger. + For example, `(regex "(a+)|(b+)")` will either match many `a`’s or + many `b`’s, but never both. + * `index` — the index of the match in the original string. + * `number` — if you find many matches, you can think of each one + as being labeled with a `number` starting at one. So the first time you + find a match, that is match `number` one. Second time is match `number` two. + This is useful when paired with `replace All` if replacement is dependent on how + many times a pattern has appeared before. +-} +type alias Match = + { match : String + , submatches : List (Maybe String) + , index : Int + , number : Int + } + +{-| `HowMany` is used to specify how many matches you want to make. So +`replace All` would replace every match, but `replace (AtMost 2)` would +replace at most two matches (i.e. zero, one, two, but never three or more). +-} +type HowMany = All | AtMost Int + +{-| Find matches in a string: + + findTwoCommas = find (AtMost 2) (regex ",") + + -- map .index (findTwoCommas "a,b,c,d,e") == [1,3] + -- map .index (findTwoCommas "a b c d e") == [] + + places = find All (regex "[oi]n a (\\w+)") "I am on a boat in a lake." + + -- map .match places == ["on a boat", "in a lake"] + -- map .submatches places == [ [Just "boat"], [Just "lake"] ] +-} +find : HowMany -> Regex -> String -> List Match +find = Native.Regex.find + +{-| Replace matches. The function from `Match` to `String` lets +you use the details of a specific match when making replacements. + + devowel = replace All (regex "[aeiou]") (\_ -> "") + + -- devowel "The quick brown fox" == "Th qck brwn fx" + + reverseWords = replace All (regex "\\w+") (\{match} -> String.reverse match) + + -- reverseWords "deliver mined parts" == "reviled denim strap" +-} +replace : HowMany -> Regex -> (Match -> String) -> String -> String +replace = Native.Regex.replace + +{-| Split a string, using the regex as the separator. + + split (AtMost 1) (regex ",") "tom,99,90,85" == ["tom","99,90,85"] + + split All (regex ",") "a,b,c,d" == ["a","b","c","d"] +-} +split : HowMany -> Regex -> String -> List String +split = Native.Regex.split
+ src/Result.elm view
@@ -0,0 +1,128 @@+module Result where +{-| A `Result` is the result of a computation that may fail. This is a great +way to manage errors in Elm. + +# Type and Constructors +@docs Result + +# Common Helpers +@docs map, andThen + +# Formatting Errors +@docs toMaybe, fromMaybe formatError +-} + +import Maybe ( Maybe(Just, Nothing) ) + + +{-| A `Result` is either `Ok` meaning the computation succeeded, or it is an +`Err` meaning that there was some failure. +-} +type Result error value = Ok value | Err error + + +{-| Apply a function to a result. If the result is `Ok`, it will be converted. +If the result is an `Err`, the same error value will propegate through. + + map sqrt (Ok 4.0) == Ok 2.0 + map sqrt (Err "bad input") == Err "bad input" +-} +map : (a -> b) -> Result e a -> Result e b +map f result = + case result of + Ok v -> Ok (f v) + Err e -> Err e + + +{-| Chain together a sequence of computations that may fail. It is helpful +to see its definition: + + andThen : Result e a -> (a -> Result e b) -> Result e b + andThen result callback = + case result of + Ok value -> callback value + Err msg -> Err msg + +This means we only continue with the callback if things are going well. For +example, say you need to use (`toInt : String -> Result String Int`) to parse +a month and make sure it is between 1 and 12: + + toValidMonth : Int -> Result String Int + toValidMonth month = + if month >= 1 && month <= 12 + then Ok month + else Err "months must be between 1 and 12" + + toMonth : String -> Result String Int + toMonth rawString = + toInt rawString `andThen` toValidMonth + + -- toMonth "4" == Ok 4 + -- toMonth "9" == Ok 9 + -- toMonth "a" == Err "cannot parse to an Int" + -- toMonth "0" == Err "months must be between 1 and 12" + +This allows us to come out of a chain of operations with quite a specific error +message. It is often best to create a custom type that explicitly represents +the exact ways your computation may fail. This way it is easy to handle in your +code. +-} +andThen : Result e a -> (a -> Result e b) -> Result e b +andThen result callback = + case result of + Ok value -> callback value + Err msg -> Err msg + + +{-| Format the error value of a result. If the result is `Ok`, it stays exactly +the same, but if the result is an `Err` we will format the error. For example, +say the errors we get have too much information: + + parseInt : String -> Result ParseError Int + + type ParseError = + { message : String + , code : Int + , position : (Int,Int) + } + + formatError .message (parseInt "123") == Ok 123 + formatError .message (parseInt "abc") == Err "char 'a' is not a number" +-} +formatError : (error -> error') -> Result error a -> Result error' a +formatError f result = + case result of + Ok v -> Ok v + Err e -> Err (f e) + + +{-| Convert to a simpler `Maybe` if the actual error message is not needed or +you need to interact with some code that primarily uses maybes. + + parseInt : String -> Result ParseError Int + + maybeParseInt : String -> Maybe Int + maybeParseInt string = + toMaybe (parseInt string) +-} +toMaybe : Result e a -> Maybe a +toMaybe result = + case result of + Ok v -> Just v + Err _ -> Nothing + + +{-| Convert from a simple `Maybe` to interact with some code that primarily +uses `Results`. + + parseInt : String -> Maybe Int + + resultParseInt : String -> Result String Int + resultParseInt string = + fromMaybe ("error parsing string: " ++ show string) (parseInt string) +-} +fromMaybe : e -> Maybe a -> Result e a +fromMaybe err maybe = + case maybe of + Just v -> Ok v + Nothing -> Err err
+ src/Set.elm view
@@ -0,0 +1,101 @@+ +module Set + ( Set + , empty, singleton, insert, remove + , member + , foldl, foldr, map + , filter, partition + , union, intersect, diff + , toList, fromList + ) where + +{-| A set of unique values. The values can be any comparable type. This +includes `Int`, `Float`, `Time`, `Char`, `String`, and tuples or lists +of comparable types. + +Insert, remove, and query operations all take *O(log n)* time. + +# Build +@docs empty, singleton, insert, remove + +# Query +@docs member + +# Combine +@docs union, intersect, diff + +# Lists +@docs toList, fromList + +# Transform +@docs map, foldl, foldr, filter, partition + +-} + +import Dict as Dict +import List as List + +type alias Set t = Dict.Dict t () + +{-| Create an empty set. -} +empty : Set comparable +empty = Dict.empty + +{-| Create a set with one value. -} +singleton : comparable -> Set comparable +singleton k = Dict.singleton k () + +{-| Insert a value into a set. -} +insert : comparable -> Set comparable -> Set comparable +insert k = Dict.insert k () + +{-| Remove a value from a set. If the value is not found, no changes are made. +-} +remove : comparable -> Set comparable -> Set comparable +remove = Dict.remove + +{-| Determine if a value is in a set. -} +member : comparable -> Set comparable -> Bool +member = Dict.member + +{-| Get the union of two sets. Keep all values. -} +union : Set comparable -> Set comparable -> Set comparable +union = Dict.union + +{-| Get the intersection of two sets. Keeps values that appear in both sets. -} +intersect : Set comparable -> Set comparable -> Set comparable +intersect = Dict.intersect + +{-| Get the difference between the first set and the second. Keeps values +that do not appear in the second set. -} +diff : Set comparable -> Set comparable -> Set comparable +diff = Dict.diff + +{-| Convert a set into a list. -} +toList : Set comparable -> List comparable +toList = Dict.keys + +{-| Convert a list into a set, removing any duplicates. -} +fromList : List comparable -> Set comparable +fromList xs = List.foldl insert empty xs + +{-| Fold over the values in a set, in order from lowest to highest. -} +foldl : (comparable -> b -> b) -> b -> Set comparable -> b +foldl f b s = Dict.foldl (\k _ b -> f k b) b s + +{-| Fold over the values in a set, in order from highest to lowest. -} +foldr : (comparable -> b -> b) -> b -> Set comparable -> b +foldr f b s = Dict.foldr (\k _ b -> f k b) b s + +{-| Map a function onto a set, creating a new set with no duplicates. -} +map : (comparable -> comparable') -> Set comparable -> Set comparable' +map f s = fromList (List.map f (toList s)) + +{-| Create a new set consisting only of elements which satisfy a predicate. -} +filter : (comparable -> Bool) -> Set comparable -> Set comparable +filter p set = Dict.filter (\k _ -> p k) set + +{-| Create two new sets; the first consisting of elements which satisfy a +predicate, the second consisting of elements which do not. -} +partition : (comparable -> Bool) -> Set comparable -> (Set comparable, Set comparable) +partition p set = Dict.partition (\k _ -> p k) set
+ src/Signal.elm view
@@ -0,0 +1,305 @@+module Signal where +{-| The library for general signal manipulation. Includes mapping, merging, +filters, past-dependence, and helpers for handling inputs from the UI. + +Some useful functions for working with time (e.g. setting FPS) and combining +signals and time (e.g. delaying updates, getting timestamps) can be found in +the [`Time`](Time) library. + +# Merging +@docs merge, mergeMany + +# Mapping +@docs map, map2, map3, map4, map5 + +# Fancy Mapping +@docs (<~), (~) + +# Past-Dependence +@docs foldp + +# Filters +@docs keepIf, dropIf, keepWhen, dropWhen, dropRepeats, sampleOn + +# Channels +@docs channel, send, subscribe + +# Constants +@docs constant + +-} + +import Native.Signal +import List +import Basics (fst, snd, not) + +type Signal a = Signal + +{-| Create a constant signal that never changes. -} +constant : a -> Signal a +constant = Native.Signal.constant + +{-| Apply a function to the current value of a signal. + + mouseIsUp : Signal Bool + mouseIsUp = + map not Mouse.isDown + + main : Signal Element + main = + map toElement Mouse.position +-} +map : (a -> result) -> Signal a -> Signal result +map = Native.Signal.map + +{-| Apply a function to the current value of two signals. In the following +example, we figure out the `aspectRatio` of the window by combining the +current width and height. + + ratio : Int -> Int -> Float + ratio width height = + toFloat width / toFloat height + + aspectRatio : Signal Float + aspectRatio = + map2 ratio Window.width Window.height +-} +map2 : (a -> b -> result) -> Signal a -> Signal b -> Signal result +map2 = Native.Signal.map2 + +map3 : (a -> b -> c -> result) -> Signal a -> Signal b -> Signal c -> Signal result +map3 = Native.Signal.map3 + +map4 : (a -> b -> c -> d -> result) -> Signal a -> Signal b -> Signal c -> Signal d -> Signal result +map4 = Native.Signal.map4 + +map5 : (a -> b -> c -> d -> e -> result) -> Signal a -> Signal b -> Signal c -> Signal d -> Signal e -> Signal result +map5 = Native.Signal.map5 + + +{-| Create a past-dependent signal. Each update from the incoming signals will +be used to step the stat forward. The outgoing signal represents the current +state. + + clickCount : Signal Int + clickCount = + foldp (\click total -> total + 1) 0 Mouse.clicks + + timeSoFar : Signal Time + timeSoFar = + foldp (+) 0 (fps 40) + +So `clickCount` updates on each mouse click, incrementing by one. `timeSoFar` +is the time the program has been running, updated 40 times a second. +-} +foldp : (a -> state -> state) -> state -> Signal a -> Signal state +foldp = Native.Signal.foldp + + +{-| Merge two signals into one. This function is extremely useful for bringing +together lots of different signals to feed into a `foldp`. + + type Update = MouseMove (Int,Int) | TimeDelta Float + + updates : Signal Update + updates = + merge + (map MouseMove Mouse.position) + (map TimeDelta (fps 40)) + +If an update comes from either of the incoming signals, it updates the outgoing +signal. If an update comes on both signals at the same time, the left update +wins. +-} +merge : Signal a -> Signal a -> Signal a +merge = Native.Signal.merge + +{-| Merge many signals into one. This is useful when you are merging more than +two signals. When multiple updates come in at the same time, the left-most +update wins, just like with `merge`. + + type Update = MouseMove (Int,Int) | TimeDelta Float | Click + + updates : Signal Update + updates = + mergeMany + [ map MouseMove Mouse.position + , map TimeDelta (fps 40) + , map (always Click) Mouse.clicks + ] +-} +mergeMany : List (Signal a) -> Signal a +mergeMany signals = + List.foldr1 merge signals + + +{-| Filter out some updates. The given function decides whether we should +keep an update. If no updates ever flow through, we use the default value +provided. The following example only keeps even numbers and has an initial +value of zero. + + numbers : Signal Int + + isEven : Int -> Bool + + evens : Signal Int + evens = + keepIf isEven 0 numbers +-} +keepIf : (a -> Bool) -> a -> Signal a -> Signal a +keepIf = + Native.Signal.keepIf + + +{-| Filter out some updates. The given function decides whether we should +drop an update. If we drop all updates, we use the default value provided. +The following example drops all even numbers and has an initial value of +one. + + numbers : Signal Int + + isEven : Int -> Bool + + odds : Signal Int + odds = + dropIf isEven 1 numbers +-} +dropIf : (a -> Bool) -> a -> Signal a -> Signal a +dropIf = + Native.Signal.dropIf + + +{-| Keep updates when the first signal is true. You provide a default value +just in case that signal is *never* true and no updates make it through. For +example, here is how you would capture mouse drags. + + dragPosition : Signal (Int,Int) + dragPosition = + keepWhen Mouse.isDown (0,0) Mouse.position +-} +keepWhen : Signal Bool -> a -> Signal a -> Signal a +keepWhen bs def sig = + snd <~ (keepIf fst (False, def) ((,) <~ (sampleOn sig bs) ~ sig)) + +{-| Drop events when the first signal is true. You provide a default value +just in case that signal is *always* true and we drop all updates. +-} +dropWhen : Signal Bool -> a -> Signal a -> Signal a +dropWhen bs = keepWhen (not <~ bs) + + +{-| Drop updates that repeat the current value of the signal. + + numbers : Signal Int + + noDups : Signal Int + noDups = + dropRepeats numbers + + -- numbers => 0 0 3 3 5 5 5 4 ... + -- noDups => 0 3 5 4 ... +-} +dropRepeats : Signal a -> Signal a +dropRepeats = Native.Signal.dropRepeats + +{-| Sample from the second input every time an event occurs on the first input. +For example, `(sampleOn clicks (every second))` will give the approximate time +of the latest click. -} +sampleOn : Signal a -> Signal b -> Signal b +sampleOn = Native.Signal.sampleOn + + +{-| An alias for `map`. A prettier way to apply a function to the current value +of a signal. +-} +(<~) : (a -> b) -> Signal a -> Signal b +f <~ s = Native.Signal.map f s + + +{-| Intended to be paired with the `(<~)` operator, this makes it possible for +many signals to flow into a function. Think of it as a fancy alias for `mapN`. +For example, the following declarations are equivalent: + + main : Signal Element + main = + scene <~ Window.dimensions ~ Mouse.position + + main : Signal ELement + main = + map2 scene Window.dimensions Mouse.position + +You can use this pattern for as many signals as you want by using `(~)` a bunch +of times, so you can go higher than `map5` if you need to. +-} +(~) : Signal (a -> b) -> Signal a -> Signal b +sf ~ s = Native.Signal.map2 (\f x -> f x) sf s + +infixl 4 <~ +infixl 4 ~ + + +---- INPUTS ---- + +type Channel a = Channel -- Signal a + +type Message = Message -- () -> () + + +{-| Create a signal channel that you can `send` messages to. To receive these +messages, `subscribe` to the channel and turn it into a normal signal. The +primary use case is receiving updates from UI elements such as buttons and +text fields. The argument is a default value for the custom signal. + +Note: This is an inherently impure function, so `(channel ())` +and `(channel ())` produce two different signals. +-} +channel : a -> Channel a +channel = + Native.Signal.input + + +{-| Create a `Message` that can be sent to an `Channel` with a handler like +`Html.onclick` or `Html.onblur`. + + import Html + + type Update = NoOp | Add Int | Remove Int + + updates : Channel Update + updates = channel NoOp + + addButton : Html.Html + addButton = + Html.button + [ onclick (send updates (Add 1)) ] + [ Html.text "Add 1" ] +-} +send : Channel a -> a -> Message +send = + Native.Signal.send + + +{-| Receive all the messages sent to an `Channel` as a `Signal`. The following +example shows how you would set up a system that uses an `Channel`. + + -- initialState : Model + -- type Update = NoOp | ... + -- step : Update -> Model -> Model + -- view : Channel Update -> Model -> Element + + updates : Channel Update + updates = channel NoOp + + main : Signal Element + main = + map + (view updates) + (foldp step initialState (subscribe updates)) + +The `updates` channel appears twice in `main` because it serves as a bridge +between your view and your signals. In the view you `send` to it, and in signal +world you `subscribe` to it. +-} +subscribe : Channel a -> Signal a +subscribe = + Native.Signal.subscribe
+ src/String.elm view
@@ -0,0 +1,345 @@+module String where +{-| A built-in representation for efficient string manipulation. String literals +are enclosed in `"double quotes"`. Strings are *not* lists of characters. + +# Basics +@docs isEmpty, length, reverse, repeat + +# Building and Splitting +@docs cons, uncons, fromChar, append, concat, split, join, words, lines + +# Get Substrings +@docs slice, left, right, dropLeft, dropRight + +# Check for Substrings +@docs contains, startsWith, endsWith, indexes, indices + +# Conversions +@docs toInt, toFloat, toList, fromList + +# Formatting +Cosmetic operations such as padding with extra characters or trimming whitespace. + +@docs toUpper, toLower, + pad, padLeft, padRight, + trim, trimLeft, trimRight + +# Higher-Order Functions +@docs map, filter, foldl, foldr, any, all +-} + +import Native.String +import Char +import Maybe (Maybe) +import Result (Result) + + +{-| Determine if a string is empty. + + isEmpty "" == True + isEmpty "the world" == False +-} +isEmpty : String -> Bool +isEmpty = Native.String.isEmpty + +{-| Add a character to the beginning of a string. -} +cons : Char -> String -> String +cons = Native.String.cons + + +{-| Create a string from a given character. + + fromChar 'a' == "a" +-} +fromChar : Char -> String +fromChar char = + cons char "" + + +{-| Split a non-empty string into its head and tail. This lets you +pattern match on strings exactly as you would with lists. + + uncons "abc" == Just ('a',"bc") + uncons "" == Nothing +-} +uncons : String -> Maybe (Char, String) +uncons = Native.String.uncons + +{-| Append two strings. You can also use [the `(++)` operator](Basics#++) +to do this. + + append "butter" "fly" == "butterfly" +-} +append : String -> String -> String +append = Native.String.append + +{-| Concatenate many strings into one. + + concat ["never","the","less"] == "nevertheless" +-} +concat : List String -> String +concat = Native.String.concat + +{-| Get the length of a string. + + length "innumerable" == 11 + length "" == 0 + +-} +length : String -> Int +length = Native.String.length + +{-| Transform every character in a string + + map (\c -> if c == '/' then '.' else c) "a/b/c" == "a.b.c" +-} +map : (Char -> Char) -> String -> String +map = Native.String.map + +{-| Keep only the characters that satisfy the predicate. + + filter isDigit "R2-D2" == "22" +-} +filter : (Char -> Bool) -> String -> String +filter = Native.String.filter + +{-| Reverse a string. + + reverse "stressed" == "desserts" +-} +reverse : String -> String +reverse = Native.String.reverse + +{-| Reduce a string from the left. + + foldl cons "" "time" == "emit" +-} +foldl : (Char -> b -> b) -> b -> String -> b +foldl = Native.String.foldl + +{-| Reduce a string from the right. + + foldr cons "" "time" == "time" +-} +foldr : (Char -> b -> b) -> b -> String -> b +foldr = Native.String.foldr + +{-| Split a string using a given separator. + + split "," "cat,dog,cow" == ["cat","dog","cow"] + split "/" "home/evan/Desktop/" == ["home","evan","Desktop", ""] + +Use `Regex.split` if you need something more flexible. +-} +split : String -> String -> List String +split = Native.String.split + +{-| Put many strings together with a given separator. + + join "a" ["H","w","ii","n"] == "Hawaiian" + join " " ["cat","dog","cow"] == "cat dog cow" + join "/" ["home","evan","Desktop"] == "home/evan/Desktop" +-} +join : String -> List String -> String +join = Native.String.join + +{-| Repeat a string *n* times. + + repeat 3 "ha" == "hahaha" +-} +repeat : Int -> String -> String +repeat = Native.String.repeat + +{-| Take a substring given a start and end index. Negative indexes +are taken starting from the *end* of the list. + + slice 7 9 "snakes on a plane!" == "on" + slice 0 6 "snakes on a plane!" == "snakes" + slice 0 -7 "snakes on a plane!" == "snakes on a" + slice -6 -1 "snakes on a plane!" == "plane" +-} +slice : Int -> Int -> String -> String +slice = Native.String.slice + +{-| Take *n* characters from the left side of a string. -} +left : Int -> String -> String +left = Native.String.left + +{-| Take *n* characters from the right side of a string. -} +right : Int -> String -> String +right = Native.String.right + +{-| Drop *n* characters from the left side of a string. -} +dropLeft : Int -> String -> String +dropLeft = Native.String.dropLeft + +{-| Drop *n* characters from the right side of a string. -} +dropRight : Int -> String -> String +dropRight = Native.String.dropRight + +{-| Pad a string on both sides until it has a given length. + + pad 5 ' ' "1" == " 1 " + pad 5 ' ' "11" == " 11 " + pad 5 ' ' "121" == " 121 " +-} +pad : Int -> Char -> String -> String +pad = Native.String.pad + +{-| Pad a string on the left until it has a given length. + + padLeft 5 '.' "1" == "....1" + padLeft 5 '.' "11" == "...11" + padLeft 5 '.' "121" == "..121" +-} +padLeft : Int -> Char -> String -> String +padLeft = Native.String.padLeft + +{-| Pad a string on the right until it has a given length. + + padRight 5 '.' "1" == "1...." + padRight 5 '.' "11" == "11..." + padRight 5 '.' "121" == "121.." +-} +padRight : Int -> Char -> String -> String +padRight = Native.String.padRight + +{-| Get rid of whitespace on both sides of a string. + + trim " hats \n" == "hats" +-} +trim : String -> String +trim = Native.String.trim + +{-| Get rid of whitespace on the left of a string. + + trimLeft " hats \n" == "hats \n" +-} +trimLeft : String -> String +trimLeft = Native.String.trimLeft + +{-| Get rid of whitespace on the right of a string. + + trimRight " hats \n" == " hats" +-} +trimRight : String -> String +trimRight = Native.String.trimRight + +{-| Break a string into words, splitting on chunks of whitespace. + + words "How are \t you? \n Good?" == ["How","are","you?","Good?"] +-} +words : String -> List String +words = Native.String.words + +{-| Break a string into lines, splitting on newlines. + + lines "How are you?\nGood?" == ["How are you?", "Good?"] +-} +lines : String -> List String +lines = Native.String.lines + +{-| Convert a string to all upper case. Useful for case insensitive comparisons +and VIRTUAL YELLING. +-} +toUpper : String -> String +toUpper = Native.String.toUpper + +{-| Convert a string to all lower case. Useful for case insensitive comparisons. -} +toLower : String -> String +toLower = Native.String.toLower + +{-| Determine whether *any* characters satisfy a predicate. + + any isDigit "90210" == True + any isDigit "R2-D2" == True + any isDigit "heart" == False +-} +any : (Char -> Bool) -> String -> Bool +any = Native.String.any + +{-| Determine whether *all* characters satisfy a predicate. + + all isDigit "90210" == True + all isDigit "R2-D2" == False + all isDigit "heart" == False +-} +all : (Char -> Bool) -> String -> Bool +all = Native.String.all + +{-| See if the second string contains the first one. + + contains "the" "theory" == True + contains "hat" "theory" == False + contains "THE" "theory" == False + +Use `Regex.contains` if you need something more flexible. +-} +contains : String -> String -> Bool +contains = Native.String.contains + +{-| See if the second string starts with the first one. + + startsWith "the" "theory" == True + startsWith "ory" "theory" == False +-} +startsWith : String -> String -> Bool +startsWith = Native.String.startsWith + +{-| See if the second string ends with the first one. + + endsWith "the" "theory" == False + endsWith "ory" "theory" == True +-} +endsWith : String -> String -> Bool +endsWith = Native.String.endsWith + +{-| Get all of the indexes for a substring in another string. + + indexes "i" "Mississippi" == [1,4,7,10] + indexes "ss" "Mississippi" == [2,5] + indexes "needle" "haystack" == [] +-} +indexes : String -> String -> List Int +indexes = Native.String.indexes + +{-| Alias for `indexes` -} +indices : String -> String -> List Int +indices = Native.String.indexes + + +{-| Try to convert a string into an int, failing on improperly formatted strings. + + toInt "123" == Ok 123 + toInt "-42" == Ok -42 + toInt "3.1" == Err "could not convert string '3.1' to an Int" + toInt "31a" == Err "could not convert string '31a' to an Int" +-} +toInt : String -> Result String Int +toInt = Native.String.toInt + +{-| Try to convert a string into a float, failing on improperly formatted strings. + + toFloat "123" == Ok 123.0 + toFloat "-42" == Ok -42.0 + toFloat "3.1" == Ok 3.1 + toFloat "31a" == Err "could not convert string '31a' to a Float" +-} +toFloat : String -> Result String Float +toFloat = Native.String.toFloat + +{-| Convert a string to a list of characters. + + toList "abc" == ['a','b','c'] +-} +toList : String -> List Char +toList = Native.String.toList + +{-| Convert a list of characters into a String. Can be useful if you +want to create a string primarly by consing, perhaps for decoding +something. + + fromList ['a','b','c'] == "abc" +-} +fromList : List Char -> String +fromList = Native.String.fromList
+ src/Text.elm view
@@ -0,0 +1,261 @@+module Text where +{-| A library for styling and displaying text. Whlie the `String` library +focuses on representing and manipulating strings of character strings, the +`Text` library focuses on how those strings should look on screen. It lets +you make text bold or italic, set the typeface, set the text size, etc. + +# Creating Text +@docs fromString, empty, append, concat, join + +# Creating Elements + +Each of the following functions places `Text` into a box. The function you use +determines the alignment of the text. + +@docs leftAligned, rightAligned, centered, justified + +# Links and Style +@docs link, Style, style, defaultStyle, Line + +# Convenience Functions + +There are two convenience functions for creating an `Element` which can be +useful when debugging or prototyping: + +@docs plainText, asText + +There are also a bunch of functions to set parts of a `Style` individually: + +@docs typeface, monospace, height, color, bold, italic, line + +-} + +import Basics (..) +import Color (Color, black) +import Graphics.Element (Element, Three, Pos, ElementPrim, Properties) +import List +import Maybe (Maybe(Nothing)) +import Native.Text + +type Text = Text + +{-| Styles for lines on text. This allows you to add an underline, an overline, +or a strike out text: + + line Under (fromString "underline") + line Over (fromString "overline") + line Through (fromString "strike out") +-} +type Line = Under | Over | Through + +{-| Represents all the ways you can style `Text`. If the `typeface` list is +empty or the `height` is `Nothing`, the users will fall back on their browser's +default settings. The following `Style` is black, 16 pixel tall, underlined, and +Times New Roman (assuming that typeface is available on the user's computer): + + { typeface = [ "Times New Roman", "serif" ] + , height = Just 16 + , color = black + , bold = False + , italic = False + , line = Just Under + } +-} +type alias Style = + { typeface : List String + , height : Maybe Float + , color : Color + , bold : Bool + , italic : Bool + , line : Maybe Line + } + +{-| Plain black text. It uses the browsers default typeface and text height. +No decorations are used. + + { typeface = [] + , height = Nothing + , color = black + , bold = False + , italic = False + , line = Nothing + } +-} +defaultStyle : Style +defaultStyle = + { typeface = [] + , height = Nothing + , color = black + , bold = False + , italic = False + , line = Nothing + } + +{-| Convert a string into text which can be styled and displayed. To show the +string `"Hello World!"` on screen in italics, you could say: + + main = leftAligned (italic (fromString "Hello World!")) +-} +fromString : String -> Text +fromString = Native.Text.fromString + + +{-| Text with nothing in it. + + empty = fromString "" +-} +empty : Text +empty = + fromString "" + + +{-| Put two chunks of text together. + + append (fromString "hello ") (fromString "world") == fromString "hello world" +-} +append : Text -> Text -> Text +append = + Native.Text.append + + +{-| Put many chunks of text together. + + concat + [ fromString "type " + , bold (fromString "Maybe") + , fromString " = Just a | Nothing" + ] +-} +concat : List Text -> Text +concat texts = + List.foldr append empty texts + + +{-| Put many chunks of text together with a separator. + + chunks : List Text + chunks = List.map fromString ["lions","tigers","bears"] + + join (fromString ", ") chunks == fromString "lions, tigers, bears" +-} +join : Text -> List Text -> Text +join seperator texts = + concat (List.intersperse seperator texts) + + +{-| Set the style of some text. For example, if you design a `Style` called +`footerStyle` that is specifically for the bottom of your page, you could apply +it to text like this: + + style footerStyle (fromString "the old prince / 2007") +-} +style : Style -> Text -> Text +style = Native.Text.style + +{-| Provide a list of prefered typefaces for some text. + + ["helvetica","arial","sans-serif"] + +Not every browser has access to the same typefaces, so rendering will use the +first typeface in the list that is found on the user's computer. If there are +no matches, it will use their default typeface. This works the same as the CSS +font-family property. +-} +typeface : List String -> Text -> Text +typeface = Native.Text.typeface + +{-| Switch to a monospace typeface. Good for code snippets. + + monospace (fromString "foldl (+) 0 [1,2,3]") +-} +monospace : Text -> Text +monospace = Native.Text.monospace + +{-| Create a link by providing a URL and the text of the link. + + link "http://elm-lang.org" (fromString "Elm Website") +-} +link : String -> Text -> Text +link = Native.Text.link + +{-| Set the height of some text. + + height 40 (fromString "Title") +-} +height : Float -> Text -> Text +height = Native.Text.height + +{-| Set the color of some text. + + color red (fromString "Red") +-} +color : Color -> Text -> Text +color = Native.Text.color + +{-| Make text bold. + + fromString "sometimes you want " ++ bold (fromString "emphasis") +-} +bold : Text -> Text +bold = Native.Text.bold + +{-| Make text italic. + + fromString "make it " ++ italic (fromString "important") +-} +italic : Text -> Text +italic = Native.Text.italic + +{-| Put lines on text. + + line Under (fromString "underlined") + line Over (fromString "overlined") + line Through (fromString "strike out") +-} +line : Line -> Text -> Text +line = Native.Text.line + +{-| Align text along the left side of the text block. This is sometimes known as +*ragged right*. +-} +leftAligned : Text -> Element +leftAligned = Native.Text.leftAligned + +{-| Align text along the right side of the text block. This is sometimes known +as *ragged left*. +-} +rightAligned : Text -> Element +rightAligned = Native.Text.rightAligned + +{-| Center text in the text block. There is equal spacing on either side of a +line of text. +-} +centered : Text -> Element +centered = Native.Text.centered + +{-| Align text along the left and right sides of the text block. Word spacing is +adjusted to make this possible. +-} +justified : Text -> Element +justified = Native.Text.justified + +{-| Display a string with no styling. + + plainText string = leftAligned (fromString string) +-} +plainText : String -> Element +plainText str = + leftAligned (fromString str) + +{-| for internal use only -} +markdown : Element +markdown = Native.Text.markdown + +{-| Convert anything to its textual representation and make it displayable in +the browser. Excellent for debugging. + + asText value = leftAligned (monospace (fromString (show value))) +-} +asText : a -> Element +asText value = + leftAligned (monospace (fromString (toString value)))
+ src/Time.elm view
@@ -0,0 +1,105 @@+module Time where + +{-| Library for working with time. + +# Units +@docs Time, millisecond, second, minute, hour, + inMilliseconds, inSeconds, inMinutes, inHours + +# Tickers +@docs fps, fpsWhen, every + +# Timing +@docs timestamp, delay, since + +-} + +import Basics (..) +import Native.Time +import Signal (Signal, map, merge, foldp) + +{-| Type alias to make it clearer when you are working with time values. +Using the `Time` constants instead of raw numbers is very highly recommended. +-} +type alias Time = Float + +{-| Units of time, making it easier to specify things like a half-second +`(500 * milliseconds)` without remembering Elm’s underlying units of time. +-} +millisecond : Time +millisecond = 1 + +second : Time +second = 1000 * millisecond + +minute : Time +minute = 60 * second + +hour : Time +hour = 60 * minute + +inMilliseconds : Time -> Float +inMilliseconds t = t + +inSeconds : Time -> Float +inSeconds t = t / second + +inMinutes : Time -> Float +inMinutes t = t / minute + +inHours : Time -> Float +inHours t = t / hour + +{-| Takes desired number of frames per second (fps). The resulting signal +gives a sequence of time deltas as quickly as possible until it reaches +the desired FPS. A time delta is the time between the last frame and the +current frame. +-} +fps : number -> Signal Time +fps = Native.Time.fps + +{-| Same as the fps function, but you can turn it on and off. Allows you +to do brief animations based on user input without major inefficiencies. +The first time delta after a pause is always zero, no matter how long +the pause was. This way summing the deltas will actually give the amount +of time that the output signal has been running. +-} +fpsWhen : number -> Signal Bool -> Signal Time +fpsWhen = Native.Time.fpsWhen + +{-| Takes a time interval t. The resulting signal is the current time, updated +every t. +-} +every : Time -> Signal Time +every = Native.Time.every + +{-| Takes a time `t` and any signal. The resulting boolean signal is true for +time `t` after every event on the input signal. So ``(second `since` +Mouse.clicks)`` would result in a signal that is true for one second after +each mouse click and false otherwise. +-} +since : Time -> Signal a -> Signal Bool +since t s = + let + start = map (always 1) s + stop = map (always -1) (delay t s) + delaydiff = foldp (+) 0 (merge start stop) + in + map ((/=) 0) delaydiff + +{-| Add a timestamp to any signal. Timestamps increase monotonically. When you +create `(timestamp Mouse.x)`, an initial timestamp is produced. The timestamp +updates whenever `Mouse.x` updates. + +Timestamp updates are tied to individual events, so +`(timestamp Mouse.x)` and `(timestamp Mouse.y)` will always have the same +timestamp because they rely on the same underlying event (`Mouse.position`). +-} +timestamp : Signal a -> Signal (Time, a) +timestamp = Native.Time.timestamp + +{-| Delay a signal by a certain amount of time. So `(delay second Mouse.clicks)` +will update one second later than any mouse click. +-} +delay : Time -> Signal a -> Signal a +delay = Native.Time.delay
+ src/Touch.elm view
@@ -0,0 +1,41 @@+module Touch where + +{-| This is an early version of the touch library. It will likely grow to +include gestures that would be useful for both games and web-pages. + +# Touches +@docs Touch, touches + +# Gestures +@docs taps +-} + +import Signal (Signal) +import Native.Touch +import Time (Time) + +{-| Every `Touch` has `xy` coordinates. It also has an identifier +`id` to distinguish one touch from another. + +A touch also keeps info about the initial point and time of contact: +`x0`, `y0`, and `t0`. This helps compute more complicated gestures +like taps, drags, and swipes which need to know about timing or direction. +-} +type alias Touch = + { x:Int + , y:Int + , id:Int + , x0:Int + , y0:Int + , t0:Time + } + +{-| A list of ongoing touches. -} +touches : Signal (List Touch) +touches = Native.Touch.touches + +{-| The last position that was tapped. Default value is `{x=0,y=0}`. +Updates whenever the user taps the screen. +-} +taps : Signal { x:Int, y:Int } +taps = Native.Touch.taps
+ src/Trampoline.elm view
@@ -0,0 +1,51 @@+module Trampoline where +{-| A [trampoline](http://en.wikipedia.org/wiki/Tail-recursive_function#Through_trampolining) +makes it possible to recursively call a function without growing the stack. + +Popular JavaScript implementations do not perform any tail-call elimination, so +recursive functions can cause a stack overflow if they go too deep. Trampolines +permit unbounded recursion despite limitations in JavaScript. + +This strategy may create many intermediate closures, which is very expensive in +JavaScript, so use this library only when it is essential that you recurse deeply. + +# Trampolines +@docs trampoline, Trampoline +-} + +import Native.Trampoline + +{-| A way to build computations that may be deeply recursive. We will take an +example of a tail-recursive function and rewrite it in a way that lets us use +a trampoline: + + length : List a -> Int + length list = length' 0 list + + length' : Int -> List a -> Int + length' accum list = + case list of + [] -> accum + hd::tl -> length' (accum+1) tl + +This finds the length of a list, but if the list is too long, it may cause a +stack overflow. We can rewrite it as follows: + + length : List a -> Int + length list = trampoline (length' 0 list) + + length' : Int -> List a -> Trampoline Int + length' accum list = + case list of + [] -> Done accum + hd::tl -> Continue (\() -> length' (accum+1) tl) + +Now it uses a trampoline and can recurse without growing the stack! +-} +type Trampoline a + = Done a + | Continue (() -> Trampoline a) + +{-| Evaluate a trampolined value in constant space. -} +trampoline : Trampoline a -> a +trampoline = Native.Trampoline.trampoline
+ src/Transform2D.elm view
@@ -0,0 +1,98 @@+module Transform2D ( Transform2D + , identity, matrix, multiply + , rotation, translation + , scale, scaleX, scaleY + ) where +{-| A library for performing [2D matrix transformations][affine]. +It is used primarily with the `groupTransform` function from `Graphics.Collage` and +allows you to do things like rotation, scaling, translation, shearing, and reflection. + +Note that all the matrices in this library are 3x3 matrices of homogeneous +coordinates, used for [affine transformations][affine]. Since the bottom row as +always `0 0 1` in these matrices, it is omitted in the diagrams below. + + [affine]: http://en.wikipedia.org/wiki/Transformation_matrix#Affine_transformations + +# Transforms +@docs identity, matrix, rotation, translation, scale, scaleX, scaleY + +# Multiplication +@docs multiply +-} + +import Native.Transform2D + +type Transform2D = Transform2D + +{-| Create an identity transform. Transforming by the identity does +not change anything, but it can come in handy as a default or +base case. + + / 1 0 0 \ + \ 0 1 0 / +-} +identity : Transform2D +identity = Native.Transform2D.identity + +{-| Create a transformation matrix. This lets you create transforms +such as scales, shears, reflections, and translations. + + matrix a b c d x y + + / a b x \ + \ c d y / + +Note that `x` and `y` are the translation values. +-} +matrix : Float -> Float -> Float -> Float -> Float -> Float -> Transform2D +matrix = Native.Transform2D.matrix + +{-| Create a [rotation matrix](http://en.wikipedia.org/wiki/Rotation_matrix). +Given an angle t, it creates a counterclockwise rotation matrix: + + rotation t + + / cos t -sin t 0 \ + \ sin t cos t 0 / +-} +rotation : Float -> Transform2D +rotation = Native.Transform2D.rotation + +{-| Create a transformation matrix for translation. + + translation x y + + / 1 0 x \ + \ 0 1 y / +-} +translation : Float -> Float -> Transform2D +translation x y = matrix 1 0 0 1 x y + +{-| Creates a transformation matrix for scaling by a all directions. + + scale s + + / s 0 0 \ + \ 0 s 0 / +-} +scale : Float -> Transform2D +scale s = matrix s 0 0 s 0 0 + +{-| Create a transformation for horizontal scaling. -} +scaleX : Float -> Transform2D +scaleX x = matrix x 0 0 1 0 0 + +{-| Create a transformation for vertical scaling. -} +scaleY : Float -> Transform2D +scaleY y = matrix 1 0 0 y 0 0 + +{-| Multiply two transforms together. + + multiply m n + + / ma mb mx \ / na nb nx \ + | mc md my | . | nc nd ny | + \ 0 0 1 / \ 0 0 1 / +-} +multiply : Transform2D -> Transform2D -> Transform2D +multiply = Native.Transform2D.multiply
+ src/WebSocket.elm view
@@ -0,0 +1,21 @@+module WebSocket where +{-| A library for low latency HTTP communication. See the HTTP library for +standard requests like GET, POST, etc. The API of this library is likely to +change to make it more flexible. + +# Open a Connection +@docs connect +-} + +import Signal (Signal) +import Native.WebSocket + +{-| Create a web-socket. The first argument is the URL of the desired +web-socket server. The input signal holds the outgoing messages, +and the resulting signal contains the incoming ones. +-} +connect : String -> Signal String -> Signal String +connect = Native.WebSocket.connect + +-- data Action = Open String | Close String | Send String String +-- connections : Signal Action -> Signal String
+ src/Window.elm view
@@ -0,0 +1,27 @@+ +module Window where + +{-| Provides information about the container that your Elm program lives in. +When you embed Elm in a `<div>` it gives the dimensions of the container, not +the whole window. + +# Dimensions +@docs dimensions, width, height + +-} + +import Signal (Signal) +import Native.Window + +{-| The current width and height of the window (i.e. the area viewable to the +user, not including scroll bars). -} +dimensions : Signal (Int,Int) +dimensions = Native.Window.dimensions + +{-| The current width of the window. -} +width : Signal Int +width = Native.Window.width + +{-| The current height of the window. -} +height : Signal Int +height = Native.Window.height