Week 47 of 2018

Development log of Elm Tree Workshop

8 items
  1. Tree example: Use Elm UI to fill the screen and center the tree
  2. Write down steps to reproduce the tree, implement or improve some examples
  3. Make transformations example interactive
  4. Make sliders stepped in Transformations example
  5. Implement NestedTransformations example
  6. Reimplement Main with Elm Markup, nest a simple counter program
  7. Evolve the Main program into Browser.element
  8. Separate markup content to own file, fetch it with HTTP

Tree example: Use Elm UI to fill the screen and center the tree

On by Tadeusz Łazurski

index a7f36d8..cde6818 100644
--- a/src/Tree.elm
+++ b/src/Tree.elm
@@ -3,6 +3,7 @@ module Tree exposing (main)
=import Browser
=import Browser.Events
=import Dict exposing (Dict)
+import Element
=import Html exposing (Html)
=import Json.Decode exposing (Decoder)
=import List.Extra as List
@@ -51,7 +52,7 @@ type alias Rules =
=
=init : Flags -> ( Model, Cmd Msg )
=init () =
-    ( { time = 50000
+    ( { time = 0
=      , rules =
=            Dict.fromList
=                [ ( "brown"
@@ -122,17 +123,20 @@ view model =
=                        )
=                    ]
=    in
-    Html.div []
-        [ Html.h1 [] [ Html.text <| String.fromFloat model.time ]
-        , svg
-            [ viewBox "-1000 -1000 2000 2000"
-            , height "800px"
-            , width "800px"
-            ]
-            [ defs [] gradients
-            , tree
-            ]
+    Element.layout
+        [ Element.width Element.fill
+        , Element.height Element.fill
=        ]
+        (Element.html <|
+            svg
+                [ viewBox "-1000 -1000 2000 2000"
+                , height "100%"
+                , width "100%"
+                ]
+                [ defs [] gradients
+                , tree
+                ]
+        )
=
=
=update : Msg -> Model -> ( Model, Cmd Msg )

Write down steps to reproduce the tree, implement or improve some examples

On by Tadeusz Łazurski

index c58cd80..47685ff 100644
--- a/index.md
+++ b/index.md
@@ -4,6 +4,29 @@ presentation:
=  theme: solarized.css
=---
=
+Steps to reproduce the tree:
+
+Make a dot
+
+  Centered (Elm UI, viewBox, cartesian coordinates)
+
+Make a line
+
+  Play with transformations (union types)
+
+Gradients
+
+Multiple lines
+
+  Rosettes (different kinds)
+
+Groups and transformations
+
+  Spiral (recursion)
+
+Tree
+
+
=<!-- slide -->
=
=## Before the course begins
index 0382141..cbd5f2e 100644
--- a/src/CenteredDot.elm
+++ b/src/CenteredDot.elm
@@ -1,4 +1,4 @@
-module Simplest exposing (main)
+module CenteredDot exposing (main)
=
=import Element
=import Svg
new file mode 100644
index 0000000..81e07be
--- /dev/null
+++ b/src/Gradient.elm
@@ -0,0 +1,45 @@
+module Gradient exposing (main)
+
+import Element
+import Svg
+import Svg.Attributes
+
+
+main =
+    Element.layout
+        [ Element.width Element.fill
+        , Element.height Element.fill
+        ]
+        (Element.html
+            (Svg.svg
+                [ Svg.Attributes.viewBox "-100 -100 200 200" ]
+                [ Svg.defs []
+                    [ Svg.linearGradient
+                        [ Svg.Attributes.id "blue-pink-gradient"
+                        , Svg.Attributes.x1 "0"
+                        , Svg.Attributes.y1 "0"
+                        , Svg.Attributes.x2 "1"
+                        , Svg.Attributes.y2 "0"
+                        , Svg.Attributes.gradientUnits "userSpaceOnUse"
+                        ]
+                        [ Svg.stop
+                            [ Svg.Attributes.stopColor "blue"
+                            , Svg.Attributes.offset "0"
+                            ]
+                            []
+                        , Svg.stop
+                            [ Svg.Attributes.stopColor "pink"
+                            , Svg.Attributes.offset "1"
+                            ]
+                            []
+                        ]
+                    ]
+                , Svg.line
+                    [ Svg.Attributes.x2 "1"
+                    , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+                    , Svg.Attributes.transform "rotate(30) scale(100, 1)"
+                    ]
+                    []
+                ]
+            )
+        )
new file mode 100644
index 0000000..0eee932
--- /dev/null
+++ b/src/Line.elm
@@ -0,0 +1,24 @@
+module Line exposing (main)
+
+import Element
+import Svg
+import Svg.Attributes
+
+
+main =
+    Element.layout
+        [ Element.width Element.fill
+        , Element.height Element.fill
+        ]
+        (Element.html
+            (Svg.svg
+                [ Svg.Attributes.viewBox "-100 -100 200 200" ]
+                [ Svg.line
+                    [ Svg.Attributes.x2 "1"
+                    , Svg.Attributes.stroke "black"
+                    , Svg.Attributes.transform "rotate(30) scale(100, 1)"
+                    ]
+                    []
+                ]
+            )
+        )
new file mode 100644
index 0000000..f39d91f
--- /dev/null
+++ b/src/LineTypedTransformations.elm
@@ -0,0 +1,71 @@
+module LineTypedTransformations exposing (main)
+
+import Element
+import Svg
+import Svg.Attributes
+
+
+main =
+    Element.layout
+        [ Element.width Element.fill
+        , Element.height Element.fill
+        ]
+        (Element.html
+            (Svg.svg
+                [ Svg.Attributes.viewBox "-100 -100 200 200" ]
+                [ Svg.line
+                    [ Svg.Attributes.x2 "1"
+                    , Svg.Attributes.stroke "black"
+                    , transform
+                        [ Rotate 30
+                        , Scale 100 1
+                        ]
+                    ]
+                    []
+                ]
+            )
+        )
+
+
+type Transformation
+    = Identity
+    | Scale Float Float
+    | Translate Float Float
+    | Rotate Float
+
+
+
+-- transform : List Transformation -> Svg.Attribute msg
+
+
+transform transformations =
+    let
+        toString : Transformation -> String
+        toString transformation =
+            case transformation of
+                Identity ->
+                    ""
+
+                Scale x y ->
+                    "scale("
+                        ++ String.fromFloat x
+                        ++ ", "
+                        ++ String.fromFloat y
+                        ++ ")"
+
+                Translate x y ->
+                    "translate("
+                        ++ String.fromFloat x
+                        ++ ", "
+                        ++ String.fromFloat y
+                        ++ ")"
+
+                Rotate angle ->
+                    "rotate("
+                        ++ String.fromFloat angle
+                        ++ ")"
+    in
+    transformations
+        |> List.map toString
+        |> String.join " "
+        |> Svg.Attributes.transform
new file mode 100644
index 0000000..d9d2060
--- /dev/null
+++ b/src/Rosette.elm
@@ -0,0 +1,69 @@
+module Rosette exposing (main)
+
+import Element
+import Svg
+import Svg.Attributes
+
+
+main =
+    Element.layout
+        [ Element.width Element.fill
+        , Element.height Element.fill
+        ]
+        (Element.html
+            (Svg.svg
+                [ Svg.Attributes.viewBox "-100 -100 200 200" ]
+                [ Svg.defs []
+                    [ Svg.linearGradient
+                        [ Svg.Attributes.id "blue-pink-gradient"
+                        , Svg.Attributes.x1 "0"
+                        , Svg.Attributes.y1 "0"
+                        , Svg.Attributes.x2 "1"
+                        , Svg.Attributes.y2 "0"
+                        , Svg.Attributes.gradientUnits "userSpaceOnUse"
+                        ]
+                        [ Svg.stop
+                            [ Svg.Attributes.stopColor "blue"
+                            , Svg.Attributes.offset "0"
+                            ]
+                            []
+                        , Svg.stop
+                            [ Svg.Attributes.stopColor "pink"
+                            , Svg.Attributes.offset "1"
+                            ]
+                            []
+                        ]
+                    ]
+                , Svg.line
+                    [ Svg.Attributes.x2 "1"
+                    , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+                    , Svg.Attributes.transform "rotate(0) scale(100, 1)"
+                    ]
+                    []
+                , Svg.line
+                    [ Svg.Attributes.x2 "1"
+                    , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+                    , Svg.Attributes.transform "rotate(72) scale(100, 1)"
+                    ]
+                    []
+                , Svg.line
+                    [ Svg.Attributes.x2 "1"
+                    , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+                    , Svg.Attributes.transform "rotate(144) scale(100, 1)"
+                    ]
+                    []
+                , Svg.line
+                    [ Svg.Attributes.x2 "1"
+                    , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+                    , Svg.Attributes.transform "rotate(216) scale(100, 1)"
+                    ]
+                    []
+                , Svg.line
+                    [ Svg.Attributes.x2 "1"
+                    , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+                    , Svg.Attributes.transform "rotate(288) scale(100, 1)"
+                    ]
+                    []
+                ]
+            )
+        )
new file mode 100644
index 0000000..dcd20c1
--- /dev/null
+++ b/src/RosetteTypedTransformations.elm
@@ -0,0 +1,125 @@
+module RosetteTypedTransformations exposing (main)
+
+import Element
+import Svg
+import Svg.Attributes
+
+
+main =
+    Element.layout
+        [ Element.width Element.fill
+        , Element.height Element.fill
+        ]
+        (Element.html
+            (Svg.svg
+                [ Svg.Attributes.viewBox "-100 -100 200 200" ]
+                [ Svg.defs []
+                    [ Svg.linearGradient
+                        [ Svg.Attributes.id "blue-pink-gradient"
+                        , Svg.Attributes.x1 "0"
+                        , Svg.Attributes.y1 "0"
+                        , Svg.Attributes.x2 "1"
+                        , Svg.Attributes.y2 "0"
+                        , Svg.Attributes.gradientUnits "userSpaceOnUse"
+                        ]
+                        [ Svg.stop
+                            [ Svg.Attributes.stopColor "blue"
+                            , Svg.Attributes.offset "0"
+                            ]
+                            []
+                        , Svg.stop
+                            [ Svg.Attributes.stopColor "pink"
+                            , Svg.Attributes.offset "1"
+                            ]
+                            []
+                        ]
+                    ]
+                , Svg.line
+                    [ Svg.Attributes.x2 "1"
+                    , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+                    , transform
+                        [ Rotate 0
+                        , Scale 100 1
+                        ]
+                    ]
+                    []
+                , Svg.line
+                    [ Svg.Attributes.x2 "1"
+                    , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+                    , transform
+                        [ Rotate 72
+                        , Scale 100 1
+                        ]
+                    ]
+                    []
+                , Svg.line
+                    [ Svg.Attributes.x2 "1"
+                    , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+                    , transform
+                        [ Rotate 144
+                        , Scale 100 1
+                        ]
+                    ]
+                    []
+                , Svg.line
+                    [ Svg.Attributes.x2 "1"
+                    , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+                    , transform
+                        [ Rotate 216
+                        , Scale 100 1
+                        ]
+                    ]
+                    []
+                , Svg.line
+                    [ Svg.Attributes.x2 "1"
+                    , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+                    , transform
+                        [ Rotate 288
+                        , Scale 100 1
+                        ]
+                    ]
+                    []
+                ]
+            )
+        )
+
+
+type Transformation
+    = Identity
+    | Scale Float Float
+    | Translate Float Float
+    | Rotate Float
+
+
+transform : List Transformation -> Svg.Attribute msg
+transform transformations =
+    let
+        toString : Transformation -> String
+        toString transformation =
+            case transformation of
+                Identity ->
+                    ""
+
+                Scale x y ->
+                    "scale("
+                        ++ String.fromFloat x
+                        ++ ", "
+                        ++ String.fromFloat y
+                        ++ ")"
+
+                Translate x y ->
+                    "translate("
+                        ++ String.fromFloat x
+                        ++ ", "
+                        ++ String.fromFloat y
+                        ++ ")"
+
+                Rotate angle ->
+                    "rotate("
+                        ++ String.fromFloat angle
+                        ++ ")"
+    in
+    transformations
+        |> List.map toString
+        |> String.join " "
+        |> Svg.Attributes.transform
index 8710e3c..9686d17 100644
--- a/src/Simplest.elm
+++ b/src/Simplest.elm
@@ -6,10 +6,12 @@ import Svg.Attributes
=
=main =
=    Svg.svg [ Svg.Attributes.style "background: pink" ]
-        [ Svg.circle
-            [ Svg.Attributes.r "10"
-            , Svg.Attributes.cx "30"
-            , Svg.Attributes.cy "30"
+        [ Svg.line
+            [ Svg.Attributes.x1 "0"
+            , Svg.Attributes.x2 "1"
+            , Svg.Attributes.y1 "0"
+            , Svg.Attributes.y2 "0"
+            , Svg.Attributes.stroke "black"
=            ]
=            []
=        ]
new file mode 100644
index 0000000..f2e2aa0
--- /dev/null
+++ b/src/Spiral.elm
@@ -0,0 +1,110 @@
+module Spiral exposing (main)
+
+import Element
+import Svg
+import Svg.Attributes
+
+
+main =
+    let
+        defs =
+            Svg.defs []
+                [ Svg.linearGradient
+                    [ Svg.Attributes.id "blue-pink-gradient"
+                    , Svg.Attributes.x1 "0"
+                    , Svg.Attributes.y1 "0"
+                    , Svg.Attributes.x2 "1"
+                    , Svg.Attributes.y2 "0"
+                    , Svg.Attributes.gradientUnits "userSpaceOnUse"
+                    ]
+                    [ Svg.stop
+                        [ Svg.Attributes.stopColor "blue"
+                        , Svg.Attributes.offset "0"
+                        ]
+                        []
+                    , Svg.stop
+                        [ Svg.Attributes.stopColor "pink"
+                        , Svg.Attributes.offset "1"
+                        ]
+                        []
+                    ]
+                ]
+    in
+    Element.layout
+        [ Element.width Element.fill
+        , Element.height Element.fill
+        ]
+        (Element.html
+            (Svg.svg
+                [ Svg.Attributes.viewBox "-100 -100 200 200" ]
+                (defs :: spiral 500)
+            )
+        )
+
+
+spiral : Int -> List (Svg.Svg msg)
+spiral age =
+    if age > 0 then
+        let
+            length =
+                500 / toFloat age
+        in
+        [ Svg.line
+            [ Svg.Attributes.x2 "1"
+            , Svg.Attributes.stroke "url(#blue-pink-gradient)"
+            , transform [ Scale length 1 ]
+            ]
+            []
+        , Svg.g
+            [ transform
+                [ Identity
+                , Translate length 0
+                , Rotate 15
+                ]
+            ]
+            (spiral (age - 1))
+        ]
+
+    else
+        []
+
+
+type Transformation
+    = Identity
+    | Scale Float Float
+    | Translate Float Float
+    | Rotate Float
+
+
+transform : List Transformation -> Svg.Attribute msg
+transform transformations =
+    let
+        toString : Transformation -> String
+        toString transformation =
+            case transformation of
+                Identity ->
+                    ""
+
+                Scale x y ->
+                    "scale("
+                        ++ String.fromFloat x
+                        ++ ", "
+                        ++ String.fromFloat y
+                        ++ ")"
+
+                Translate x y ->
+                    "translate("
+                        ++ String.fromFloat x
+                        ++ ", "
+                        ++ String.fromFloat y
+                        ++ ")"
+
+                Rotate angle ->
+                    "rotate("
+                        ++ String.fromFloat angle
+                        ++ ")"
+    in
+    transformations
+        |> List.map toString
+        |> String.join " "
+        |> Svg.Attributes.transform

Make transformations example interactive

On by Tadeusz Łazurski

Separate axes from CartesianPlane.graph.

index 6e4ad58..6029a39 100644
--- a/src/CartesianPlane.elm
+++ b/src/CartesianPlane.elm
@@ -1,25 +1,19 @@
-module CartesianPlane exposing (graph)
+module CartesianPlane exposing (axes, graph)
=
+import Direction2d
+import Geometry.Svg
=import Html exposing (Html)
+import LineSegment2d
+import Point2d exposing (Point2d)
=import Svg exposing (..)
=import Svg.Attributes exposing (..)
+import Triangle2d
+import Vector2d
=
=
=graph : Float -> List (Svg msg) -> Html msg
=graph size shapes =
=    let
-        top =
-            0 - size / 2
-
-        left =
-            0 - size / 2
-
-        bottom =
-            size / 2
-
-        right =
-            size / 2
-
=        hairline =
=            size / 1000
=
@@ -27,29 +21,14 @@ graph size shapes =
=            size / 200
=
=        background =
-            g []
-                [ line
-                    [ x1 (String.fromFloat left)
-                    , y1 "0"
-                    , x2 (String.fromFloat right)
-                    , y2 "0"
-                    , stroke "black"
-                    , strokeWidth (String.fromFloat hairline)
-                    ]
-                    []
-                , line
-                    [ x1 "0"
-                    , x2 "0"
-                    , y1 (String.fromFloat top)
-                    , y2 (String.fromFloat bottom)
-                    , stroke "black"
-                    , strokeWidth (String.fromFloat hairline)
-                    ]
-                    []
+            axes
+                [ stroke "gray"
+                , fill "gray"
=                ]
+                size
=    in
=    svg
-        [ [ left, top, size, size ]
+        [ [ negate size / 2, negate size / 2, size, size ]
=            |> List.map String.fromFloat
=            |> String.join " "
=            |> viewBox
@@ -59,3 +38,85 @@ graph size shapes =
=        , Svg.Attributes.style "width: 100%, height: 100%"
=        ]
=        (background :: shapes)
+
+
+axes attributes size =
+    let
+        max =
+            size / 2
+
+        min =
+            negate max
+
+        xAxis =
+            { start = Point2d.fromCoordinates ( min, 0 )
+            , end = Point2d.fromCoordinates ( max, 0 )
+            }
+
+        yAxis =
+            { start = Point2d.fromCoordinates ( 0, min )
+            , end = Point2d.fromCoordinates ( 0, max )
+            }
+    in
+    g []
+        [ arrow
+            attributes
+            xAxis.start
+            xAxis.end
+        , arrow
+            attributes
+            yAxis.start
+            yAxis.end
+        , label [ fontSize "8", color "gray" ] (xAxis.end |> Point2d.translateIn (Direction2d.fromAngle (degrees -135)) 10) "x"
+        , label [ fontSize "8", color "gray" ] (yAxis.end |> Point2d.translateIn (Direction2d.fromAngle (degrees -45)) 10) "y"
+        , label [ fontSize "8", color "gray" ] (Point2d.origin |> Point2d.translateIn (Direction2d.fromAngle (degrees 135)) 10) "O"
+        ]
+
+
+arrow : List (Svg.Attribute msg) -> Point2d -> Point2d -> Svg msg
+arrow attributes start end =
+    let
+        origin =
+            Point2d.fromCoordinates ( 0, 0 )
+
+        direction =
+            Direction2d.from start end
+
+        triangle =
+            Triangle2d.fromVertices
+                ( Point2d.fromCoordinates ( 0, 0 )
+                , Point2d.fromCoordinates ( -4, -2 )
+                , Point2d.fromCoordinates ( -4, 2 )
+                )
+
+        vector =
+            Vector2d.from origin end
+    in
+    case direction of
+        Nothing ->
+            g [] []
+
+        Just dir ->
+            g []
+                [ triangle
+                    |> Triangle2d.rotateAround origin (Direction2d.toAngle dir)
+                    |> Triangle2d.translateBy vector
+                    |> Geometry.Svg.triangle2d ([ strokeWidth "0" ] ++ attributes)
+                , end
+                    |> Point2d.translateIn dir -4
+                    |> LineSegment2d.from start
+                    |> Geometry.Svg.lineSegment2d attributes
+                ]
+
+
+label : List (Svg.Attribute msg) -> Point2d -> String -> Svg msg
+label attributes center content =
+    text_
+        ([ x <| String.fromFloat (Point2d.xCoordinate center)
+         , y <| String.fromFloat (Point2d.yCoordinate center)
+         , dominantBaseline "central"
+         , textAnchor "middle"
+         ]
+            ++ attributes
+        )
+        [ text content ]
index 80a3718..744c43a 100644
--- a/src/Transformations.elm
+++ b/src/Transformations.elm
@@ -1,13 +1,20 @@
=module Transformations exposing (main)
=
+import Array exposing (Array)
=import Browser
=import Browser.Events
=import CartesianPlane
=import Dict exposing (Dict)
-import Element
+import Element exposing (Element)
+import Element.Background as Background
+import Element.Border as Border
+import Element.Input as Input
+import Geometry.Svg
=import Html exposing (Html)
=import Json.Decode exposing (Decoder)
+import LineSegment2d
=import List.Extra as List
+import Point2d
=import Svg exposing (..)
=import Svg.Attributes exposing (..)
=
@@ -26,7 +33,7 @@ type alias Flags =
=
=
=type alias Model =
-    List Transformation
+    Array Transformation
=
=
=type Transformation
@@ -37,16 +44,14 @@ type Transformation
=
=
=type Msg
-    = Progress Float
-    | Regress Float
+    = AddTransformation Transformation
+    | DeleteTransformation Int
+    | SetTransformation Int Transformation
=
=
=init : Flags -> ( Model, Cmd Msg )
=init () =
-    ( [ Identity
-      , Scale 2 2
-      , Translate 20 0
-      ]
+    ( Array.empty
=    , Cmd.none
=    )
=
@@ -54,13 +59,23 @@ init () =
=view : Model -> Html Msg
=view model =
=    let
+        transformations =
+            Array.toList model
+
=        wrapper element =
-            Element.el
-                [ Element.width (Element.maximum 600 Element.fill), Element.centerX ]
-                (Element.html element)
+            Element.column
+                [ Element.width (Element.maximum 600 Element.fill)
+                , Element.centerX
+                , Element.spacing 20
+                ]
+                [ Element.el
+                    [ Element.width Element.fill ]
+                    (Element.html element)
+                , transformationsUI transformations
+                ]
=
=        shape =
-            g [ transform (apply model) ]
+            g [ transform (apply transformations) ]
=                [ line
=                    [ x1 "0"
=                    , x2 "100"
@@ -71,13 +86,20 @@ view model =
=                    ]
=                    []
=                , circle [ cx "0", cy "0", r "2", fill "red" ] []
+                , grid
+                    [ stroke "pink"
+                    , fill "pink"
+                    , strokeWidth "0.3"
+                    ]
+                    10
+                    30
=
=                -- , rect [ x "", y "-2", width "1", height "4" ] []
=                ]
=    in
=    shape
=        |> List.singleton
-        |> CartesianPlane.graph 600
+        |> CartesianPlane.graph 300
=        |> wrapper
=        |> Element.layout
=            [ Element.height Element.fill
@@ -85,16 +107,187 @@ view model =
=            ]
=
=
+transformationsUI : List Transformation -> Element Msg
+transformationsUI transformations =
+    let
+        addButtons =
+            [ Input.button []
+                { onPress = Just (AddTransformation (Translate 0 0))
+                , label = Element.text "Translate"
+                }
+            , Input.button []
+                { onPress = Just (AddTransformation (Scale 1 1))
+                , label = Element.text "Scale"
+                }
+            , Input.button []
+                { onPress = Just (AddTransformation (Rotate 0))
+                , label = Element.text "Rotate"
+                }
+            ]
+
+        currentTrasformations =
+            transformations
+                |> List.indexedMap transformationUI
+    in
+    Element.column
+        [ Element.width Element.fill
+        , Element.spacing 10
+        ]
+        [ Element.row
+            [ Element.width Element.fill
+            , Element.spacing 10
+            ]
+            addButtons
+        , Element.column
+            [ Element.width Element.fill
+            , Element.spacing 10
+            ]
+            currentTrasformations
+        ]
+
+
+transformationUI : Int -> Transformation -> Element Msg
+transformationUI index transformation =
+    let
+        sliderBackground =
+            Element.el
+                [ Element.width Element.fill
+                , Element.height (Element.px 2)
+                , Element.centerY
+                , Background.color <| Element.rgb 0.7 0.7 0.7
+                , Border.rounded 2
+                ]
+                Element.none
+
+        controls =
+            case transformation of
+                Identity ->
+                    [ Element.text <| Debug.toString transformation ]
+
+                Scale horizontal vertical ->
+                    [ Input.slider
+                        [ Element.behindContent sliderBackground
+                        ]
+                        { onChange =
+                            \x ->
+                                SetTransformation index (Scale x vertical)
+                        , label = Input.labelLeft [] (Element.text "horizontal")
+                        , min = 0
+                        , max = 10
+                        , value = horizontal
+                        , thumb = Input.defaultThumb
+                        , step = Nothing
+                        }
+                    , Input.slider
+                        [ Element.behindContent sliderBackground
+                        ]
+                        { onChange =
+                            \y ->
+                                SetTransformation index (Scale horizontal y)
+                        , label = Input.labelLeft [] (Element.text "vertical")
+                        , min = 0
+                        , max = 10
+                        , value = vertical
+                        , thumb = Input.defaultThumb
+                        , step = Nothing
+                        }
+                    ]
+
+                Translate x y ->
+                    [ Input.slider
+                        [ Element.behindContent sliderBackground
+                        ]
+                        { onChange =
+                            \value ->
+                                SetTransformation index (Translate value y)
+                        , label = Input.labelLeft [] (Element.text "x")
+                        , min = -100
+                        , max = 100
+                        , value = x
+                        , thumb = Input.defaultThumb
+                        , step = Nothing
+                        }
+                    , Input.slider
+                        [ Element.behindContent sliderBackground
+                        ]
+                        { onChange =
+                            \value ->
+                                SetTransformation index (Translate x value)
+                        , label = Input.labelLeft [] (Element.text "y")
+                        , min = -100
+                        , max = 100
+                        , value = y
+                        , thumb = Input.defaultThumb
+                        , step = Nothing
+                        }
+                    ]
+
+                Rotate angle ->
+                    [ Input.slider
+                        [ Element.behindContent sliderBackground
+                        ]
+                        { onChange =
+                            \value ->
+                                SetTransformation index (Rotate value)
+                        , label = Input.labelLeft [] (Element.text "angle")
+                        , min = -360
+                        , max = 360
+                        , value = angle
+                        , thumb = Input.defaultThumb
+                        , step = Nothing
+                        }
+                    ]
+    in
+    Element.column
+        [ Element.width Element.fill
+        , Background.color (Element.rgb 0.9 0.9 0.9)
+        , Element.padding 5
+        , Element.spacing 20
+        ]
+        [ Element.row [ Element.width Element.fill ]
+            [ transformation
+                |> List.singleton
+                |> apply
+                |> Element.text
+                |> Element.el [ Element.width Element.fill ]
+            , Input.button []
+                { onPress = Just (DeleteTransformation index)
+                , label = Element.text "X"
+                }
+            ]
+        , Element.column
+            [ Element.width Element.fill
+            , Element.spacing 20
+            ]
+            controls
+        ]
+
+
=update : Msg -> Model -> ( Model, Cmd Msg )
=update msg model =
=    case msg of
-        Progress delta ->
-            ( model
+        AddTransformation transformation ->
+            ( Array.push transformation model
=            , Cmd.none
=            )
=
-        Regress delta ->
-            ( model
+        DeleteTransformation index ->
+            let
+                end =
+                    Array.length model
+
+                front =
+                    Array.slice 0 index model
+
+                back =
+                    Array.slice (index + 1) end model
+            in
+            ( Array.append front back
+            , Cmd.none
+            )
+
+        SetTransformation index transformation ->
+            ( Array.set index transformation model
=            , Cmd.none
=            )
=
@@ -135,3 +328,43 @@ apply transformations =
=    transformations
=        |> List.map toString
=        |> String.join " "
+
+
+grid : List (Svg.Attribute msg) -> Float -> Float -> Svg msg
+grid attributes unit size =
+    let
+        positiveValues =
+            size
+                / 2
+                |> floor
+                |> List.range 1
+                |> List.map toFloat
+                |> List.map ((*) unit)
+
+        negativeValues =
+            positiveValues
+                |> List.map negate
+
+        max =
+            unit * size / 2
+
+        min =
+            negate max
+    in
+    ((positiveValues ++ negativeValues)
+        |> List.map
+            (\value ->
+                [ ( Point2d.fromCoordinates ( value, min )
+                  , Point2d.fromCoordinates ( value, max )
+                  )
+                , ( Point2d.fromCoordinates ( min, value )
+                  , Point2d.fromCoordinates ( max, value )
+                  )
+                ]
+            )
+        |> List.concat
+        |> List.map LineSegment2d.fromEndpoints
+        |> List.map (Geometry.Svg.lineSegment2d attributes)
+    )
+        |> (::) (CartesianPlane.axes attributes (size * unit))
+        |> g []

Make sliders stepped in Transformations example

On by Tadeusz Łazurski

Also other minor tweaks.

index 744c43a..0d66cf5 100644
--- a/src/Transformations.elm
+++ b/src/Transformations.elm
@@ -51,7 +51,11 @@ type Msg
=
=init : Flags -> ( Model, Cmd Msg )
=init () =
-    ( Array.empty
+    ( Array.fromList
+        [ Translate 0 0
+        , Rotate 0
+        , Scale 1 1
+        ]
=    , Cmd.none
=    )
=
@@ -69,9 +73,17 @@ view model =
=                , Element.spacing 20
=                ]
=                [ Element.el
-                    [ Element.width Element.fill ]
+                    [ Element.width Element.fill
+                    ]
=                    (Element.html element)
-                , transformationsUI transformations
+                , Element.row [ Element.width Element.fill ]
+                    [ Element.el
+                        [ Background.color (Element.rgb 1 0.8 0.8)
+                        , Element.padding 10
+                        , Element.width Element.fill
+                        ]
+                        (transformationsUI transformations)
+                    ]
=                ]
=
=        shape =
@@ -111,7 +123,8 @@ transformationsUI : List Transformation -> Element Msg
=transformationsUI transformations =
=    let
=        addButtons =
-            [ Input.button []
+            [ Element.text "Add transformation: "
+            , Input.button []
=                { onPress = Just (AddTransformation (Translate 0 0))
=                , label = Element.text "Translate"
=                }
@@ -176,7 +189,7 @@ transformationUI index transformation =
=                        , max = 10
=                        , value = horizontal
=                        , thumb = Input.defaultThumb
-                        , step = Nothing
+                        , step = Just 0.1
=                        }
=                    , Input.slider
=                        [ Element.behindContent sliderBackground
@@ -189,7 +202,7 @@ transformationUI index transformation =
=                        , max = 10
=                        , value = vertical
=                        , thumb = Input.defaultThumb
-                        , step = Nothing
+                        , step = Just 0.1
=                        }
=                    ]
=
@@ -205,7 +218,7 @@ transformationUI index transformation =
=                        , max = 100
=                        , value = x
=                        , thumb = Input.defaultThumb
-                        , step = Nothing
+                        , step = Just 1
=                        }
=                    , Input.slider
=                        [ Element.behindContent sliderBackground
@@ -218,7 +231,7 @@ transformationUI index transformation =
=                        , max = 100
=                        , value = y
=                        , thumb = Input.defaultThumb
-                        , step = Nothing
+                        , step = Just 1
=                        }
=                    ]
=
@@ -234,13 +247,14 @@ transformationUI index transformation =
=                        , max = 360
=                        , value = angle
=                        , thumb = Input.defaultThumb
-                        , step = Nothing
+                        , step = Just 1
=                        }
=                    ]
=    in
=    Element.column
=        [ Element.width Element.fill
-        , Background.color (Element.rgb 0.9 0.9 0.9)
+        , Border.color (Element.rgb 0.9 0.9 0.9)
+        , Border.width 3
=        , Element.padding 5
=        , Element.spacing 20
=        ]

Implement NestedTransformations example

On by Tadeusz Łazurski

index d17943a..6cfda8a 100644
--- a/elm.json
+++ b/elm.json
@@ -11,11 +11,13 @@
=            "elm/html": "1.0.0",
=            "elm/json": "1.0.0",
=            "elm/svg": "1.0.1",
+            "elm-community/basics-extra": "4.0.0",
=            "elm-community/list-extra": "8.1.0",
=            "elm-explorations/markdown": "1.0.0",
=            "ianmackenzie/elm-geometry": "1.2.1",
=            "ianmackenzie/elm-geometry-svg": "1.0.2",
-            "mdgriffith/elm-ui": "1.1.0"
+            "mdgriffith/elm-ui": "1.1.0",
+            "turboMaCk/any-dict": "1.0.1"
=        },
=        "indirect": {
=            "elm/time": "1.0.0",
new file mode 100644
index 0000000..30da4f4
--- /dev/null
+++ b/src/NestedTransformations.elm
@@ -0,0 +1,437 @@
+module Transformations exposing (main)
+
+import Array exposing (Array)
+import Basics.Extra exposing (..)
+import Browser
+import Browser.Events
+import CartesianPlane
+import Dict.Any as Dict exposing (AnyDict)
+import Element exposing (Element)
+import Element.Background as Background
+import Element.Border as Border
+import Element.Input as Input
+import Geometry.Svg
+import Html exposing (Html)
+import Json.Decode exposing (Decoder)
+import LineSegment2d
+import List.Extra as List
+import Point2d
+import Svg exposing (..)
+import Svg.Attributes exposing (..)
+
+
+main =
+    Browser.element
+        { init = init
+        , view = view
+        , update = update
+        , subscriptions = subscriptions
+        }
+
+
+type alias Flags =
+    ()
+
+
+type alias Model =
+    AnyDict String Group (Array Transformation)
+
+
+type Transformation
+    = Identity
+    | Scale Float Float
+    | Translate Float Float
+    | Rotate Float
+
+
+type Group
+    = Pink
+    | Green
+
+
+type Msg
+    = Msg Group GroupMsg
+
+
+type GroupMsg
+    = AddTransformation Transformation
+    | DeleteTransformation Int
+    | SetTransformation Int Transformation
+
+
+init : Flags -> ( Model, Cmd Msg )
+init () =
+    ( Dict.empty Debug.toString
+        |> Dict.insert Pink
+            (Array.fromList
+                [ Translate 0 0
+                , Rotate 0
+                , Scale 1 1
+                ]
+            )
+        |> Dict.insert Green
+            (Array.fromList
+                [ Translate 0 0
+                , Rotate 0
+                , Scale 1 1
+                ]
+            )
+    , Cmd.none
+    )
+
+
+view : Model -> Html Msg
+view model =
+    let
+        wrapper element =
+            Element.column
+                [ Element.width (Element.maximum 600 Element.fill)
+                , Element.centerX
+                , Element.spacing 20
+                ]
+                [ Element.el
+                    [ Element.width Element.fill
+                    ]
+                    (Element.html element)
+                , Element.row [ Element.width Element.fill ]
+                    (model
+                        |> Dict.toList
+                        |> List.map (uncurry controls)
+                    )
+                ]
+
+        controls : Group -> Array Transformation -> Element Msg
+        controls group transformations =
+            Element.el
+                [ Background.color (toColor group)
+                , Element.padding 10
+                , Element.width Element.fill
+                ]
+                (transformations
+                    |> Array.toList
+                    |> transformationsUI
+                    |> Element.map (Msg group)
+                )
+
+        shape =
+            Dict.foldr
+                nestTransformationsGroup
+                (g [] [])
+                model
+
+        nestTransformationsGroup : Group -> Array Transformation -> Svg Msg -> Svg Msg
+        nestTransformationsGroup group transformations item =
+            let
+                transformation =
+                    transformations |> Array.toList |> apply
+
+                color =
+                    group
+                        |> Debug.toString
+                        |> String.toLower
+            in
+            g [ transform transformation ]
+                [ line
+                    [ x1 "0"
+                    , x2 "100"
+                    , y1 "0"
+                    , y2 "0"
+                    , stroke color
+                    , strokeWidth "1"
+                    ]
+                    []
+                , circle [ cx "0", cy "0", r "2", fill color ] []
+                , item
+                ]
+    in
+    shape
+        |> List.singleton
+        |> CartesianPlane.graph 300
+        |> wrapper
+        |> Element.layout
+            [ Element.height Element.fill
+            , Element.width Element.fill
+            ]
+
+
+transformationsUI : List Transformation -> Element GroupMsg
+transformationsUI transformations =
+    let
+        addButtons =
+            [ Element.text "Add transformation: "
+            , Input.button []
+                { onPress = Just (AddTransformation (Translate 0 0))
+                , label = Element.text "Translate"
+                }
+            , Input.button []
+                { onPress = Just (AddTransformation (Scale 1 1))
+                , label = Element.text "Scale"
+                }
+            , Input.button []
+                { onPress = Just (AddTransformation (Rotate 0))
+                , label = Element.text "Rotate"
+                }
+            ]
+
+        currentTrasformations =
+            transformations
+                |> List.indexedMap transformationUI
+    in
+    Element.column
+        [ Element.width Element.fill
+        , Element.spacing 10
+        ]
+        [ Element.row
+            [ Element.width Element.fill
+            , Element.spacing 10
+            ]
+            addButtons
+        , Element.column
+            [ Element.width Element.fill
+            , Element.spacing 10
+            ]
+            currentTrasformations
+        ]
+
+
+transformationUI : Int -> Transformation -> Element GroupMsg
+transformationUI index transformation =
+    let
+        sliderBackground =
+            Element.el
+                [ Element.width Element.fill
+                , Element.height (Element.px 2)
+                , Element.centerY
+                , Background.color <| Element.rgb 0.7 0.7 0.7
+                , Border.rounded 2
+                ]
+                Element.none
+
+        controls =
+            case transformation of
+                Identity ->
+                    [ Element.text <| Debug.toString transformation ]
+
+                Scale horizontal vertical ->
+                    [ Input.slider
+                        [ Element.behindContent sliderBackground
+                        ]
+                        { onChange =
+                            \x ->
+                                SetTransformation index (Scale x vertical)
+                        , label = Input.labelLeft [] (Element.text "horizontal")
+                        , min = 0
+                        , max = 10
+                        , value = horizontal
+                        , thumb = Input.defaultThumb
+                        , step = Just 0.1
+                        }
+                    , Input.slider
+                        [ Element.behindContent sliderBackground
+                        ]
+                        { onChange =
+                            \y ->
+                                SetTransformation index (Scale horizontal y)
+                        , label = Input.labelLeft [] (Element.text "vertical")
+                        , min = 0
+                        , max = 10
+                        , value = vertical
+                        , thumb = Input.defaultThumb
+                        , step = Just 0.1
+                        }
+                    ]
+
+                Translate x y ->
+                    [ Input.slider
+                        [ Element.behindContent sliderBackground
+                        ]
+                        { onChange =
+                            \value ->
+                                SetTransformation index (Translate value y)
+                        , label = Input.labelLeft [] (Element.text "x")
+                        , min = -100
+                        , max = 100
+                        , value = x
+                        , thumb = Input.defaultThumb
+                        , step = Just 1
+                        }
+                    , Input.slider
+                        [ Element.behindContent sliderBackground
+                        ]
+                        { onChange =
+                            \value ->
+                                SetTransformation index (Translate x value)
+                        , label = Input.labelLeft [] (Element.text "y")
+                        , min = -100
+                        , max = 100
+                        , value = y
+                        , thumb = Input.defaultThumb
+                        , step = Just 1
+                        }
+                    ]
+
+                Rotate angle ->
+                    [ Input.slider
+                        [ Element.behindContent sliderBackground
+                        ]
+                        { onChange =
+                            \value ->
+                                SetTransformation index (Rotate value)
+                        , label = Input.labelLeft [] (Element.text "angle")
+                        , min = -360
+                        , max = 360
+                        , value = angle
+                        , thumb = Input.defaultThumb
+                        , step = Just 1
+                        }
+                    ]
+    in
+    Element.column
+        [ Element.width Element.fill
+        , Border.color (Element.rgb 0.9 0.9 0.9)
+        , Border.width 3
+        , Element.padding 5
+        , Element.spacing 20
+        ]
+        [ Element.row [ Element.width Element.fill ]
+            [ transformation
+                |> List.singleton
+                |> apply
+                |> Element.text
+                |> Element.el [ Element.width Element.fill ]
+            , Input.button []
+                { onPress = Just (DeleteTransformation index)
+                , label = Element.text "X"
+                }
+            ]
+        , Element.column
+            [ Element.width Element.fill
+            , Element.spacing 20
+            ]
+            controls
+        ]
+
+
+update : Msg -> Model -> ( Model, Cmd Msg )
+update (Msg group msg) model =
+    let
+        transformations =
+            model
+                |> Dict.get group
+                |> Maybe.withDefault Array.empty
+                |> (\current ->
+                        case msg of
+                            AddTransformation transformation ->
+                                Array.push transformation current
+
+                            DeleteTransformation index ->
+                                arrayDelete index current
+
+                            SetTransformation index transformation ->
+                                Array.set index transformation current
+                   )
+
+        arrayDelete index array =
+            let
+                end =
+                    Array.length array
+
+                front =
+                    Array.slice 0 index array
+
+                back =
+                    Array.slice (index + 1) end array
+            in
+            Array.append front back
+    in
+    ( Dict.insert group transformations model
+    , Cmd.none
+    )
+
+
+subscriptions : Model -> Sub Msg
+subscriptions model =
+    Sub.none
+
+
+apply : List Transformation -> String
+apply transformations =
+    let
+        toString : Transformation -> String
+        toString transformation =
+            case transformation of
+                Identity ->
+                    ""
+
+                Scale x y ->
+                    "scale("
+                        ++ String.fromFloat x
+                        ++ ", "
+                        ++ String.fromFloat y
+                        ++ ")"
+
+                Translate x y ->
+                    "translate("
+                        ++ String.fromFloat x
+                        ++ ", "
+                        ++ String.fromFloat y
+                        ++ ")"
+
+                Rotate angle ->
+                    "rotate("
+                        ++ String.fromFloat angle
+                        ++ ")"
+    in
+    transformations
+        |> List.map toString
+        |> String.join " "
+
+
+toColor : Group -> Element.Color
+toColor group =
+    case group of
+        Pink ->
+            Element.rgb 1 0.73 0.8
+
+        Green ->
+            Element.rgb 0.0 0.5 0.0
+
+
+grid : List (Svg.Attribute msg) -> Float -> Float -> Svg msg
+grid attributes unit size =
+    let
+        positiveValues =
+            size
+                / 2
+                |> floor
+                |> List.range 1
+                |> List.map toFloat
+                |> List.map ((*) unit)
+
+        negativeValues =
+            positiveValues
+                |> List.map negate
+
+        max =
+            unit * size / 2
+
+        min =
+            negate max
+    in
+    ((positiveValues ++ negativeValues)
+        |> List.map
+            (\value ->
+                [ ( Point2d.fromCoordinates ( value, min )
+                  , Point2d.fromCoordinates ( value, max )
+                  )
+                , ( Point2d.fromCoordinates ( min, value )
+                  , Point2d.fromCoordinates ( max, value )
+                  )
+                ]
+            )
+        |> List.concat
+        |> List.map LineSegment2d.fromEndpoints
+        |> List.map (Geometry.Svg.lineSegment2d attributes)
+    )
+        |> (::) (CartesianPlane.axes attributes (size * unit))
+        |> g []

Reimplement Main with Elm Markup, nest a simple counter program

On by Tadeusz Łazurski

The idea is that Main will be our website and all the example programs will be nested in it.

index 6cfda8a..d305cfa 100644
--- a/elm.json
+++ b/elm.json
@@ -13,13 +13,16 @@
=            "elm/svg": "1.0.1",
=            "elm-community/basics-extra": "4.0.0",
=            "elm-community/list-extra": "8.1.0",
+            "elm-community/result-extra": "2.2.1",
=            "elm-explorations/markdown": "1.0.0",
=            "ianmackenzie/elm-geometry": "1.2.1",
=            "ianmackenzie/elm-geometry-svg": "1.0.2",
+            "mdgriffith/elm-markup": "1.0.0",
=            "mdgriffith/elm-ui": "1.1.0",
=            "turboMaCk/any-dict": "1.0.1"
=        },
=        "indirect": {
+            "elm/parser": "1.1.0",
=            "elm/time": "1.0.0",
=            "elm/url": "1.0.0",
=            "elm/virtual-dom": "1.0.2",
new file mode 100644
index 0000000..0f0689c
--- /dev/null
+++ b/src/Counter.elm
@@ -0,0 +1,71 @@
+module Counter exposing
+    ( Model
+    , Msg
+    , init
+    , main
+    , ui
+    , update
+    )
+
+import Browser
+import Element exposing (Element)
+import Element.Border as Border
+import Element.Input as Input
+import Html exposing (Html)
+
+
+main =
+    Browser.sandbox
+        { init = init
+        , view = view
+        , update = update
+        }
+
+
+type alias Model =
+    Int
+
+
+type Msg
+    = Increment
+    | Decrement
+
+
+init =
+    0
+
+
+view : Model -> Html Msg
+view model =
+    model
+        |> ui
+        |> Element.layout []
+
+
+ui : Model -> Element Msg
+ui model =
+    Element.row
+        [ Element.padding 10
+        , Element.spacing 10
+        ]
+        [ Input.button []
+            { onPress = Just Decrement
+            , label = Element.text "-"
+            }
+        , model
+            |> String.fromInt
+            |> Element.text
+        , Input.button []
+            { onPress = Just Increment
+            , label = Element.text "+"
+            }
+        ]
+
+
+update msg model =
+    case msg of
+        Increment ->
+            model + 1
+
+        Decrement ->
+            model - 1
index f18bc73..d1066f3 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -1,578 +1,134 @@
=module Main exposing (main)
=
-import Browser exposing (Document)
-import Browser.Events
-import CartesianCoordinates
-import Dict
-import Element
-import Element.Font as Font
-import Element.Keyed
-import Html
-import Html.Attributes
-import Json.Decode as Decode exposing (Decoder)
-import PolarCoordinates
-import Presentation exposing (Slide, markdown)
+import Browser
+import Counter
+import Element exposing (Element)
+import Element.Border as Border
+import Element.Input as Input
+import Html exposing (Html)
+import Mark
+import Mark.Custom
+import Result.Extra as Result
=
=
=main =
-    Browser.document
+    Browser.sandbox
=        { init = init
=        , view = view
=        , update = update
-        , subscriptions = subscriptions
=        }
=
=
-type alias Flags =
-    ()
-
-
=type alias Model =
-    { currentSlide : Int
-
-    -- Nested programs
-    , cartesianCoordinates : CartesianCoordinates.Model
-    , polarCoordinates : PolarCoordinates.Model
-    }
+    { counter : Counter.Model }
=
=
=type Msg
-    = Next
-    | Previous
-      -- Nested programs
-    | CartesianCoordinatesMsg CartesianCoordinates.Msg
-    | PolarCoordinatesMsg PolarCoordinates.Msg
+    = CounterMsg Counter.Msg
=
=
-init : Flags -> ( Model, Cmd Msg )
-init flags =
-    let
-        ( cartesianCoordinatesModel, cartesianCoordinatesCmd ) =
-            CartesianCoordinates.init ()
+init =
+    { counter = Counter.init
+    }
=
-        ( polarCoordinatesModel, polarCoordinatesCmd ) =
-            PolarCoordinates.init ()
-    in
-    ( { currentSlide = 0
-      , cartesianCoordinates = cartesianCoordinatesModel
-      , polarCoordinates = polarCoordinatesModel
-      }
-    , Cmd.batch
-        [ Cmd.map CartesianCoordinatesMsg cartesianCoordinatesCmd
-        , Cmd.map PolarCoordinatesMsg polarCoordinatesCmd
-        ]
-    )
-
-
-view : Model -> Document Msg
+
+view : Model -> Html Msg
=view model =
-    { title = "FP-Art!"
-    , body =
-        [ Element.layout
-            [ Element.width Element.fill
-            , Element.height Element.fill
-            ]
-          <|
-            Element.column
-                [ Element.centerX
-                , Font.center
-                , Element.height Element.fill
-                , Element.width (Element.maximum 800 Element.fill)
-                ]
-                [ slides model
-                    |> Dict.get model.currentSlide
-                    |> Maybe.map
-                        (Element.column
-                            [ Element.width Element.fill
-                            , Element.centerY
-                            ]
-                        )
-                    |> Maybe.withDefault (Element.text "404: Slide not found")
-                ]
-        ]
-    }
+    content
+        |> Mark.parseWith options
+        |> Result.mapError Debug.toString
+        |> Result.map (\fn -> fn model)
+        |> Result.extract Element.text
+        |> Element.layout []
=
=
-update : Msg -> Model -> ( Model, Cmd Msg )
=update msg model =
=    case msg of
-        Next ->
-            ( { model
-                | currentSlide =
-                    min (Dict.size (slides model) - 1) (model.currentSlide + 1)
-              }
-            , Cmd.none
-            )
-
-        Previous ->
-            ( { model
-                | currentSlide =
-                    max 0 (model.currentSlide - 1)
-              }
-            , Cmd.none
-            )
-
-        -- Nested programs
-        CartesianCoordinatesMsg msg_ ->
-            let
-                ( model_, cmd_ ) =
-                    CartesianCoordinates.update msg_ model.cartesianCoordinates
-            in
-            ( { model | cartesianCoordinates = model_ }
-            , Cmd.map CartesianCoordinatesMsg cmd_
-            )
-
-        PolarCoordinatesMsg msg_ ->
-            let
-                ( model_, cmd_ ) =
-                    PolarCoordinates.update msg_ model.polarCoordinates
-            in
-            ( { model | polarCoordinates = model_ }
-            , Cmd.map PolarCoordinatesMsg cmd_
-            )
-
-
-subscriptions model =
-    let
-        handleKeyPress : Decoder Msg
-        handleKeyPress =
-            Decode.field "key" Decode.string
-                |> Decode.andThen
-                    (\key ->
-                        case Debug.log "Key" key of
-                            "ArrowLeft" ->
-                                Decode.succeed Previous
-
-                            "ArrowRight" ->
-                                Decode.succeed Next
-
-                            "a" ->
-                                Decode.succeed Previous
-
-                            "d" ->
-                                Decode.succeed Next
-
-                            _ ->
-                                Decode.fail "Unsupported key"
-                    )
-    in
-    Browser.Events.onKeyPress handleKeyPress
-
-
-slides model =
-    Dict.fromList <|
-        List.indexedMap Tuple.pair <|
-            [ markdown """
-                # Software Garden
-                ## A functional programming workshop
-                ### for non-programmers
-              """
-            ]
-                :: [ markdown """
-                        ## Setup
-                     """
-                   ]
-                :: [ markdown """
-                        > This setup instructions are based on an assumption that you are using a Mac.
-                        >
-                        > If you are using Linux or BSD, then you probably know how to install stuff.
-                        >
-                        > If you are using anything else, then... well, good luck.
-                        >
-                        > The rest of the instructions should work with any platform.
-
-                     """ ]
-                :: [ markdown """
-
-                        We will need:
-
-                        - a text editor (I use [Atom][])
-                        - and the [Elm programming language][]
-
-                        [Atom]: https://atom.io/
-                        [Elm programming language]: https://elm-lang.org/
-
-                     """
-                   ]
-                :: [ markdown """
-                        ### Elm Installation
-                     """
-                   ]
-                :: [ markdown """
-                        To install the Elm programming language, go to the [website][Elm] and follow the installation instructions.
-
-                        [Elm]: http://elm-lang.org/
-                     """
-                   ]
-                :: [ markdown """
-                        Some of the tools we use with Elm require Node.js.
-
-                        Go to the [Node.js website][Node.js] and install the current version (the green button on the right).
-
-                        [Node.js]: https://nodejs.org/en/
-                     """
-                   ]
-                :: [ markdown """
-                        We will need to use the terminal a little bit.
-
-                        # :fa-terminal:
-
-                        Don't be scared. It's easy :)
-
-                        <!-- slide data-background-image="images/mac-launchpad-terminal.png" data-background-size="cover" data-background-position="top center"-->
-
-                        > <p style="color: white">In Launchpad find a program called <code>terminal</code> and start it.</p>
-
-                     """
-                   ]
-                :: [ markdown """
-                        You should see a window like this
-                     """
-                   , Html.img
-                        [ Html.Attributes.alt "Mac Terminal app window"
-                        , Html.Attributes.src "../assets/mac-terminal-window.png"
-                        ]
-                        []
-                        |> Element.html
-                        |> Element.el
-                            [ Element.height Element.fill
-                            , Element.width Element.fill
-                            ]
-                   , markdown """
-                        Here is some more markdown.
-                     """
-                   ]
-                :: [ markdown """
-
-                        Now we are going to install few things.
-
-                        - Homebrew (to install other things)
-                        - Elm programming language
-                        - Atom editor
-
-                     """
-                   ]
-                :: [ markdown """
-
-                        ### Install Homebrew
-
-                        Follow instructions on the [Homebrew website](https://brew.sh/) by typing the following in the terminal (you probably want to copy and paste it):
-
-                        ```sh
-                        /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
-                        ```
-
-                        <small>
-                        Make sure that the command is exactly like the one above, including the `/` character at the beginning, the quotes and parentheses.
-
-                        It will ask you to confirm several actions and ask for your password. It may take few minutes to finish, so get your coffee
-                        </small>
-
-                        :fa-coffee:
-
-                        Once it's done you should see a command prompt like that:
-
-                        ```
-                        ~ your-name$
-                        ```
-
-
-                     """
-                   ]
-                :: [ markdown """
-
-                        ### Install the Elm programming language
-
-                        <small>Once we have Homebrew installed, we can use it to install the language.</small>
-
-                        Type the following in the terminal.
-
-                        ```sh
-                        brew install node elm
-                        ```
-
-                        and check if it works by typing:
-
-                        ```
-                        elm repl
-                        ```
-
-                     """
-                   ]
-                :: [ markdown """
-
-                        Note that the command line changes. You should see something like that
-
-                        ```
-                        ---- Elm 0.19.0 ----------------------------------------------------------------
-                        Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help, imports, etc.
-                        --------------------------------------------------------------------------------
-                        >
-                        ```
-
-                        It's the Elm REPL
-
-                        <small>Read - Evaluate - Print Loop</small>
-
-                        *[REPL]: Read - Evaluate - Print Loop.
-
-                     """
-                   ]
-                :: [ markdown """
-
-                        Inside the REPL type
-
-                        ```
-                        2 + 2
-                        ```
-
-                        And expect to see
-
-                        ```
-                        ---- Elm 0.19.0 ----------------------------------------------------------------
-                        Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help, imports, etc.
-                        --------------------------------------------------------------------------------
-                        > 2 + 2
-                        4 : number
-                        >
-                        ```
-
-                        Easy, huh?
-
-                     """
-                   ]
-                :: [ markdown """
-
-                        We will learn more about REPL later. For now type `:exit` to close it.
+        CounterMsg m ->
+            { model | counter = Counter.update m model.counter }
=
-                        The command line should look like before again.
=
-                     """
-                   ]
-                :: [ markdown """
+type alias Styling =
+    Mark.Styling Msg
=
-                        ### Install Atom text editor
=
-                        Computer programs are represented as text, so the text editor is the most fundamental tool of a programmer. There is a lot of good text editors, but to get you started, we will use [Atom] here.
+type alias Options =
+    Mark.Options Model Styling Msg
=
-                    """
-                   ]
-                :: [ markdown """
=
-                        Type following in the terminal:
-
-                        ```
-                        brew cask install atom
-                        ```
-
-                        And start it with:
-
-                        ```sh
-                        atom
-                        ```
-
-                     """
-                   ]
-                :: [ markdown """
-
-                        One last thing we need is Elm support for Atom editor. You can install it by typing in the terminal:
-
-                        ```
-                        apm install language-elm
-                        ```
-
-                        <small>APM is the Atom Package Manager, but don't pay much attention to it. We won't be using it again.</small>
-
-                     """
-                   ]
-                :: [ markdown """
-
-                        **We are all set!**
-
-                        :smile:
-
-                    """
-                   ]
-                :: [ markdown """
-
-
-                        # First program!
-
-                     """
-                   ]
-                :: [ markdown """
-
-                        As mentioned before, programs are represented as text (called *the source code*).
-
-                        The source code is stored in files
-
-                        :fa-file:
-
-                        and files are organized in directories
-
-                        :fa-folder:
-
-                     """
-                   ]
-                :: [ markdown """
-
-                        So the first step is to create a directory for our new program. Let's call it `fpart`.
-
-                        In the terminal type
-
-                        ```sh
-                        mkdir fpart/
-                        ```
-
-                        and then
-
-                        ```
-                        cd fpart/
-                        ```
-
-                        <small>This creates a new directory and makes it the current one. Again, don't worry about the details.</small>
-
-                     """
-                   ]
-                :: [ markdown """
-
-                        To easily create a new program, we can type
-
-                        ```
-                        elm init
-                        ```
-
-                     """
-                   ]
-                :: [ markdown """
-
-                        Then to create a file with source code, type
-
-                        ```
-                        atom src/Main.elm
-                        ```
-
-                        <small>This command should open a new Atom window with empty text file.</small>
-
-                     """
-                   ]
-                :: [ markdown """
-
-                        ### `main.elm`
-
-                        ```elm
-                        module Main exposing (main)
-
-                        import Html
-
-
-                        main =
-                            Html.text "Hello, Tree!"
-                        ```
-
-                        <small>Type the above in the editor and save the file</small>
-
-                     """
-                   ]
-                :: [ markdown """
-
-                        To see the program running type following in the terminal
-
-                        ```sh
-                        elm reactor
-                        ```
-
-                        <small>This command starts the Elm reactor, which will let you run your program in the web browser. Note that it won't give you the command line back - it will run as long as you don't stop it.</small>
-
-                     """
-                   ]
-                :: [ markdown """
-
-                        # Voila!
-
-                        <small>Open following address in the web browser</small>
-
-                        http://localhost:8000/src/Main.elm
+options : Options
+options =
+    let
+        default =
+            Mark.default
+
+        counterBlock : Mark.Custom.Block Model Styling Msg
+        counterBlock =
+            Mark.Custom.block "counter" counterView
+
+        counterView : Styling -> Model -> Element Msg
+        counterView style model =
+            model.counter
+                |> Counter.ui
+                |> Element.el
+                    [ Element.centerX
+                    ]
+                |> Element.el
+                    [ Element.centerX
+                    , Border.color (Element.rgb 1 0.6 0.6)
+                    , Border.rounded 5
+                    , Border.width 2
+                    ]
+                |> Element.map CounterMsg
+    in
+    { default
+        | blocks =
+            counterBlock :: Mark.defaultBlocks
+    }
=
-                        <small>Note that the same address was printed by Elm reactor</small>
=
-                     """
-                   ]
-                :: [ markdown """
+content =
+    """
+| header
+    To do:
=
-                        # Let's make a dot!
-                        # :fa-circle:
+Steps to reproduce the tree:
=
-                     """
-                   ]
-                :: [ markdown """
+Make a dot
=
-                        We are going to use a technology called SVG
+  Centered (Elm UI, viewBox, cartesian coordinates)
=
-                        <small>Scalable Vector Graphics</small>
+Make a line
=
+  Play with transformations (union types)
=
-                        *[SVG]: Scalable Vector Graphics
+Gradients
=
-                     """
-                   ]
-                :: [ markdown """
+Multiple lines
=
-                        Let's install an Elm package to help us work with SVG.
+  Rosettes (different kinds)
=
-                        Stop the Reactor running in terminal by pressing
+Groups and transformations
=
-                        `CTRL-C`
+  Spiral (recursion)
=
-                        and type the following
+Tree
=
-                        ```
-                        elm install elm/svg
-                        ```
=
-                        Then start the reactor again
+| header
+    Before the course begins
=
-                        ```
-                        elm reactor
-                        ```
+Setup the development environment
=
-                        <small>you can press up arrow :fa-arrow-up: on the keyboard to get to previous commands</small>
+| list
+    - Elm
+    - Node.js
=
-                     """
-                   ]
-                :: [ markdown """
=
-                        <iframe id="cartesian" data-src="./CartesianCoordinates.html" class="stretch">
-                        </iframe>
+| counter
=
-                     """
-                   ]
-                :: [ markdown """
-                        Move sliders to change the `x` and `y` coordinates of the dot. It's like moving a thing on a table by telling how far left, right, up or down should it go.
-                     """
-                   , model.cartesianCoordinates
-                        |> CartesianCoordinates.ui
-                        |> Element.map CartesianCoordinatesMsg
-                        |> Element.el
-                            [ Element.centerX
-                            , Element.width (Element.maximum 600 Element.fill)
-                            ]
-                   ]
-                :: [ markdown """
-                        Move sliders to change the `angle` and `length` properties of the line connecting the dot and origin. The x and y coordinates will be calculated like this:
+Last sentence
=
-                        ```
-                        x = cos(angle) * length
+| counter
=
-                        y = sin(angle) * length
-                        ```
-                     """
-                   , model.polarCoordinates
-                        |> PolarCoordinates.ui
-                        |> Element.map PolarCoordinatesMsg
-                        |> Element.el
-                            [ Element.centerX
-                            , Element.width (Element.maximum 600 Element.fill)
-                            ]
-                   ]
-                :: []
+"""

Evolve the Main program into Browser.element

On by Tadeusz Łazurski

index d1066f3..939218f 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -12,13 +12,18 @@ import Result.Extra as Result
=
=
=main =
-    Browser.sandbox
+    Browser.element
=        { init = init
=        , view = view
=        , update = update
+        , subscriptions = subscriptions
=        }
=
=
+type alias Flags =
+    ()
+
+
=type alias Model =
=    { counter : Counter.Model }
=
@@ -27,9 +32,12 @@ type Msg
=    = CounterMsg Counter.Msg
=
=
-init =
-    { counter = Counter.init
-    }
+init : Flags -> ( Model, Cmd Msg )
+init flags =
+    ( { counter = Counter.init
+      }
+    , Cmd.none
+    )
=
=
=view : Model -> Html Msg
@@ -42,10 +50,18 @@ view model =
=        |> Element.layout []
=
=
+update : Msg -> Model -> ( Model, Cmd Msg )
=update msg model =
=    case msg of
=        CounterMsg m ->
-            { model | counter = Counter.update m model.counter }
+            ( { model | counter = Counter.update m model.counter }
+            , Cmd.none
+            )
+
+
+subscriptions : Model -> Sub Msg
+subscriptions model =
+    Sub.none
=
=
=type alias Styling =

Separate markup content to own file, fetch it with HTTP

On by Tadeusz Łazurski

The newly released elm/http 2.0.0 requires updated versions of elm/core and elm/json.

index d305cfa..c770fd4 100644
--- a/elm.json
+++ b/elm.json
@@ -7,9 +7,10 @@
=    "dependencies": {
=        "direct": {
=            "elm/browser": "1.0.0",
-            "elm/core": "1.0.0",
+            "elm/core": "1.0.2",
=            "elm/html": "1.0.0",
-            "elm/json": "1.0.0",
+            "elm/http": "2.0.0",
+            "elm/json": "1.1.2",
=            "elm/svg": "1.0.1",
=            "elm-community/basics-extra": "4.0.0",
=            "elm-community/list-extra": "8.1.0",
@@ -22,6 +23,8 @@
=            "turboMaCk/any-dict": "1.0.1"
=        },
=        "indirect": {
+            "elm/bytes": "1.0.7",
+            "elm/file": "1.0.1",
=            "elm/parser": "1.1.0",
=            "elm/time": "1.0.0",
=            "elm/url": "1.0.0",
@@ -35,4 +38,4 @@
=        "direct": {},
=        "indirect": {}
=    }
-}
\ No newline at end of file
+}
new file mode 100644
index 0000000..5dc347c
--- /dev/null
+++ b/index.txt
@@ -0,0 +1,39 @@
+| header
+    To do:
+
+Steps to reproduce the tree:
+
+Make a dot
+
+  Centered (Elm UI, viewBox, cartesian coordinates)
+
+Make a line
+
+  Play with transformations (union types)
+
+Gradients
+
+Multiple lines
+
+  Rosettes (different kinds)
+
+Groups and transformations
+
+  Spiral (recursion)
+
+Tree
+
+
+| header
+    Before the course begins
+
+Setup the development environment
+
+| list
+    - Elm
+    - Node.js
+
+
+| counter
+
+Last sentence
index 939218f..b8450ee 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -6,6 +6,7 @@ import Element exposing (Element)
=import Element.Border as Border
=import Element.Input as Input
=import Html exposing (Html)
+import Http
=import Mark
=import Mark.Custom
=import Result.Extra as Result
@@ -25,34 +26,64 @@ type alias Flags =
=
=
=type alias Model =
-    { counter : Counter.Model }
+    { markup : Maybe String
+    , counter : Counter.Model
+    }
=
=
=type Msg
-    = CounterMsg Counter.Msg
+    = DocumentFetched (Result Http.Error String)
+    | CounterMsg Counter.Msg
=
=
=init : Flags -> ( Model, Cmd Msg )
=init flags =
-    ( { counter = Counter.init
+    ( { markup = Nothing
+      , counter = Counter.init
=      }
-    , Cmd.none
+    , Http.get
+        { url = "/index.txt"
+        , expect = Http.expectString DocumentFetched
+        }
=    )
=
=
=view : Model -> Html Msg
=view model =
-    content
-        |> Mark.parseWith options
-        |> Result.mapError Debug.toString
-        |> Result.map (\fn -> fn model)
-        |> Result.extract Element.text
-        |> Element.layout []
+    let
+        content =
+            case model.markup of
+                Nothing ->
+                    Element.el [ Element.centerX, Element.centerY ] <|
+                        Element.text "Loading content..."
+
+                Just markup ->
+                    markup
+                        |> Mark.parseWith options
+                        |> Result.mapError Debug.toString
+                        |> Result.map (\fn -> fn model)
+                        |> Result.extract Element.text
+    in
+    Element.layout
+        [ Element.width Element.fill
+        , Element.height Element.fill
+        ]
+        content
=
=
=update : Msg -> Model -> ( Model, Cmd Msg )
=update msg model =
-    case msg of
+    case Debug.log "update" msg of
+        DocumentFetched (Ok markup) ->
+            ( { model | markup = Just markup }
+            , Cmd.none
+            )
+
+        DocumentFetched (Err markup) ->
+            ( { model | markup = Nothing }
+            , Cmd.none
+            )
+
=        CounterMsg m ->
=            ( { model | counter = Counter.update m model.counter }
=            , Cmd.none
@@ -101,50 +132,3 @@ options =
=        | blocks =
=            counterBlock :: Mark.defaultBlocks
=    }
-
-
-content =
-    """
-| header
-    To do:
-
-Steps to reproduce the tree:
-
-Make a dot
-
-  Centered (Elm UI, viewBox, cartesian coordinates)
-
-Make a line
-
-  Play with transformations (union types)
-
-Gradients
-
-Multiple lines
-
-  Rosettes (different kinds)
-
-Groups and transformations
-
-  Spiral (recursion)
-
-Tree
-
-
-| header
-    Before the course begins
-
-Setup the development environment
-
-| list
-    - Elm
-    - Node.js
-
-
-| counter
-
-Last sentence
-
-| counter
-
-"""