Commits: 17
Prevent rewind from going below minAge and forward from exceeding maxAge in Animated Tree.
Also make tree grow faster.
index 42a14cb..fc858b3 100644
--- a/src/Examples/AnimatedTree.elm
+++ b/src/Examples/AnimatedTree.elm
@@ -147,7 +147,7 @@ update msg model =
= Animate delta ->
= let
= progress =
- min 32 delta / 5000
+ min 32 delta / 1000
=
= age =
= (model.age + progress)
@@ -164,12 +164,24 @@ update msg model =
= )
=
= Rewind ->
- ( { model | age = model.age - 1 }
+ let
+ age =
+ (model.age - 1)
+ |> min model.maxAge
+ |> max model.minAge
+ in
+ ( { model | age = age }
= , Cmd.none
= )
=
= Forward ->
- ( { model | age = model.age + 1 }
+ let
+ age =
+ (model.age + 1)
+ |> min model.maxAge
+ |> max model.minAge
+ in
+ ( { model | age = age }
= , Cmd.none
= )
=Refactoring the code in rewind, forward and animate in animated tree.
index fc858b3..9ac54bf 100644
--- a/src/Examples/AnimatedTree.elm
+++ b/src/Examples/AnimatedTree.elm
@@ -148,13 +148,8 @@ update msg model =
= let
= progress =
= min 32 delta / 1000
-
- age =
- (model.age + progress)
- |> min model.maxAge
- |> max model.minAge
= in
- ( { model | age = age }
+ ( { model | age = grow progress model }
= , Cmd.none
= )
=
@@ -164,28 +159,22 @@ update msg model =
= )
=
= Rewind ->
- let
- age =
- (model.age - 1)
- |> min model.maxAge
- |> max model.minAge
- in
- ( { model | age = age }
+ ( { model | age = grow -1 model }
= , Cmd.none
= )
=
= Forward ->
- let
- age =
- (model.age + 1)
- |> min model.maxAge
- |> max model.minAge
- in
- ( { model | age = age }
+ ( { model | age = grow 1 model }
= , Cmd.none
= )
=
=
+grow progress model =
+ (model.age + progress)
+ |> min model.maxAge
+ |> max model.minAge
+
+
=subscriptions : Model -> Sub Msg
=subscriptions model =
= if model.play thenAdd spacing to the buttons in animated tree.
index 9ac54bf..8fb8ffd 100644
--- a/src/Examples/AnimatedTree.elm
+++ b/src/Examples/AnimatedTree.elm
@@ -58,7 +58,9 @@ ui model =
= { config | axiom = { axiom | age = model.age } }
=
= controls =
- Element.row []
+ Element.row
+ [ Element.spacing 12
+ ]
= [ if model.play then
= pauseButton
=Implement animated tree block in mark up.
index 12dfff2..aeb3dd8 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -22,6 +22,7 @@ import Element.Font as Font
=import Element.Input as Input
=import Element.Lazy
=import Examples
+import Examples.AnimatedTree
=import Examples.CartesianCoordinates
=import Examples.Circle
=import Examples.Counter
@@ -831,6 +832,7 @@ document =
= , rosette
= , spiral
= , tree
+ , animatedTree
= , viewBox
= ]
=
@@ -1118,6 +1120,18 @@ document =
= rules
= )
=
+ animatedTree : Mark.Block (Examples.Model -> Element Msg)
+ animatedTree =
+ let
+ render : Examples.Model -> Element Msg
+ render model =
+ Examples.AnimatedTree.ui model.animatedTree
+ |> Element.map Examples.AnimatedTreeMsg
+ |> Element.map ExamplesMsg
+ in
+ Mark.stub "AnimatedTree"
+ render
+
= -- Mark.Block Examples.Tree.Config
= -- result = Examples.Tree.Config
= -- start = AxiomFix animated tree controls not visible in the window element.
Limit the height of the animated tree to 400px. This is somewhat arbitrary, but works well with the Window element, and currently we are only displaying animated tree inside window.
Also make controls overlay and visible only when paused or mouse hover.
index 8fb8ffd..d82915d 100644
--- a/src/Examples/AnimatedTree.elm
+++ b/src/Examples/AnimatedTree.elm
@@ -107,11 +107,32 @@ ui model =
= , label = Element.text "Forward"
= }
= in
- Element.column
- [ Element.width Element.fill, Element.height Element.fill ]
- [ Examples.Tree.ui tree
- , controls
- ]
+ Examples.Tree.ui tree
+ |> Element.el
+ [ Element.height (Element.maximum 400 Element.fill)
+ , Element.width Element.fill
+ , Element.inFront
+ (Element.el
+ [ Element.alpha
+ (if model.play then
+ 0
+
+ else
+ 0.3
+ )
+ , Element.mouseOver [ Element.alpha 0.5 ]
+ , Element.width Element.fill
+ , Element.height Element.fill
+ ]
+ (Element.el
+ [ Element.centerX
+ , Element.alignBottom
+ , Element.padding 40
+ ]
+ controls
+ )
+ )
+ ]
=
=
=type MsgUse icons for animated tree controls.
index d82915d..15224c9 100644
--- a/src/Examples/AnimatedTree.elm
+++ b/src/Examples/AnimatedTree.elm
@@ -13,6 +13,7 @@ import Browser.Events
=import Element exposing (Element)
=import Element.Input as Input
=import Examples.Tree
+import FeatherIcons
=import Html exposing (Html)
=
=
@@ -61,13 +62,15 @@ ui model =
= Element.row
= [ Element.spacing 12
= ]
- [ if model.play then
+ [ rewindButton
+ , if model.age == model.maxAge then
+ resetButton
+
+ else if model.play then
= pauseButton
=
= else
= playButton
- , resetButton
- , rewindButton
= , forwardButton
= ]
=
@@ -80,31 +83,46 @@ ui model =
= playButton =
= Input.button []
= { onPress = Just Play
- , label = Element.text "Play"
+ , label =
+ FeatherIcons.play
+ |> FeatherIcons.toHtml []
+ |> Element.html
= }
=
= pauseButton =
= Input.button []
= { onPress = Just Pause
- , label = Element.text "Pause"
+ , label =
+ FeatherIcons.pause
+ |> FeatherIcons.toHtml []
+ |> Element.html
= }
=
= resetButton =
= Input.button []
= { onPress = Just Reset
- , label = Element.text "Reset"
+ , label =
+ FeatherIcons.refreshCw
+ |> FeatherIcons.toHtml []
+ |> Element.html
= }
=
= rewindButton =
= Input.button []
= { onPress = Just Rewind
- , label = Element.text "Rewind"
+ , label =
+ FeatherIcons.rewind
+ |> FeatherIcons.toHtml []
+ |> Element.html
= }
=
= forwardButton =
= Input.button []
= { onPress = Just Forward
- , label = Element.text "Forward"
+ , label =
+ FeatherIcons.fastForward
+ |> FeatherIcons.toHtml []
+ |> Element.html
= }
= in
= Examples.Tree.ui treeAdd animated tree block to day 5.
index fe7dc56..1a8e6b2 100644
--- a/content/day-5.txt
+++ b/content/day-5.txt
@@ -23,47 +23,8 @@
=
=We have a nice picture of a tree, but ultimately it may have been easier to just draw it using some graphics program. We promised you a growing tree, so let's make it grow like this:
=
-| Monospace
- TODO: Implement growing tree example
-
+| Window
= | AnimatedTree
- | Axiom
- color = brown
- rotation = -90
- minAge = 0
- maxAge = 10
-
- | Rule
- | Parent
- color = brown
-
- | Child
- color = "green"
- rotation = 0
-
- | Child
- color = "green"
- rotation = 20
-
- | Child
- color = "green"
- rotation = -30
-
- | Rule
- | Parent
- color = green
-
- | Child
- color = "red"
- rotation = -45
-
- | Child
- color = "red"
- rotation = -5
-
- | Child
- color = "red"
- rotation = 50
=
=| Header
= The Elm ArchitectureEdits to day 2
index 26e0ab6..7958918 100644
--- a/content/day-2.txt
+++ b/content/day-2.txt
@@ -29,8 +29,7 @@ Previously we have created a program that displays one dot in the center of the
=| Header
= Multiple Dots
=
-
-First obvious difference is that now we have multiple dots. The only dot we have created using {Code|Svg.circle} function on line 9 - 15, this code block:
+We will start from where we left off yesterday. In the browser go to your saved Ellie bookmark. If you run it you should notice first obvious difference. Currently we only have one dot. It's the one created using {Code|Svg.circle} function on lines 9 - 15, this code fragment:
=
=| Editor
= | Annotations
@@ -372,7 +371,7 @@ This time the radius will affect the size of an imaginary circle on which the do
=
=Ok, so this describes what it means to lay on a circle. But what are some efficient methods of putting things there? How about the following.
=
-You choose a center (anywhere, say {Code|(0, 0)}) and radius (any length you like, say 80).
+You choose a center (anywhere, say {Code|(0, 0)}) and radius (any length you like, say {Code|80}).
=
=Take a ruler and place it's one end (the one showing the value 0) at the center point. Then move the thing you want to place along the ruler the length you chose. Leave it there.
=
@@ -456,14 +455,14 @@ In mathematics there is a special operation to determine it. Perhaps you have he
=
=Maybe you can solve it in your head, but if not, why not use an online Elm REPL? Go to {Link|elmrepl.cuberoot.in|url=http://elmrepl.cuberoot.in/}.
=
-And then
+And then type
=
=| Monospace
= 360 / 5
=
-You should see:
+and press {Key|enter}. You should see:
=
-| Monospace
+| ElmRepl
= ---- Elm 0.19.0 ----------------------------------------------------------------
= Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help, imports, etc.
= --------------------------------------------------------------------------------
@@ -497,7 +496,7 @@ Now that we understand what it means to be placed on a circle and evenly distrib
= radius = 80
= scatter = False
=
-We will start from where we left off yesterday. Open Eliie in your browser and go to your saved bookmark. It should have the following code:
+Let's get back to our code in Ellie. It should look something like this:
=
=| Editor
= | Annotations
@@ -512,33 +511,64 @@ We will start from where we left off yesterday. Open Eliie in your browser and g
=
=
= main =
- Svg.svg
- [ Svg.Attributes.viewBox "-100 -100 200 200"
+ [ Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "skyblue"
= ]
- [ Svg.circle
- [ Svg.Attributes.r "10"
- , Svg.Attributes.cx "0"
- , Svg.Attributes.cy "0"
- ]
- []
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "40"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "orange"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "80"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "red"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "120"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "lime"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "160"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "maroon"
+ ]
+ []
+ ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.viewBox "-300 -300 600 600"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
= ]
- |> Element.html
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
=
-It's a good starting point. We already have a single dot in the middle. Let's say this will be the center of our circle.
=
-Now it's time to introduce our protractor and ruler. These is a property of an SVG element called `transform`. You use it like that:
+| Note
+ At this point there may be some differences in colours and positions of the dots, but if you follow the instructions so far, the code should look familiar.
+
+It's a good starting point. We already have five dots, one of them in the center ({Code|cx} and {Code|cy} set to {Code|0}). Let's say this will also be the center of our circle. Move all the other dots there. Now it's time to introduce our protractor and ruler. These is a property of an SVG element called `transform`. You use it like that:
=
=| Monospace
= Svg.Attributes.transform "translate(80)"
=
=This will move the dot ("translate" is a fancy term for "move") 80 units.
=
-Also, let's change the color of the dot. In the end it should look like this:
-
=
=| Editor
= | Annotations
@@ -549,8 +579,8 @@ Also, let's change the color of the dot. In the end it should look like this:
= length = 11
=
= | Fold
- start = 21
- length = 7
+ start = 27
+ length = 50
=
= | Code
= module Main exposing (main)
@@ -572,6 +602,34 @@ Also, let's change the color of the dot. In the end it should look like this:
= , Svg.Attributes.fill "skyblue"
= ]
= []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "orange"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "red"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "lime"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "maroon"
+ ]
+ []
= ]
= |> Element.html
= |> Element.layout
@@ -582,18 +640,57 @@ Also, let's change the color of the dot. In the end it should look like this:
=With this change, click {Key|COMPILE}. You should see something like this:
=
=| Window
- | Circle
- dots = 1
- circle = 0 1
- protractor = False
- center = none
- radi = 0 1
- radius = 80
- scatter = False
+ | Shapes
+ | Container
+ background = none
+ fill = True
+ viewBox = -100 -100 200 200
+
+ | Dot
+ radius = 10
+ color = skyblue
+ transformations =
+ | Translate
+ x = 80
+ y = 0
+
+ | Dot
+ radius = 10
+ color = orange
+ transformations =
+ | Translate
+ x = 0
+ y = 0
+
+ | Dot
+ radius = 10
+ color = red
+ transformations =
+ | Translate
+ x = 0
+ y = 0
+
+
+ | Dot
+ radius = 10
+ color = lime
+ transformations =
+ | Translate
+ x = 0
+ y = 0
+
+
+ | Dot
+ radius = 10
+ color = maroon
+ transformations =
+ | Translate
+ x = 0
+ y = 0
=
-As you can see the dot is no longer in the center - it has moved!
+Looks like two dots, but we are not going to fall for this again. We know that there are four of them stacked in the center (with the maroon one on top). What's interesting is the blue one. It's moved!
=
-Now we need a second dot. If you remember the discussion about lists from previous day, you know what to do. Just duplicate the first SVG circle and put it after the first one, with a coma in between. Like this:
+Now let's move the second dot.
=
=| Editor
= | Annotations
@@ -604,8 +701,14 @@ Now we need a second dot. If you remember the discussion about lists from previo
= length = 11
=
= | Fold
- start = 29
- length = 8
+ start = 27
+ length = 50
+
+ | Highlight
+ top = 24
+ left = 14
+ width = 40
+ height = 1
=
= | Code
= module Main exposing (main)
@@ -631,8 +734,29 @@ Now we need a second dot. If you remember the discussion about lists from previo
= [ Svg.Attributes.r "10"
= , Svg.Attributes.cx "0"
= , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "orange"
= , Svg.Attributes.transform "translate(80)"
+ , Svg.Attributes.fill "orange"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "red"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "lime"
+ ]
+ []
+ , Svg.circle
+ [ Svg.Attributes.r "10"
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill "maroon"
= ]
= []
= ]
@@ -642,16 +766,15 @@ Now we need a second dot. If you remember the discussion about lists from previo
= , Element.height Element.fill
= ]
=
-Now let's focus on the second one. We gave it a different color, so we can distinguish between them, but now we don't see the first one! That's because it's exactly in the same spot as the second one, which covers it. They are stacked in a way. We need to move the second dot in a different direction, so it's on a circle, but 72 degree apart from the first one.
=
-For that we need to add another transformation before the move. First we will rotate, then translate:
+Of course it will end up on top of the blue one. That's not what we want. We need to change the direction of the movement. Let's rotate it 72 degree before we translate, like this:
=
=| Editor
= | Annotations
= | Highlight
= top = 25
- left = 14
- width = 51
+ left = 40
+ width = 12
= height = 1
=
= | Fold
@@ -660,7 +783,7 @@ For that we need to add another transformation before the move. First we will ro
=
= | Fold
= start = 29
- length = 8
+ length = 50
=
= | Code
= module Main exposing (main)
@@ -690,67 +813,25 @@ For that we need to add another transformation before the move. First we will ro
= , Svg.Attributes.transform "rotate(72) translate(80)"
= ]
= []
- ]
- |> Element.html
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- ]
-
-This will rotate the element by 72 degrees. On its own it wouldn't make any difference, because the dot is round - it doesn't matter how you rotate it, it always looks the same. But It also rotates its internal coordinates system. So the translation will move it in a different direction then the first dot.
-
-Now it's time to add the third, red dot. Duplicate the second one, change fill to `"red"` and put `144` for rotate function, like this:
-
-| Editor
- | Annotations
- | Highlight
- top = 33
- left = 14
- width = 54
- height = 1
-
- | Fold
- start = 1
- length = 27
-
- | Fold
- start = 37
- length = 7
-
- | Code
- module Main exposing (main)
-
- import Element
- import Svg
- import Svg.Attributes
-
-
- main =
- Svg.svg
- [ Svg.Attributes.viewBox "-100 -100 200 200"
- ]
- [ Svg.circle
+ , Svg.circle
= [ Svg.Attributes.r "10"
= , Svg.Attributes.cx "0"
= , Svg.Attributes.cy "0"
- , Svg.Attributes.transform "translate(80)"
- , Svg.Attributes.fill "skyblue"
+ , Svg.Attributes.fill "red"
= ]
= []
= , Svg.circle
= [ Svg.Attributes.r "10"
= , Svg.Attributes.cx "0"
= , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "orange"
- , Svg.Attributes.transform "rotate(72) translate(80)"
+ , Svg.Attributes.fill "lime"
= ]
= []
= , Svg.circle
= [ Svg.Attributes.r "10"
= , Svg.Attributes.cx "0"
= , Svg.Attributes.cy "0"
- , Svg.Attributes.fill "red"
- , Svg.Attributes.transform "rotate(144) translate(80)"
+ , Svg.Attributes.fill "maroon"
= ]
= []
= ]
@@ -760,34 +841,66 @@ Now it's time to add the third, red dot. Duplicate the second one, change fill t
= , Element.height Element.fill
= ]
=
-Why 144? It's because 72 + 72 is 144! So to be 72 degree apart from a dot that's at 72 degree, you need to be at 144 (or 0, but the first dot is already there).
+This will rotate the element by 72 degrees. On its own it wouldn't make any difference, because the dot is round - it doesn't matter how you rotate it, it always looks the same. But It also rotates its internal coordinates system. So the translation will move it in a different direction then the first dot. It's like rotating the ruler before moving the dot along it's edge. Here is the expected result:
=
-This makes the last two dots trivial. Just rotate them to 216 and 288 and give them lime and maroon colors. Note that 288 + 72 = 360, so the distance between the first and last dot is correct.
+| Window
+ | Shapes
+ | Container
+ background = none
+ fill = True
+ viewBox = -100 -100 200 200
=
+ | Dot
+ radius = 10
+ color = skyblue
+ transformations =
+ | Translate
+ x = 80
+ y = 0
=
-The complete program should look like this:
+ | Dot
+ radius = 10
+ color = orange
+ transformations =
+ | Rotate
+ 72
+ | Translate
+ x = 80
+ y = 0
=
-| Editor
- | Annotations
- | Highlight
- top = 41
- left = 12
- width = 54
- height = 1
+ | Dot
+ radius = 10
+ color = red
+ transformations =
+ | Translate
+ x = 0
+ y = 0
=
- | Highlight
- top = 49
- left = 12
- width = 54
- height = 1
=
- | Fold
- start = 1
- length = 35
+ | Dot
+ radius = 10
+ color = lime
+ transformations =
+ | Translate
+ x = 0
+ y = 0
=
- | Fold
- start = 53
- length = 7
+
+ | Dot
+ radius = 10
+ color = maroon
+ transformations =
+ | Translate
+ x = 0
+ y = 0
+
+Now it's time to move the fird and the rest of the dots. Red one gets rotated {Code|144} degree, next one {Code|216} and finally {Code|288}. Why {Code|144}? It's because {Code|72 + 72 is 144}! So to be {Code|72} degree apart from a dot that's at {Code|72} degree, you need to be at {Code|144} (or {Code|0}, but the blue dot is already there). Note that {Code|288 + 72 = 360}, so the distance between the first and last dot is correct. The beauty of math 🤓
+
+The complete program should look like this:
+
+| Editor
+ | Annotations
+ | None
=
= | Code
= module Main exposing (main)Edits to day 3
index 7389e27..661c9b8 100644
--- a/content/day-3.txt
+++ b/content/day-3.txt
@@ -4,11 +4,6 @@
=| Emphasize
= Connecting the Dots
=
-| Note
- *We are still working on this content.*
-
- Stay tuned {Icon|name=radio}
-
=| Note
= So far we have been writing the code of our program without thinking too much about what it means. Today we are going to learn about
=
@@ -48,7 +43,7 @@ Let's start with values. The simplest way to create a value is to literally type
= "Hello!" : String
= >
=
-These expressions ({Code|2} and {Code|"Hello!"}) are called /literals/, because their values are literally what they look like. The value of {Code|2} is simply {Code|2}! When you enter them, the REPL evaluates and prints their value (which in this case is very easy) and type (the text after {Code|:}). We will get to types later.
+These expressions ({Code|2} and {Code|"Hello!"}) are called /literals/, because their values are literally what they look like. The value of {Code|2} is simply {Code|2}! When you enter them, the REPL evaluates and prints their *value* (which in this case is very easy) and *type* (the text after {Code|:}). We will get to types later.
=
=Some value literals are simple, like numbers and strings. Some are complex. Previously we have touched the subject of lists. You can also type them literally, like this:
=
@@ -133,7 +128,7 @@ Every value in Elm has a type. You can see the type in the REPL - after evaluati
= > kittens
= 2 : number
=
-Above {Code|2} is the value and `number` is the type. Similar here:
+Above {Code|2} is the value and {Code|number} is the type. Similar here:
=
=| ElmRepl
= > fun "Learning Elm"
@@ -183,7 +178,7 @@ What's the type of an empty list? Let's see:
= > []
= [] : List a
=
-A list of {Code|a}. Obviously {Code|a} is not a type. This means that the type of the elements cannot be determined yet. This is signaled by the lowercase term where the type should be: {Code|a} instead of the capitalized {Code|String}.
+A list of {Code|a}. Obviously {Code|a} is not a type. This means that the type of the elements cannot be determined yet. This is signaled by the lowercase term where the type should be: lowercase {Code|a} in contrast with the capitalized {Code|String}.
=
=| Note
= Notice that the {Code|number} (type of value {Code|15} for example) is also lowercase. That's because it's not a concrete type! Elm has two concrete types for numbers: {Code|Int} that can represent an integer number (1, 2, 3, 0, -122 etc.) and {Code|Float} that can represent a number with a fraction, (like 1.2, -3.5, 44.2). Because fraction part can be 0 (e.g. 1.0, 2.0, 0.0, -122.0 - so called whole numbers), it's not possible to tell if {Code|2} is an {Code|Int} or a {Code|Float}. We will see some of the implications of this later.
@@ -239,6 +234,9 @@ But what are functions good for? They are very good for making similar values th
=
=We don't have to create all the functions that we need. There are thousands of them already defined and we can use them by importing modules.
=
+| Header
+ Exercise
+
=Let's look at our code and try to recognize some literal values, lists, names and functions. Here is how it should look after day 2:
=
=| Editor
@@ -653,7 +651,7 @@ This way we declared that when calling a {Code|dot} name you will provide two va
=
=Let's reload again and see that there is still only one, skyblue dot visible. That's because we did not tell Elm what to do with the values of the two parameters we provide for the {Code|dot} function. We gave them names ({Code|color} and {Code|rotation}), but never called them.
=
-First, let's use the {Code|color} parameter. If you want to change the color of the dot, you need to pass a different value to the {Code|Svg.Attributes.fill} function on line {Code|12}. Currently the value is a literal string {Code|"skyblue"}. Instead we want to give it whatever value was given for the {Code|color} parameter. We do it by simply calling the name of the parameter in place of a literal value, like this:
+First, let's use the {Code|color} parameter. If you want to change the color of the dot, you need to pass a different value to the {Code|Svg.Attributes.fill} function on line {Code|30}. Currently the value is a literal string {Code|"skyblue"}. Instead we want to give it whatever value was given for the {Code|color} parameter. We do it by simply calling the name of the parameter in place of a literal value, like this:
=
=| Editor
= | Annotations
@@ -753,7 +751,12 @@ We can use the {Code|++} operator to glue the strings together. First part is co
= ]
= []
=
-But Elm will complain about this. First it looks like we gave 5 arguments to the {Code|Svg.Attributes.transform} function, and it only takes one. We can fix it by putting a parentheses around the {Code|"rotate(" ++ rotation ++ ") translate(80)" }, basically telling Elm that this is a single expression and it should evaluate its value before passing it to the {Code|Svg.Attributes.transform} function. Now it should look like this:
+But Elm will complain about this. First it looks like we gave 5 arguments to the {Code|Svg.Attributes.transform} function, and it only takes one. We can fix it by putting a parentheses around this part:
+
+| Monospace
+ "rotate(" ++ rotation ++ ") translate(80)" }
+
+basically telling Elm that this is a single expression and it should evaluate it before passing its value to the {Code|Svg.Attributes.transform} function. Now it should look like this:
=
=| Editor
= | Annotations
@@ -886,16 +889,50 @@ That's what we need! Let's pass the value of {Code|rotation} through this functi
= , Svg.Attributes.cx "0"
= , Svg.Attributes.cy "0"
= , Svg.Attributes.fill color
- , Svg.Attributes.transform ("rotate(" ++ (String.fromFloat rotation) ++ ") translate(80)")
+ , Svg.Attributes.transform ("rotate("
+ ++ (String.fromFloat rotation)
+ ++ ") translate(80)"
+ )
= ]
= []
=
+This should work! Ellie should now present the dots as intended and I hope you agree that the code is more readable now.
+
+| Header
+ Lines to Connect the Dots
+
+Armed with our functional superpowers, let's make another function that draws a line. Let me first show you a complete code and then we can discuss it.
+
=| Editor
= | Annotations
- | None
+ | Fold
+ start = 1
+ length = 36
=
= | Code
- IDEA: What if we use String.concat instead?
+ module Main exposing (main)
+
+ import Element
+ import Svg
+ import Svg.Attributes
+
+
+ main =
+ Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200"
+ ]
+ [ dot "skyblue" 0
+ , dot "orange" 72
+ , dot "red" 144
+ , dot "lime" 216
+ , dot "maroon" 288
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
=
= dot color rotation =
= Svg.circle
@@ -903,27 +940,14 @@ That's what we need! Let's pass the value of {Code|rotation} through this functi
= , Svg.Attributes.cx "0"
= , Svg.Attributes.cy "0"
= , Svg.Attributes.fill color
- , Svg.Attributes.transform (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate(80)"
- ]
+ , Svg.Attributes.transform ("rotate("
+ ++ (String.fromFloat rotation)
+ ++ ") translate(80)"
= )
= ]
= []
=
-This should work! Ellie should now present the dots as intended and I hope you agree that the code is more readable now.
-
-| Header
- Lines to Connect the Dots
-
-Armed with our functional superpowers, let's make another function that draws a line. Let me first show you a complete code and then we can discuss it.
-
-| Editor
- | Annotations
- | None
=
- | Code
= line color rotation =
= Svg.line
= [ Svg.Attributes.strokeWidth "1"
@@ -987,9 +1011,9 @@ Once you have the function defined, let's use it. For every dot, create one line
= , dot "red" 144
= , line "red" 144
= , dot "lime" 216
- , line " lime" 216
+ , line "lime" 216
= , dot "maroon" 288
- , line " maroon" 288
+ , line "maroon" 288
= ]
= |> Svg.svg
= [ Svg.Attributes.height "100%"
@@ -1134,8 +1158,6 @@ You should see something like this in on Ellie:
=| Emphasize
= Congratulations!
=
-| Emphasize
= {Icon|name=award}
=
-| Emphasize
- You are ready for {Link|Day 4|url=/day-4.html}!
+ This was the toughest day of the workshop. Now you are ready for {Link|Day 4|url=/day-4.html}!Edits to day 4
index 497fe17..db3ac4a 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -4,11 +4,6 @@
=| Emphasize
= Let's Make a Tree
=
-| Note
- *We are still working on this content.*
-
- Stay tuned {Icon|name=radio}
-
=| Note
= Last day we have learned about basic building blocks of an Elm program: *value*, *name* and *type*. Today we are going to use this blocks to compose more complex structures. On the way we are going to learn about:
=
@@ -147,20 +142,7 @@ The tree is built from segments: a line and a dot. In this respect it is similar
= ]
= []
=
-{Key|COMPILE} on Ellie there should be no difference at this point. Looking back at code I notice that each segment looks the same: it is a group with a dot and a line, where the {Code|group} and {Code|line} functions have the same arguments passed to them. This kind of repetition begs for a function. Let's call it {Code|segment} and define like this:
-
-| Editor
- | Annotations
- | None
-
- | Code
- segment color rotation =
- Svg.g []
- [ dot color rotation
- , line color rotation
- ]
-
-Type this code at the bottom of the file and then replace main with the following:
+Press {Key|COMPILE} on Ellie. There should be no difference at this point. Looking back at code I notice that each segment looks the same: it is a group with a dot and a line, where the {Code|group} and {Code|line} functions have the same arguments passed to them. This kind of repetition begs for a function. Let's call it {Code|segment} and define like this:
=
=| Editor
= | Annotations
@@ -261,7 +243,7 @@ Click {Key|COMPILE}. There should still be no visible difference in the behavior
=
=The big difference between our program and the one we are trying to build is that ours have only one level of segments, whereas in the example segments grow from the tip of other segments. You can think of it as segments having child segments: the green segment has two child red segments and one green segment. Each red segment has two green child segments.
=
-It's similar to how the way an SVG group has child elements. But group itself (together with its children) is an element, so we can put a group within a group. Let's do that! Change the definition of {Code|segment} function as follows:
+It's similar to the way an SVG group has child elements. But group itself (together with its children) is an element, so we can put a group within a group. Let's do that! Change the definition of {Code|segment} function as follows:
=
=| Editor
= | Annotations
@@ -340,24 +322,24 @@ It's similar to how the way an SVG group has child elements. But group itself (t
=
=
= segment color rotation =
- Svg.g []
- [ dot color rotation
- , line color rotation
- , Svg.g
- [ Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate(80)"
- ]
- )
- ]
- [ dot "red" 15
- , line "red" 15
- , dot "blue" -15
- , line "blue" -15
+ Svg.g []
+ [ dot color rotation
+ , line color rotation
+ , Svg.g
+ [ Svg.Attributes.transform
+ (String.concat
+ [ "rotate("
+ , String.fromFloat rotation
+ , ") translate(80)"
+ ]
+ )
+ ]
+ [ dot "red" 15
+ , line "red" 15
+ , dot "blue" -15
+ , line "blue" -15
+ ]
= ]
- ]
=
=Now the program should display something like this:
=
@@ -707,7 +689,7 @@ Now the program should display something like this:
= x = 80
= y = 0
=
-It already starts to look interesting, but there are two problems with it. First it doesn't fit on screen. This is easy to fix using {Code|viewBox}. We need to zoom out to see more of a picture. Change the value passed to {Code|viewBox} on line 19 to {Code|"-500 -500 1000 1000"}, effectively zooming out 5x but preserving keeping the origin in the center of our viewbox.
+It already starts to look interesting, but there are two problems with it. First it doesn't fit on screen. This is easy to fix using {Code|viewBox}. We need to zoom out to see more of a picture. Change the value passed to {Code|viewBox} on line 19 to {Code|"-500 -500 1000 1000"}, effectively zooming out 5x but keeping the origin in the center.
=
=Then the lines look ugly displayed on top of the dots of different color. This is also easy to fix. In SVG several siblings (children of the same parent element) lay one on top of another. The "younger" sibling is laying on top of the older, so in our case the group containing the blue and red dots and lines lays on top of the dot and line. Let's change the order of siblings by moving the group to the beginning of the list, like this:
=
@@ -719,12 +701,6 @@ Then the lines look ugly displayed on top of the dots of different color. This i
= width = 46
= height = 1
=
- | Highlight
- top = 74
- left = 10
- width = 21
- height = 8
-
= | Fold
= start = 1
= length = 7
@@ -1182,20 +1158,20 @@ So now each segment has two sub segments: red and blue. The red one is rotated 1
=
= | Fold
= start = 21
- length = 13
+ length = 12
=
= | Fold
= start = 50
= length = 39
=
= | Highlight
- top = 35
- left = 1
- width = 31
+ top = 34
+ left = 23
+ width = 10
= height = 1
=
= | Highlight
- top = 46
+ top = 45
= left = 13
= width = 8
= height = 1
@@ -1234,7 +1210,6 @@ So now each segment has two sub segments: red and blue. The red one is rotated 1
= ]
=
=
- segment : String -> Float -> List (Svg msg) -> Svg.Svg msg
= segment color rotation children =
= Svg.g []
= [ Svg.g
@@ -2201,7 +2176,7 @@ The tree following these rules will look like this:
= y = 0
=
=
-To represent this in code we will need to learn few new concepts: *dictionary*, *record*, *type alias* and *mapping over list*.
+To represent this in code we will need to learn few new concepts: *dictionary*, *record*, *type alias* and *mapping over a list*.
=
=Let's start with a record. Currently our {Code|segment} function takes three arguments first two are {Code|color : String} and {Code|rotation : Float}. The last one is a list of child segments. Instead we can merge the first two into one argument of type {Code|\{ color : String, rotation : Float \\}}. It will look like this:
=
@@ -2836,7 +2811,7 @@ There are three new things here:
=
=The first one is pretty simple. Previously we have been inserting entries into the dictionary. Each entry has a key (color of the segment) and value (list of child segments). Now we are getting these values back.
=
-But what if the entry for a given key is not there? For example we have not inserted the entry for the {Code|"red"} key? Well, then we get {Code|Nothing}. But our {Code|segment} function cannot work with nothing - it has to get a record with color and rotation. So when we have nothing, we can't call the {Code|segment} function. To understand this, consider the following.
+But what if the entry for a given key is not there? For example we have not inserted the entry for the {Code|"red"} key? Well, then we get {Code|Nothing}. But our {Code|segment} function cannot work with nothing - it has to get a list of records with colors and rotations. So when we have nothing, we can't call the {Code|segment} function. To understand this, consider the following.
=
=If there is an entry, we will get a list of records. We want a list of segments. Each of the records can be passed to the {Code|segment} function to produce one segment. That's where {Code|List.map} comes in. Given a list and a function it will call the function with each of the elements in the list and give back the list of produced values. Let's see a simpler example in the REPL:
=
@@ -3204,26 +3179,20 @@ Since the size depends on the age, and the age is given for every segment, we ca
= | Annotations
= | Highlight
= top = 62
- left = 1
- width = 22
+ left = 4
+ width = 5
= height = 1
=
= | Highlight
= top = 64
- left = 10
- width = 39
- height = 1
-
- | Highlight
- top = 53
- left = 30
- width = 17
+ left = 29
+ width = 20
= height = 1
=
= | Highlight
- top = 72
+ top = 57
= left = 18
- width = 17
+ width = 5
= height = 1
=
= | Fold
@@ -3334,32 +3303,142 @@ Since the size depends on the age, and the age is given for every segment, we ca
=
=Next let's pass the age to the {Code|line} function and use it for both the thickness ({Code|strokWidth}) and length ({Code|x2}).
=
-If you click {Key|COMPILE} now, the tree will look as if it exploded. It's funny. The reason is that we did not adjust the translation of the child groups nor the dots. It's always 80, even though the length of the lines is variable - see lines {Code|53} and {Code|72}. Let's change it like that:
=
=| Editor
= | Annotations
+ | Fold
+ start = 1
+ length = 36
+
+
+ | Fold
+ start = 61
+ length = 16
+
+
= | Highlight
- top = 88
- left = 10
- width = 47
+ top = 79
+ left = 4
+ width = 5
= height = 1
=
= | Highlight
- top = 91
- left = 10
- width = 49
+ top = 81
+ left = 29
+ width = 20
= height = 1
=
= | Highlight
- top = 75
+ top = 58
= left = 18
- width = 27
+ width = 5
+ height = 1
+
+ | Code
+ module Main exposing (main)
+
+ import Dict
+ import Element
+ import Svg exposing (Svg)
+ import Svg.Attributes
+
+
+ main =
+ [ segment 4 { color = "brown", rotation = -90 } ]
+ |> Svg.svg
+ [ Svg.Attributes.height "100%"
+ , Svg.Attributes.width "100%"
+ , Svg.Attributes.style "background: none"
+ , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ ]
+ |> Element.html
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ ]
+
+
+ rules =
+ Dict.empty
+ |> Dict.insert "brown"
+ [ { color = "brown", rotation = 0 }
+ , { color = "green", rotation = 20 }
+ , { color = "green", rotation = -30 }
+ ]
+ |> Dict.insert "green"
+ [ { color = "red", rotation = -45 }
+ , { color = "red", rotation = -5 }
+ , { color = "red", rotation = 50 }
+ ]
+
+
+ segment age { color, rotation } =
+ if age <= 0 then
+ Svg.g [] []
+
+ else
+ Svg.g []
+ [ rules
+ |> Dict.get color
+ |> Maybe.withDefault []
+ |> List.map (segment (age - 1))
+ |> Svg.g
+ [ Svg.Attributes.transform
+ ("rotate("
+ ++ String.fromFloat rotation
+ ++ ") translate(80)"
+ )
+ ]
+ , dot age color rotation
+ , line age color rotation
+ ]
+
+
+ dot age color rotation =
+ Svg.circle
+ [ Svg.Attributes.r (String.fromFloat age)
+ , Svg.Attributes.cx "0"
+ , Svg.Attributes.cy "0"
+ , Svg.Attributes.fill color
+ , Svg.Attributes.transform
+ ("rotate("
+ ++ String.fromFloat rotation
+ + ") translate(80)"
+ )
+ ]
+ []
+
+
+ line age color rotation =
+ Svg.line
+ [ Svg.Attributes.strokeWidth (String.fromFloat age)
+ , Svg.Attributes.x1 "0"
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.x1 (String.fromFloat (age * 10))
+ , Svg.Attributes.y1 "0"
+ , Svg.Attributes.stroke color
+ , Svg.Attributes.transform
+ ( "rotate("
+ ++ String.fromFloat rotation
+ ++ ")"
+ )
+ ]
+ []
+
+If you click {Key|COMPILE} now, the tree will look as if it exploded. It's funny. The reason is that we did not adjust the translation of the child groups nor the dots. It's always 80, even though the length of the lines is variable - see lines {Code|53} and {Code|72}. Let's change it like that:
+
+| Editor
+ | Annotations
+ | Highlight
+ top = 72
+ left = 21
+ width = 25
= height = 1
=
= | Highlight
- top = 54
- left = 30
- width = 27
+ top = 53
+ left = 33
+ width = 26
= height = 1
=
= | Fold
@@ -3370,6 +3449,10 @@ If you click {Key|COMPILE} now, the tree will look as if it exploded. It's funny
= start = 1
= length = 37
=
+ | Fold
+ start = 78
+ length = 100
+
= | Code
= module Main exposing (main)
=
@@ -3420,13 +3503,11 @@ If you click {Key|COMPILE} now, the tree will look as if it exploded. It's funny
= |> List.map (segment (age - 1))
= |> Svg.g
= [ Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate("
- , String.fromFloat (age * 10)
- , ")"
- ]
+ ("rotate("
+ ++ String.fromFloat rotation
+ ++ ") translate("
+ ++ String.fromFloat (age * 10)
+ ++ ")"
= )
= ]
= , dot age color rotation
@@ -3441,13 +3522,11 @@ If you click {Key|COMPILE} now, the tree will look as if it exploded. It's funny
= , Svg.Attributes.cy "0"
= , Svg.Attributes.fill color
= , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ") translate("
- , String.fromFloat (age * 10)
- , ")"
- ]
+ ("rotate("
+ ++ String.fromFloat rotation
+ ++ ") translate("
+ ++ String.fromFloat (age * 10)
+ ++ ")"
= )
= ]
= []
@@ -3455,23 +3534,21 @@ If you click {Key|COMPILE} now, the tree will look as if it exploded. It's funny
=
= line age color rotation =
= Svg.line
- [ Svg.Attributes.strokeWidth "1"
+ [ Svg.Attributes.strokeWidth (String.fromFloat age)
= , Svg.Attributes.x1 "0"
= , Svg.Attributes.y1 "0"
= , Svg.Attributes.x1 (String.fromFloat (age * 10))
= , Svg.Attributes.y1 "0"
= , Svg.Attributes.stroke color
- , Svg.Attributes.strokeWidth (String.fromFloat age)
= , Svg.Attributes.transform
- (String.concat
- [ "rotate("
- , String.fromFloat rotation
- , ")"
- ]
+ ("rotate("
+ ++ String.fromFloat rotation
+ ++ ")"
= )
= ]
= []
=
+
=Now the tree should look correctly, like this:
=
=| Window
@@ -3517,8 +3594,6 @@ Now the tree should look correctly, like this:
=| Emphasize
= Congratulations!
=
-| Emphasize
= {Icon|name=award}
=
-| Emphasize
= You are ready for {Link|the final day|url=/day-5.html}!Merge branch 'master' into animated-tree
Merge remote-tracking branch 'origin/master' into animated-tree
Make rewind and fast-forward more granullar in AnimatedTree example
index 15224c9..fe2d90a 100644
--- a/src/Examples/AnimatedTree.elm
+++ b/src/Examples/AnimatedTree.elm
@@ -200,12 +200,12 @@ update msg model =
= )
=
= Rewind ->
- ( { model | age = grow -1 model }
+ ( { model | age = grow -0.2 model }
= , Cmd.none
= )
=
= Forward ->
- ( { model | age = grow 1 model }
+ ( { model | age = grow 0.2 model }
= , Cmd.none
= )
=Merge branch 'animated-tree' into 'master'
Animated tree
See merge request software-garden/software-garden.gitlab.io!26
Merge remote-tracking branch 'origin/master' into rewrite-content-ellie
Edits to day 5
index a19cee5..10a10f6 100644
--- a/content/day-5.txt
+++ b/content/day-5.txt
@@ -183,7 +183,7 @@ First let's change the value of {Code|main} and create the {Code|view} function,
= ]
= []
=
-Notice that the {Code|view} takes an argument called {Code|age} and passes it to the first (brown) segment. The value of {Code|age} is our state. In some programs the type of state can be very complex, but in our program it's simply a {Code|Float} number. It will indicate how much time in milliseconds have passed from the start of the program. Because the time is counted in milliseconds, to get correct results we need to divide it by 5000 (so the tree will grows one generation of segments per five second).
+Notice that the {Code|view} takes an argument called {Code|age} and passes it to the first (brown) segment. The value of {Code|age} is our state. In some programs the type of state can be very complex, but in our program it's simply a {Code|Float} number. It will indicate how much time in milliseconds have passed from the start of the program. Because the time is counted in milliseconds, to get correct results we need to divide it by 5000 (so the tree will grow one generation of segments per five second).
=
=Now let's provide the init function. It takes an argument sometimes called flags, but we will ignore it. Let's just say that it will always receive {Code|()} - an empty value called unit. It needs to return two values bound together in a structure called /tuple/.
=
@@ -229,17 +229,7 @@ To use it we first need to import the {Code|Browser.Events} module. The {Code|Br
=| Note
= Perhaps you have noticed that we use a lot of functions. Functions are passed to functions that sometimes return functions 🤕 That's why Elm is called a *functional programming language*.
=
-The whole {Code|subscriptions} looks like that:
-
-| Editor
- | Annotations
- | None
-
- | Code
- subscriptions age =
- Browser.Events.onAnimationFrameDelta identity
-
-And the complete code like this:
+The complete code like this:
=
=| Editor
= | Annotations
@@ -255,21 +245,12 @@ And the complete code like this:
= width = 31
= height = 7
=
- | Fold
- start = 1
- length = 18
-
= | Highlight
= top = 48
= left = 1
= width = 49
= height = 2
=
- | Fold
- start = 51
- length = 81
-
-
= | Code
= module Main exposing (main)
=
@@ -404,10 +385,8 @@ And the complete code like this:
=| Emphasize
= That's it!
=
-| Emphasize
= {Icon|name=flag}
=
-| Emphasize
= Your tree is growing over time!
=
=We hope you had good time learning programming with us. If we still have some time left, we can play with the code.Merge branch 'rewrite-content-ellie' into 'master'
Use Ellie for writing and running the code
See merge request software-garden/software-garden.gitlab.io!27