Week 09 of 2019
Development log of Elm Tree Workshop
31 items
- Create a fancy coupon widget and use it for Utrecht test run on
- Merge branch 'big-utrecht-button' into 'master'
- Make AnimatedTree markup block customizable
- Give id attribute to each subtitle, so it can be linked directly
- Move AnimatedTree lower in the home page
- Merge branch 'custom-animated-tree' into 'master'
- Make AnimatedTree grow older and zoom in on it
- Make few corrections to day 4
- Fix typo on day 4.
- Merge branch 'day-4-corrections' into 'master'
- Merge branch 'custom-animated-tree' into 'master'
- Implement a Spring module for animated values
- Prevent scaling the viewport on mobile devices
- Merge branch 'fix-mobile-scaling' into 'master'
- Set mobile scale to 0.75
- Merge branch 'fix-mobile-scaling' into 'master'
- Make the text in Coupon element wrap
- Merge branch 'fix-mobile-scaling'
- Do not build examples in develop screipt
- Use springs to improve performance of CartesianCoordinates system
- Merge branch 'master' into springs
- In CartesianCoordinates do not subscribe to animation frames if springs are at rest
- Use tad-lispy/springs
- Remove Examples/Spring
- Add a text on test-run.
- Merge branch 'reg-wait-list' into 'master'
- Merge branch 'springs' into 'master'
- Create flash cards for day 1
- Merge branch 'flash-cards' into 'master'
- Schedule a timeline for day 1
- Merge branch 'timeline' into 'master'
Create a fancy coupon widget and use it for Utrecht test run on
On by
We want the link to be more prominent.
index a010fa5..67672db 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -15,8 +15,11 @@ Hello! We are running a workshop that will give you a glimpse into the way softw
=
=During this *5 days workshop* (4 hours each day) you will learn to solve problems using a functional programming language. We think {Link|it's important|url=/motivation.html}. Upon completion of the course your name, photo and link to your website (or LinkedIn profile etc) will be posted here. The price for participation is *500 €* (inc. 21% VAT).
=
-| Note
- {Icon|name=gift} {Link|Special offer for University of Utrecht students|url=/test-run.html}.
+| Coupon
+ icon = gift
+ primary = True
+ text = Special offer for University of Utrecht students
+ href = /test-run.html
=
=Below is the material through which we will be going during the workshop. Only the first section is a required read before you come.
=index 41c32c3..2eac9c4 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -827,6 +827,7 @@ document =
= [ Mark.Custom.editor
= , Mark.Custom.elmRepl
= , Mark.Custom.note
+ , Mark.Custom.coupon
= ]
=
= special =index 88c3af4..1e547c3 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -1,5 +1,6 @@
=module Mark.Custom exposing
= ( colors
+ , coupon
= , editor
= , elmRepl
= , emphasize
@@ -103,6 +104,100 @@ link =
= (Mark.field "label" paragraph)
=
=
+coupon =
+ let
+ render :
+ { icon : String
+ , text : String
+ , primary : Bool
+ , href : String
+ }
+ -> model
+ -> Element msg
+ render config model =
+ { url = config.href
+ , label =
+ [ FeatherIcons.icons
+ |> Dict.get config.icon
+ |> Maybe.map (FeatherIcons.toHtml [])
+ |> Maybe.map Element.html
+ |> Maybe.map (Element.el [ Element.paddingXY 10 0 ])
+ |> Maybe.withDefault Element.none
+ , config.text
+ |> Element.text
+ ]
+ |> Element.row
+ [ Element.centerX
+ ]
+ }
+ |> Element.link
+ [ Element.paddingXY 0 20
+ , Font.size 24
+ , Font.bold
+ , Background.color colors.white
+ , Element.width Element.fill
+
+ -- , Background.color colors.charcoal
+ , Font.color
+ (if config.primary then
+ colors.green
+
+ else
+ colors.charcoal
+ )
+ , Border.rounded 9
+ , Border.width 1
+ , Border.color
+ (if config.primary then
+ colors.green
+
+ else
+ colors.charcoal
+ )
+ , Border.width 1
+ , Border.dashed
+ , Element.mouseOver
+ [ Border.shadow
+ { offset = ( 0, 2 )
+ , size = 0
+ , blur = 3
+ , color = colors.charcoal
+ }
+ , Element.moveDown 5
+ , Element.moveLeft 3
+ , Element.rotate (degrees 1)
+ ]
+ ]
+ |> Element.el
+ [ Background.color colors.gray
+ , Border.innerShadow
+ { offset = ( 0, 2 )
+ , size = 1
+ , blur = 3
+ , color = colors.charcoal
+ }
+ , Border.rounded 10
+ , Border.width 1
+ , Border.color
+ (if config.primary then
+ colors.green
+
+ else
+ colors.charcoal
+ )
+ , Border.width 1
+ , Border.dashed
+ ]
+ in
+ Mark.record4 "Coupon"
+ (\icon_ text_ primary href -> { icon = icon_, text = text_, primary = primary, href = href })
+ (Mark.field "icon" Mark.string)
+ (Mark.field "text" Mark.string)
+ (Mark.field "primary" Mark.bool)
+ (Mark.field "href" Mark.string)
+ |> Mark.map render
+
+
=monospace : Mark.Block (model -> Element msg)
=monospace =
= Mark.Default.monospaceMerge branch 'big-utrecht-button' into 'master'
On by
Create a fancy coupon widget and use it for Utrecht test run
See merge request software-garden/software-garden.gitlab.io!29
Make AnimatedTree markup block customizable
On by
It takes the same configuration as Tree block. The minimum and maximum age are hardcoded (0 - 6).
Also add a Subtitle block.
index 10a10f6..33c7a49 100644
--- a/content/day-5.txt
+++ b/content/day-5.txt
@@ -25,6 +25,38 @@ We have a nice picture of a tree, but ultimately it may have been easier to just
=
=| Window
= | AnimatedTree
+ | Axiom
+ color = olive
+ rotation = -90
+ age = 8
+ growing = True
+
+ | Rule
+ parent = olive
+
+ children =
+ | Child
+ color = olive
+ rotation = 5.0
+
+ | Child
+ color = maroon
+ rotation = 40
+
+ | Child
+ color = maroon
+ rotation = -40
+
+ | Rule
+ parent = maroon
+ children =
+ | Child
+ color = olive
+ rotation = 20
+ | Child
+ color = olive
+ rotation = -20
+
=
=| Header
= The Elm Architectureindex 67672db..7bae3a4 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -1,11 +1,42 @@
=| Title
= Software Garden
=
-| Emphasize
- ⚘
-
+| Subtitle
= A software development workshop for non-programmers
=
+| AnimatedTree
+ | Axiom
+ color = olive
+ rotation = -90
+ age = 8
+ growing = True
+
+ | Rule
+ parent = olive
+
+ children =
+ | Child
+ color = olive
+ rotation = 5.0
+
+ | Child
+ color = maroon
+ rotation = 40
+
+ | Child
+ color = maroon
+ rotation = -40
+
+ | Rule
+ parent = maroon
+ children =
+ | Child
+ color = olive
+ rotation = 20
+ | Child
+ color = olive
+ rotation = -20
+
=Hello! We are running a workshop that will give you a glimpse into the way software is created. Our workshop is intended for people with no prior experience in programming and doesn't require any technical knowledge. Everybody is welcome!
=
=| Emphasizeindex fe2d90a..d13c1f8 100644
--- a/src/Examples/AnimatedTree.elm
+++ b/src/Examples/AnimatedTree.elm
@@ -27,12 +27,17 @@ main =
= }
=
=
+minAge =
+ 0
+
+
+maxAge =
+ 6
+
+
=type alias Model =
= { age : Float
- , maxAge : Float
- , minAge : Float
= , play : Bool
- , config : Examples.Tree.Config
= }
=
=
@@ -43,27 +48,27 @@ type alias Flags =
=init : Flags -> ( Model, Cmd msg )
=init flags =
= ( { age = 0.0
- , maxAge = 6.0
- , minAge = 0
= , play = False
- , config = Examples.Tree.defaults
= }
= , Cmd.none
= )
=
=
-ui : Model -> Element Msg
-ui model =
+ui : Examples.Tree.Config -> Model -> Element Msg
+ui config model =
= let
= tree =
= { config | axiom = { axiom | age = model.age } }
=
+ axiom =
+ config.axiom
+
= controls =
= Element.row
= [ Element.spacing 12
= ]
= [ rewindButton
- , if model.age == model.maxAge then
+ , if model.age == maxAge then
= resetButton
=
= else if model.play then
@@ -74,12 +79,6 @@ ui model =
= , forwardButton
= ]
=
- config =
- model.config
-
- axiom =
- config.axiom
-
= playButton =
= Input.button []
= { onPress = Just Play
@@ -165,7 +164,7 @@ type Msg
=view : Model -> Html Msg
=view model =
= model
- |> ui
+ |> ui Examples.Tree.defaults
= |> Element.layout
= [ Element.width Element.fill
= , Element.height Element.fill
@@ -190,30 +189,42 @@ update msg model =
= progress =
= min 32 delta / 1000
= in
- ( { model | age = grow progress model }
+ ( { model
+ | age = grow progress model
+ , play = model.age < maxAge
+ }
= , Cmd.none
= )
=
= Reset ->
- ( { model | age = model.minAge }
+ ( { model
+ | age = minAge
+ , play = True
+ }
= , Cmd.none
= )
=
= Rewind ->
- ( { model | age = grow -0.2 model }
+ ( { model
+ | age = grow -0.2 model
+ , play = False
+ }
= , Cmd.none
= )
=
= Forward ->
- ( { model | age = grow 0.2 model }
+ ( { model
+ | age = grow 0.2 model
+ , play = False
+ }
= , Cmd.none
= )
=
=
=grow progress model =
= (model.age + progress)
- |> min model.maxAge
- |> max model.minAge
+ |> min maxAge
+ |> max minAge
=
=
=subscriptions : Model -> Sub Msgindex 2eac9c4..70dd2ff 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -819,6 +819,7 @@ document =
= , Mark.Custom.link
= , Mark.Custom.monospace
= , Mark.Custom.emphasize
+ , Mark.Custom.subtitle
= , Mark.Custom.list
= , Mark.Custom.image
= ]
@@ -1140,19 +1141,49 @@ document =
= animatedTree : Mark.Block (Examples.Model -> Element Msg)
= animatedTree =
= let
- render : Examples.Model -> Element Msg
- render model =
- Examples.AnimatedTree.ui model.animatedTree
+ render : Examples.Tree.Config -> Examples.Model -> Element Msg
+ render config model =
+ model.animatedTree
+ |> Examples.AnimatedTree.ui config
= |> Element.map Examples.AnimatedTreeMsg
= |> Element.map ExamplesMsg
+
+ axiom : Mark.Block Examples.Tree.Axiom
+ axiom =
+ Mark.record4 "Axiom"
+ Examples.Tree.Axiom
+ (Mark.field "color" Mark.string)
+ (Mark.field "rotation" Mark.float)
+ (Mark.field "age" Mark.float)
+ (Mark.field "growing" Mark.bool)
+
+ rules : Mark.Block (List Examples.Tree.Rule)
+ rules =
+ Mark.manyOf [ rule ]
+
+ rule =
+ Mark.record2 "Rule"
+ Tuple.pair
+ (Mark.field "parent" Mark.string)
+ (Mark.field "children" (Mark.manyOf [ child ]))
+
+ parent =
+ Mark.block "Parent" identity Mark.string
+
+ -- Mark.stub "Parent" "green"
+ child =
+ Mark.record2 "Child"
+ Examples.Tree.Segment
+ (Mark.field "color" Mark.string)
+ (Mark.field "rotation" Mark.float)
= in
- Mark.stub "AnimatedTree"
+ Mark.block "AnimatedTree"
= render
+ (Mark.startWith Examples.Tree.Config
+ axiom
+ rules
+ )
=
- -- Mark.Block Examples.Tree.Config
- -- result = Examples.Tree.Config
- -- start = Axiom
- -- rest = List Rule
= viewBox : Mark.Block (Examples.Model -> Element Msg)
= viewBox =
= letindex 1e547c3..e17b54e 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -13,6 +13,7 @@ module Mark.Custom exposing
= , note
= , paragraph
= , row
+ , subtitle
= , text
= , title
= , window
@@ -484,6 +485,26 @@ emphasize =
= (Mark.manyOf [ text ])
=
=
+subtitle =
+ let
+ render :
+ (model -> List (Element msg))
+ -> model
+ -> Element msg
+ render content model =
+ content model
+ |> Element.paragraph
+ [ Font.center
+ , Element.paddingXY 0 10
+ , Element.width Element.fill
+ , Element.spacing 10
+ ]
+ in
+ Mark.block "Subtitle"
+ render
+ text
+
+
=image : Mark.Block (model -> Element msg)
=image =
= Mark.Default.imageGive id attribute to each subtitle, so it can be linked directly
On by
index 7bae3a4..eb4b093 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -80,7 +80,7 @@ Below is the material through which we will be going during the workshop. Only t
= label = Day 5 - Let's Make the Tree Grow
=
=
-| Header
+| Subtitle
= About us
=
=*Ted*: I'm going to be your teacher during the workshop. I've been working as a software developer for the best part of the last six years. Before that I've been a lawyer.index e17b54e..075de33 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -488,21 +488,29 @@ emphasize =
=subtitle =
= let
= render :
- (model -> List (Element msg))
+ String
= -> model
= -> Element msg
= render content model =
- content model
+ content
+ |> Element.text
+ |> List.singleton
= |> Element.paragraph
= [ Font.center
= , Element.paddingXY 0 10
= , Element.width Element.fill
= , Element.spacing 10
+ , content
+ |> String.toLower
+ |> String.words
+ |> String.join "-"
+ |> Html.Attributes.id
+ |> Element.htmlAttribute
= ]
= in
= Mark.block "Subtitle"
= render
- text
+ Mark.string
=
=
=image : Mark.Block (model -> Element msg)Move AnimatedTree lower in the home page
On by
The reason is that on 13' screens (e.g. popular MacBooks) the AnimatedTree element was the last thing visible on the page without scrolling. This made it look like there is nothing else there. The concern was that some readers will think it's just a title and animation and they will miss the "call to action" elements.
index eb4b093..42287fc 100644
--- a/content/index.txt
+++ b/content/index.txt
@@ -4,39 +4,6 @@
=| Subtitle
= A software development workshop for non-programmers
=
-| AnimatedTree
- | Axiom
- color = olive
- rotation = -90
- age = 8
- growing = True
-
- | Rule
- parent = olive
-
- children =
- | Child
- color = olive
- rotation = 5.0
-
- | Child
- color = maroon
- rotation = 40
-
- | Child
- color = maroon
- rotation = -40
-
- | Rule
- parent = maroon
- children =
- | Child
- color = olive
- rotation = 20
- | Child
- color = olive
- rotation = -20
-
=Hello! We are running a workshop that will give you a glimpse into the way software is created. Our workshop is intended for people with no prior experience in programming and doesn't require any technical knowledge. Everybody is welcome!
=
=| Emphasize
@@ -44,7 +11,43 @@ Hello! We are running a workshop that will give you a glimpse into the way softw
=
= or drop us a note at {Link|fana@software.garden|url= mailto:fana@software.garden}
=
-During this *5 days workshop* (4 hours each day) you will learn to solve problems using a functional programming language. We think {Link|it's important|url=/motivation.html}. Upon completion of the course your name, photo and link to your website (or LinkedIn profile etc) will be posted here. The price for participation is *500 €* (inc. 21% VAT).
+During this *5 days workshop* (4 hours each day) you will learn to solve problems using a functional programming language. We think {Link|it's important|url=/motivation.html}. Together, we will build a program that simulates growth of a tree, like this:
+
+| Window
+ | AnimatedTree
+ | Axiom
+ color = olive
+ rotation = -90
+ age = 8
+ growing = True
+
+ | Rule
+ parent = olive
+
+ children =
+ | Child
+ color = olive
+ rotation = 5.0
+
+ | Child
+ color = maroon
+ rotation = 40
+
+ | Child
+ color = maroon
+ rotation = -40
+
+ | Rule
+ parent = maroon
+ children =
+ | Child
+ color = olive
+ rotation = 20
+ | Child
+ color = olive
+ rotation = -20
+
+Upon completion of the course your name, program that you will have created, photo and link to your website (or LinkedIn profile etc) will be posted here. The price for participation is *500 €* (inc. 21% VAT).
=
=| Coupon
= icon = giftMerge branch 'custom-animated-tree' into 'master'
On by
Make Animated Tree customizable, show it on Home page
See merge request software-garden/software-garden.gitlab.io!30
Make AnimatedTree grow older and zoom in on it
On by
index d13c1f8..c9288ce 100644
--- a/src/Examples/AnimatedTree.elm
+++ b/src/Examples/AnimatedTree.elm
@@ -32,7 +32,7 @@ minAge =
=
=
=maxAge =
- 6
+ 7
=
=
=type alias Model =index 1b26fc6..3de5d72 100644
--- a/src/Examples/Tree.elm
+++ b/src/Examples/Tree.elm
@@ -17,7 +17,8 @@ import Transformations
=
=defaults : Config
=defaults =
- { axiom =
+ { viewBox = "-400 -400 800 800"
+ , axiom =
= { color = "purple"
= , rotation = -90
= , age = 7
@@ -47,7 +48,8 @@ defaults =
=
=
=type alias Config =
- { axiom : Axiom
+ { viewBox : String
+ , axiom : Axiom
= , rules : List Rule
= }
=
@@ -191,7 +193,7 @@ ui config =
= [ Svg.Attributes.height "100%"
= , Svg.Attributes.width "100%"
= , Svg.Attributes.style "background: none"
- , Svg.Attributes.viewBox "-500 -500 1000 1000"
+ , Svg.Attributes.viewBox config.viewBox
= ]
= |> Element.html
=index 70dd2ff..386af8c 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -1133,7 +1133,7 @@ document =
= in
= Mark.block "Tree"
= render
- (Mark.startWith Examples.Tree.Config
+ (Mark.startWith (Examples.Tree.Config "-400 -400 800 800")
= axiom
= rules
= )
@@ -1179,7 +1179,7 @@ document =
= in
= Mark.block "AnimatedTree"
= render
- (Mark.startWith Examples.Tree.Config
+ (Mark.startWith (Examples.Tree.Config "-200 -300 400 400")
= axiom
= rules
= )Make few corrections to day 4
On by
Wrong annotations, a typo and comment about the final tree lushness.
index db3ac4a..8190a9f 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -3301,7 +3301,7 @@ 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}).
+Next let's pass the age to the {Code|line} function and use it for both the thickness ({Code|strokeWidth}) and length ({Code|x2}).
=
=
=| Editor
@@ -3312,25 +3312,31 @@ Next let's pass the age to the {Code|line} function and use it for both the thic
=
=
= | Fold
- start = 61
- length = 16
+ start = 59
+ length = 15
=
=
= | Highlight
- top = 79
- left = 4
+ top = 75
+ left = 5
= width = 5
= height = 1
=
= | Highlight
- top = 81
- left = 29
+ top = 77
+ left = 39
= width = 20
= height = 1
=
= | Highlight
- top = 58
- left = 18
+ top = 80
+ left = 30
+ width = 28
+ height = 1
+
+ | Highlight
+ top = 56
+ left = 19
= width = 5
= height = 1
=
@@ -3591,6 +3597,8 @@ Now the tree should look correctly, like this:
= color = red
= rotation = 50
=
+It doesn't look as lush as the one above. That's because the rules and age are different. I will leave it to you to play with the rules. Make your tree beautiful - tomorrow we will make it grow!
+
=| Emphasize
= Congratulations!
=Fix typo on day 4.
On by
index 8190a9f..6b632c1 100644
--- a/content/day-4.txt
+++ b/content/day-4.txt
@@ -1263,7 +1263,7 @@ So now each segment has two sub segments: red and blue. The red one is rotated 1
= []
=
=
-We have added third parameter to the {Code|segment} function on line 35 (before the changes it was 64). The parameter is named {Code|children} and we use it in place of the list on line 46 (previously 75). The list itself was moved to line 11, where it is passed as a value for the parameter of the first segment. This move made all the lines shift down. All other segments will also need to get value for {Code|children} parameter, so let's just give each of them an empty list - meaning they have 0 children.
+We have added third parameter to the {Code|segment} function on line 34 (before the changes it was 64). The parameter is named {Code|children} and we use it in place of the list on line 45 (previously 75). The list itself was moved to line 11, where it is passed as a value for the parameter of the first segment. This move made all the lines shift down. All other segments will also need to get value for {Code|children} parameter, so let's just give each of them an empty list - meaning they have 0 children.
=
=Once all changes are applied, click {Key|COMPILE} and see that only first, skyblue segment has child segments.
=
@@ -1611,7 +1611,7 @@ A segment within a segment! Consider that every segment can have child segments.
=
= | Fold
= start = 30
- length = 60
+ length = 61
=
= | Code
= module Main exposing (main)Merge branch 'day-4-corrections' into 'master'
On by
Make few corrections to day 4
See merge request software-garden/software-garden.gitlab.io!31
Merge branch 'custom-animated-tree' into 'master'
On by
Make AnimatedTree grow older and zoom in on it
See merge request software-garden/software-garden.gitlab.io!32
Implement a Spring module for animated values
On by
Use it to animate model.scroll - a value driving progress bar. The performance is better, esp. on mobile devices.
With this improvement the biggest performance bottleneck is layout calculation of the progress bar. Perhaps it should be done with a combination of gradient and css transformations, or with SVG.
The cute squid on a spring example is designed by Fana.
Co-Authored-By: Fana jewiet@outlook.com
index 64c0692..a5f4ae6 100644
--- a/elm.json
+++ b/elm.json
@@ -13,6 +13,7 @@
= "elm/json": "1.1.2",
= "elm/parser": "1.1.0",
= "elm/svg": "1.0.1",
+ "elm/time": "1.0.0",
= "elm/url": "1.0.0",
= "elm-community/basics-extra": "4.0.0",
= "elm-community/list-extra": "8.1.0",
@@ -29,7 +30,6 @@
= "indirect": {
= "elm/bytes": "1.0.7",
= "elm/file": "1.0.1",
- "elm/time": "1.0.0",
= "elm/virtual-dom": "1.0.2",
= "ianmackenzie/elm-float-extra": "1.0.1",
= "ianmackenzie/elm-interval": "1.0.1",
@@ -40,4 +40,4 @@
= "direct": {},
= "indirect": {}
= }
-}
+}
\ No newline at end of filenew file mode 100644
index 0000000..690d5fb
--- /dev/null
+++ b/src/Examples/Spring.elm
@@ -0,0 +1,139 @@
+module Examples.Spring exposing
+ ( Model
+ , Msg
+ , init
+ , main
+ , subscriptions
+ , ui
+ , update
+ )
+
+import Browser
+import Browser.Events
+import Element exposing (Element)
+import Element.Background as Background
+import Element.Events as Events
+import Element.Font as Font
+import Element.Input as Input
+import Examples.Tree
+import FeatherIcons
+import Html exposing (Html)
+import Html.Events
+import Json.Decode as Decode exposing (Decoder)
+import Spring exposing (Spring)
+
+
+main : Program Flags Model Msg
+main =
+ Browser.element
+ { init = init
+ , view = view
+ , update = update
+ , subscriptions = subscriptions
+ }
+
+
+type alias Model =
+ { x : Spring
+ , y : Spring
+ }
+
+
+type alias Flags =
+ ()
+
+
+init : Flags -> ( Model, Cmd msg )
+init flags =
+ let
+ strength =
+ 20
+
+ dampness =
+ 20
+ in
+ ( { x =
+ Spring.create strength dampness
+ |> Spring.setTarget 200
+ , y =
+ Spring.create strength dampness
+ |> Spring.setTarget 200
+ }
+ , Cmd.none
+ )
+
+
+ui : Model -> Element Msg
+ui model =
+ Element.el
+ [ Element.width (Element.px 20)
+ , Element.height (Element.px 20)
+ , Font.size 30
+
+ -- , Background.color (Element.rgb 1 0 0)
+ , model.x
+ |> Spring.value
+ |> Element.moveRight
+ , model.y
+ |> Spring.value
+ |> Element.moveDown
+ ]
+ (Element.text "\u{1F991}")
+
+
+type Msg
+ = Animate Float
+ | Mouse Float Float
+
+
+view : Model -> Html Msg
+view model =
+ model
+ |> ui
+ |> Element.layout
+ [ Element.width Element.fill
+ , Element.height Element.fill
+ , mouseDecoder
+ |> Html.Events.on "click"
+ |> Element.htmlAttribute
+ ]
+
+
+update : Msg -> Model -> ( Model, Cmd Msg )
+update msg model =
+ case msg of
+ Animate delta ->
+ ( { model
+ | x = Spring.animate delta model.x
+ , y = Spring.animate delta model.y
+ }
+ , Cmd.none
+ )
+
+ Mouse x y ->
+ ( { model
+ | x = Spring.setTarget x model.x
+ , y = Spring.setTarget y model.y
+ }
+ , Cmd.none
+ )
+
+
+mouseDecoder : Decoder Msg
+mouseDecoder =
+ Decode.map2 Mouse
+ (Decode.field "clientX" Decode.float)
+ (Decode.field "clientY" Decode.float)
+ |> Decode.map (Debug.log "Msg")
+
+
+subscriptions : Model -> Sub Msg
+subscriptions model =
+ [ if Spring.atRest model.x && Spring.atRest model.y then
+ Sub.none
+
+ else
+ Browser.Events.onAnimationFrameDelta Animate
+ , Browser.Events.onMouseMove mouseDecoder
+ ]
+ |> Sub.batchindex 386af8c..eb30eb9 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -49,8 +49,10 @@ import Parser
=import Parser.Advanced
=import Result.Extra as Result
=import Routes exposing (Route)
+import Spring exposing (Spring)
=import Svg.Attributes
=import Task
+import Time
=import Transformations exposing (Transformation)
=import Url exposing (Url)
=
@@ -77,7 +79,7 @@ type alias Model =
= , key : Navigation.Key
= , content : Content
= , examples : Examples.Model
- , scroll : Float
+ , scroll : Spring
= , viewport : { width : Int, height : Int }
= }
=
@@ -91,6 +93,7 @@ type Msg
= | ExamplesMsg Examples.Msg
= | ContainerMeasured (Result Dom.Error Dom.Viewport)
= | ViewportMeasured { width : Int, height : Int }
+ | Tick Time.Posix
=
=
=type Content
@@ -135,7 +138,7 @@ init flags url key =
= , key = key
= , content = content
= , examples = examplesModel
- , scroll = 0
+ , scroll = Spring.create 20 20
= , viewport =
= { width = 800, height = 600 }
= }
@@ -393,7 +396,10 @@ update msg model =
= ( { model
= | location = url
= , content = Loading
- , scroll = 0
+ , scroll =
+ model.scroll
+ |> Spring.setTarget 0
+ |> Spring.jumpTo 0
= }
= , Cmd.batch
= [ url
@@ -432,9 +438,16 @@ update msg model =
= ContainerMeasured (Ok { viewport, scene }) ->
= let
= scroll =
- viewport.y / (scene.height - viewport.height)
+ if (scene.height - viewport.height) == 0 then
+ 0
+
+ else
+ viewport.y / (scene.height - viewport.height)
= in
- ( { model | scroll = scroll }
+ ( { model
+ | scroll =
+ Spring.setTarget scroll model.scroll
+ }
= , Cmd.none
= )
=
@@ -449,6 +462,15 @@ update msg model =
= )
=
= Animate delta ->
+ ( { model
+ | scroll =
+ model.scroll
+ |> Spring.animate delta
+ }
+ , Cmd.none
+ )
+
+ Tick time ->
= ( model
= , Dom.getViewport
= |> Task.attempt ContainerMeasured
@@ -465,7 +487,12 @@ subscriptions model =
= (\width height ->
= ViewportMeasured { width = width, height = height }
= )
- , Browser.Events.onAnimationFrameDelta Animate
+ , if Spring.atRest model.scroll then
+ Sub.none
+
+ else
+ Browser.Events.onAnimationFrameDelta Animate
+ , Time.every 250 Tick
= ]
=
=
@@ -717,7 +744,7 @@ contentNavigationBar { location, scroll, viewport } =
= , size = 1
= , blur = 3
= , color =
- if scroll == 0 then
+ if Spring.target scroll == 0 then
= Element.rgba 0.8 0.8 0.8 0
=
= else
@@ -726,12 +753,11 @@ contentNavigationBar { location, scroll, viewport } =
= , Element.htmlAttribute (Html.Attributes.id "navigation-bar")
= ]
= [ linksRow
- , progressBar
- (List.length links)
- -- index of current route
- past
- -- scroll position (0 - 1)
- scroll
+ , scroll
+ |> Spring.value
+ |> progressBar
+ (List.length links)
+ past
= ]
=
=new file mode 100644
index 0000000..0b7bdc3
--- /dev/null
+++ b/src/Spring.elm
@@ -0,0 +1,248 @@
+module Spring exposing
+ ( Spring
+ , animate
+ , atRest
+ , create
+ , jumpTo
+ , setTarget
+ , target
+ , value
+ )
+
+{-| A rough model of a mass attached to a spring, as described by [Hooke's law](https://en.wikipedia.org/wiki/Hooke's_law). Good for making smooth and organic looking animations or modeling oscilating values (e.g. emotions). High physical accuracy is not a priority - performance and API is more important.
+-}
+
+
+{-| A model of mass attached to a spring. The spring is anchored to a target. The mass is constant (1).
+
+Value represents the current position of the mass. It is re-calculated (together with velocity) by animate function.
+
+Strength regulates how strongly the spring pulls toward target. It is also called the stiffness but I find the former term more intuitive.
+
+Dampness is how resistant the spring is to change in it's stretch (both stretching out and contracting in). If dumpness is low relative to strength, then the animation will end in long period of vibration around the target value - in other words lowering dumpness will increase wobbliness. Setting dumpness to 0 will result in something like a sine wave oscilator (but it's not advise to depend on it's accuracy).
+
+Target is the value toward which the mass is pulled. Typically the spring will start in an equilibrium position (i.e. value == target) and later on (due to an event) the target will be changed and the value will follow according to the strength and dampness of the spring.
+
+Value is where the mass is. It can be extracted from the spring using `value` function and set (with `setValue` function - rarely useful).
+
+Velocity is an internal property that cannot be directly modified or read.
+
+Let's say we are creating a program that animates the position of an element toward last click position.
+
+ type alias Model =
+ { x : Spring
+ , y : Spring
+ }
+
+-}
+type Spring
+ = Spring
+ { strength : Float
+ , dampness : Float
+ , target : Float
+ , value : Float
+ , velocity : Float
+ }
+
+
+{-| Create a spring in an equilibrium state.
+
+Let's say we are creating a program that animates the position of an element toward last click position.
+
+ type alias Model =
+ { x : Spring
+ , y : Spring
+ }
+
+ init : Flags -> ( Model, Cmd msg )
+ init flags =
+ ( { x = Spring.create 20 20
+ , y = Spring.create 20 20
+ }
+ , Cmd.none
+ )
+
+-}
+create : Float -> Float -> Spring
+create strength dampness =
+ Spring
+ { strength = strength
+ , dampness = dampness
+ , target = 0
+ , value = 0
+ , velocity = 0
+ }
+
+
+{-| Set a new target (relaxed value) for the spring.
+
+The current value and it's velocity will remain the same. Typically you would set a target in response to an event, e.g.:
+
+ update : Msg -> Model -> ( Model, Cmd Msg )
+ update msg model =
+ case msg of
+ Click x y ->
+ ( { model
+ | x = Spring.setTarget x model.x
+ , y = Spring.setTarget y model.y
+ }
+ , Cmd.none
+ )
+
+-}
+setTarget : Float -> Spring -> Spring
+setTarget target_ (Spring spring) =
+ Spring
+ { spring
+ | target =
+ target_
+ }
+
+
+{-| Update the spring
+
+Typically you would do it in response to an animation frame message, like this:
+
+ subscriptions : Model -> Sub Msg
+ subscriptions model =
+ Browser.Events.onAnimationFrameDelta Animate
+
+ update : Msg -> Model -> ( Model, Cmd Msg )
+ update msg model =
+ case msg of
+ Animate delta ->
+ ( { model
+ | x = Spring.animate delta model.x
+ , y = Spring.animate delta model.y
+ }
+ , Cmd.none
+ )
+
+-}
+animate : Float -> Spring -> Spring
+animate delta ((Spring spring) as this) =
+ if atRest this then
+ {- If it's in equilibrium, then let's just skip the whole calculation. Be lazy. -}
+ Spring spring
+
+ else
+ let
+ time =
+ {- Low sampling rates often result in nasty errors.
+
+ With this cap on delta, if the frame rate is lower than 30fps the animation will slow down and good sampling rate will be preserved.
+ -}
+ min delta 32 / 1000
+
+ stretch =
+ spring.target - spring.value
+
+ force =
+ stretch * spring.strength
+
+ dampening =
+ spring.velocity * spring.dampness
+
+ acceleration =
+ time * (force - dampening)
+ in
+ if
+ (abs (spring.value - spring.target) < 0.001)
+ && (abs spring.velocity < 0.001)
+ then
+ {- In reality the spring never stops vibrating, but at some point the vibration is lost in the background noise (uncertainity principle). In our case it's also a wasted computation. Let's just say that it is at rest already. -} {- Snap to ideal equilibrium -}
+ Spring
+ { spring
+ | value = spring.target
+ , velocity = 0
+ }
+
+ else
+ Spring
+ { spring
+ | value =
+ spring.value + (spring.velocity * time)
+ , velocity =
+ spring.velocity + acceleration
+ }
+
+
+{-| Measure the value of the spring.
+
+Typically you want to access it in the view function, like this:
+
+ Element.el
+ [ Element.width (Element.px 20)
+ , Element.height (Element.px 20)
+ , Font.size 30
+ , model.x
+ |> Spring.value
+ |> Element.moveRight
+ , model.y
+ |> Spring.value
+ |> Element.moveDown
+ ]
+ (Element.text "\u{1F991}")
+
+Above we use Elm UI Elements and Attributes, but it's not difficult to implement same behavior using CSS transformations. Spring value is just a `Float`.
+
+-}
+value : Spring -> Float
+value (Spring spring) =
+ spring.value
+
+
+{-| Get current target of a spring
+
+Can be useful to see where the spring is going. Maybe you want to display something there?
+
+-}
+target : Spring -> Float
+target (Spring spring) =
+ spring.target
+
+
+{-| Check if the spring is at rest
+
+It indicates that no animation is running. Maybe you want to unsubscribe from animation frames? Or remove an element?
+
+ subscriptions : Model -> Sub Msg
+ subscriptions model =
+ if Spring.atRest model.x && Spring.atRest model.y then
+ Sub.none
+
+ else
+ Browser.Events.onAnimationFrameDelta Animate
+
+-}
+atRest : Spring -> Bool
+atRest (Spring spring) =
+ spring.value == spring.target && spring.velocity == 0.0
+
+
+{-| Forcefully set the value and interrupt the animation.
+
+It is useful when you set the spring for the first time (e.g. in init function) or you want to reset the animation.
+
+ init : Flags -> ( Model, Cmd msg )
+ init flags =
+ ( { x =
+ Spring.create 20 20
+ |> Spring.setTarget 200
+ |> Spring.jumpTo 200
+ , y =
+ Spring.create 20 20
+ |> Spring.setTarget 200
+ |> Spring.jumpTo 200
+ }
+ , Cmd.none
+ )
+
+-}
+jumpTo : Float -> Spring -> Spring
+jumpTo value_ (Spring spring) =
+ Spring
+ { spring
+ | value = value_
+ , velocity = 0.0
+ }Prevent scaling the viewport on mobile devices
On by
Use real device width without 75% scaling.
index c0abf0b..5502e69 100644
--- a/container.html
+++ b/container.html
@@ -2,6 +2,7 @@
=<html lang="en" dir="ltr">
= <head>
= <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
= <title>Software Garden</title>
= <style>
= @import url('https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Source+Code+Pro:300,400,700');Merge branch 'fix-mobile-scaling' into 'master'
On by
Prevent scaling the viewport on mobile devices
See merge request software-garden/software-garden.gitlab.io!33
Set mobile scale to 0.75
On by
It was set to 1 by mistake.
index 5502e69..6e062a5 100644
--- a/container.html
+++ b/container.html
@@ -2,7 +2,7 @@
=<html lang="en" dir="ltr">
= <head>
= <meta charset="utf-8">
- <meta name="viewport" content="width=device-width, initial-scale=1">
+ <meta name="viewport" content="width=device-width, initial-scale=0.75">
= <title>Software Garden</title>
= <style>
= @import url('https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Source+Code+Pro:300,400,700');Merge branch 'fix-mobile-scaling' into 'master'
On by
Set mobile scale to 0.75
See merge request software-garden/software-garden.gitlab.io!34
Make the text in Coupon element wrap
On by
Otherwise it doesn't fit on small screens.
index 075de33..2c9e0ce 100644
--- a/src/Mark/Custom.elm
+++ b/src/Mark/Custom.elm
@@ -126,6 +126,8 @@ coupon =
= |> Maybe.withDefault Element.none
= , config.text
= |> Element.text
+ |> List.singleton
+ |> Element.paragraph [ Font.center ]
= ]
= |> Element.row
= [ Element.centerXMerge branch 'fix-mobile-scaling'
On by
Do not build examples in develop screipt
On by
There is no need for that. We can just run Elm Reactor side by side with it.
Also remove the debug flag from develop script. It is never realy useful and with animation frames subscription it significantly affects performance.
index d3e4036..b671dc2 100755
--- a/scripts/develop
+++ b/scripts/develop
@@ -2,17 +2,11 @@
=
=set -euo pipefail
=
-# Build examples
-for example in src/Examples/*.elm
-do
- echo "Compiling ${example} to public/${example}.html"
- npx elm make --output "public/${example}.html" "${example}"
-done
-
=npx elm-live src/Main.elm \
= --pushstate \
= --start-page container.html \
= --port 8001 \
= -- \
= --output built/index.js \
- --debug
+ # --optimize
+ # --debugUse springs to improve performance of CartesianCoordinates system
On by
index 2492c7d..b8781f3 100644
--- a/src/Examples.elm
+++ b/src/Examples.elm
@@ -39,18 +39,24 @@ type Msg
=init : ( Model, Cmd Msg )
=init =
= let
- ( animatedTreeModel, animatedTreeMsg ) =
+ ( animatedTreeModel, animatedTreeCmd ) =
= Examples.AnimatedTree.init ()
+
+ ( cartesianCoordinatesModel, cartesianCoordinatesCmd ) =
+ Examples.CartesianCoordinates.init ()
= in
= ( { counter = Examples.Counter.init
= , transformations = Examples.Transformations.init
= , nestedTransformations = Examples.NestedTransformations.init
- , cartesianCoordinates = Examples.CartesianCoordinates.init
+ , cartesianCoordinates = cartesianCoordinatesModel
= , polarCoordinates = Examples.PolarCoordinates.init
= , viewBox = Examples.ViewBox.init
= , animatedTree = animatedTreeModel
= }
- , animatedTreeMsg
+ , [ animatedTreeCmd |> Cmd.map AnimatedTreeMsg
+ , cartesianCoordinatesCmd |> Cmd.map CartesianCoordinatesMsg
+ ]
+ |> Cmd.batch
= )
=
=
@@ -79,11 +85,16 @@ update msg model =
= )
=
= CartesianCoordinatesMsg m ->
+ let
+ ( cartesianCoordinatesModel, cartesianCoordinatesCmd ) =
+ Examples.CartesianCoordinates.update m model.cartesianCoordinates
+ in
= ( { model
= | cartesianCoordinates =
- Examples.CartesianCoordinates.update m model.cartesianCoordinates
+ cartesianCoordinatesModel
= }
- , Cmd.none
+ , cartesianCoordinatesCmd
+ |> Cmd.map CartesianCoordinatesMsg
= )
=
= PolarCoordinatesMsg m ->
@@ -109,6 +120,11 @@ update msg model =
=
=subscriptions : Model -> Sub Msg
=subscriptions model =
- model.animatedTree
+ [ model.animatedTree
= |> Examples.AnimatedTree.subscriptions
= |> Sub.map AnimatedTreeMsg
+ , model.cartesianCoordinates
+ |> Examples.CartesianCoordinates.subscriptions
+ |> Sub.map CartesianCoordinatesMsg
+ ]
+ |> Sub.batchindex b8b1723..f45515a 100644
--- a/src/Examples/CartesianCoordinates.elm
+++ b/src/Examples/CartesianCoordinates.elm
@@ -4,18 +4,21 @@ module Examples.CartesianCoordinates exposing
= , Msg
= , init
= , main
+ , subscriptions
= , ui
= , update
= , view
= )
=
=import Browser
+import Browser.Events
=import CartesianPlane exposing (graph)
=import Element exposing (Element)
=import Element.Background as Background
=import Element.Border as Border
=import Element.Input as Input
=import Html exposing (Html)
+import Spring exposing (Spring)
=import Svg exposing (..)
=import Svg.Attributes exposing (..)
=
@@ -39,27 +42,37 @@ defaults =
=
=main : Program () Model Msg
=main =
- Browser.sandbox
+ Browser.element
= { init = init
= , view = view
= , update = update
+ , subscriptions = subscriptions
= }
=
=
=type alias Model =
- { x : Float
- , y : Float
+ { x : Spring
+ , y : Spring
= }
=
=
=type Msg
= = SetX Float
= | SetY Float
+ | Animate Float
=
=
-init : Model
-init =
- { x = 0, y = 0 }
+type alias Flags =
+ ()
+
+
+init : Flags -> ( Model, Cmd Msg )
+init () =
+ ( { x = Spring.create 20 20
+ , y = Spring.create 20 20
+ }
+ , Cmd.none
+ )
=
=
=view : Model -> Html Msg
@@ -76,14 +89,30 @@ view model =
= ]
=
=
-update : Msg -> Model -> Model
+update : Msg -> Model -> ( Model, Cmd Msg )
=update msg model =
- case msg of
+ case Debug.log "CartesianCoordinatesMsg" msg of
= SetX x ->
- { model | x = x }
+ ( { model | x = Spring.setTarget x model.x }
+ , Cmd.none
+ )
=
= SetY y ->
- { model | y = y }
+ ( { model | y = Spring.setTarget y model.y }
+ , Cmd.none
+ )
+
+ Animate delta ->
+ ( { model
+ | x = Spring.jumpTo (Spring.target model.x) model.x
+ , y = Spring.jumpTo (Spring.target model.y) model.y
+ }
+ , Cmd.none
+ )
+
+
+subscriptions model =
+ Browser.Events.onAnimationFrameDelta Animate
=
=
=ui : Config -> Model -> Element Msg
@@ -91,15 +120,19 @@ ui config model =
= let
= graph =
= [ circle
- [ cx <| String.fromFloat model.x
- , cy <| String.fromFloat model.y
+ [ Spring.value model.x
+ |> String.fromFloat
+ |> cx
+ , Spring.value model.y
+ |> String.fromFloat
+ |> cy
= , r "2"
= , fill "magenta"
= ]
= []
= , text_
- [ x <| String.fromFloat (model.x + 5)
- , y <| String.fromFloat (model.y + 5)
+ [ x <| String.fromFloat (Spring.value model.x + 5)
+ , y <| String.fromFloat (Spring.value model.y + 5)
= , fontSize "6"
= , fontFamily "Source Code Pro, monospace"
= , fill "gray"
@@ -116,9 +149,9 @@ ui config model =
=
= coordinates =
= "{ x = "
- ++ String.fromFloat model.x
+ ++ (model.x |> Spring.target |> String.fromFloat)
= ++ ", y = "
- ++ String.fromFloat model.y
+ ++ (model.y |> Spring.target |> String.fromFloat)
= ++ "}"
= in
= Element.column
@@ -143,10 +176,10 @@ ui config model =
= { onChange = SetX
= , label =
= Input.labelBelow [ Element.centerX ] <|
- Element.text ("x = " ++ String.fromFloat model.x)
+ Element.text ("x = " ++ String.fromFloat (Spring.target model.y))
= , min = config.minX
= , max = config.maxX
- , value = model.x
+ , value = Spring.target model.x
= , thumb = Input.defaultThumb
= , step = Just 0.01
= }
@@ -165,10 +198,10 @@ ui config model =
= { onChange = SetY
= , label =
= Input.labelBelow [ Element.centerX ] <|
- Element.text ("y = " ++ String.fromFloat model.y)
+ Element.text ("y = " ++ String.fromFloat (Spring.target model.y))
= , min = config.minY
= , max = config.maxY
- , value = model.y
+ , value = Spring.target model.y
= , thumb = Input.defaultThumb
= , step = Just 0.01
= }Merge branch 'master' into springs
On by
In CartesianCoordinates do not subscribe to animation frames if springs are at rest
On by
It leads to frequent garbage collection, which hits performance.
Also use translations instead of setting x and y attributes for SVG elements. Otherwise browsers are recalcuating the layout and frame rate drops.
index f45515a..a8f1e95 100644
--- a/src/Examples/CartesianCoordinates.elm
+++ b/src/Examples/CartesianCoordinates.elm
@@ -21,6 +21,7 @@ import Html exposing (Html)
=import Spring exposing (Spring)
=import Svg exposing (..)
=import Svg.Attributes exposing (..)
+import Transformations exposing (Transformation)
=
=
=type alias Config =
@@ -91,7 +92,7 @@ view model =
=
=update : Msg -> Model -> ( Model, Cmd Msg )
=update msg model =
- case Debug.log "CartesianCoordinatesMsg" msg of
+ case msg of
= SetX x ->
= ( { model | x = Spring.setTarget x model.x }
= , Cmd.none
@@ -112,7 +113,11 @@ update msg model =
=
=
=subscriptions model =
- Browser.Events.onAnimationFrameDelta Animate
+ if Spring.atRest model.x && Spring.atRest model.y then
+ Sub.none
+
+ else
+ Browser.Events.onAnimationFrameDelta Animate
=
=
=ui : Config -> Model -> Element Msg
@@ -120,19 +125,21 @@ ui config model =
= let
= graph =
= [ circle
- [ Spring.value model.x
- |> String.fromFloat
- |> cx
- , Spring.value model.y
- |> String.fromFloat
- |> cy
+ [ Transformations.Translate
+ (Spring.value model.x)
+ (Spring.value model.y)
+ |> Transformations.toString
+ |> Svg.Attributes.transform
= , r "2"
= , fill "magenta"
= ]
= []
= , text_
- [ x <| String.fromFloat (Spring.value model.x + 5)
- , y <| String.fromFloat (Spring.value model.y + 5)
+ [ Transformations.Translate
+ (5 + Spring.value model.x)
+ (-5 + Spring.value model.y)
+ |> Transformations.toString
+ |> Svg.Attributes.transform
= , fontSize "6"
= , fontFamily "Source Code Pro, monospace"
= , fill "gray"Use tad-lispy/springs
On by
I've published it 😎
index a5f4ae6..f19e8dc 100644
--- a/elm.json
+++ b/elm.json
@@ -25,6 +25,7 @@
= "ianmackenzie/elm-geometry-svg": "1.0.2",
= "mdgriffith/elm-markup": "2.0.6",
= "mdgriffith/elm-ui": "1.1.0",
+ "tad-lispy/springs": "1.0.4",
= "turboMaCk/any-dict": "1.0.1"
= },
= "indirect": {index a8f1e95..18f4a42 100644
--- a/src/Examples/CartesianCoordinates.elm
+++ b/src/Examples/CartesianCoordinates.elm
@@ -69,8 +69,8 @@ type alias Flags =
=
=init : Flags -> ( Model, Cmd Msg )
=init () =
- ( { x = Spring.create 20 20
- , y = Spring.create 20 20
+ ( { x = Spring.create { strength = 20, dampness = 5 }
+ , y = Spring.create { strength = 20, dampness = 5 }
= }
= , Cmd.none
= )index eb30eb9..0ffec3a 100644
--- a/src/Main.elm
+++ b/src/Main.elm
@@ -138,7 +138,7 @@ init flags url key =
= , key = key
= , content = content
= , examples = examplesModel
- , scroll = Spring.create 20 20
+ , scroll = Spring.create { strength = 20, dampness = 4 }
= , viewport =
= { width = 800, height = 600 }
= }deleted file mode 100644
index 0b7bdc3..0000000
--- a/src/Spring.elm
+++ /dev/null
@@ -1,248 +0,0 @@
-module Spring exposing
- ( Spring
- , animate
- , atRest
- , create
- , jumpTo
- , setTarget
- , target
- , value
- )
-
-{-| A rough model of a mass attached to a spring, as described by [Hooke's law](https://en.wikipedia.org/wiki/Hooke's_law). Good for making smooth and organic looking animations or modeling oscilating values (e.g. emotions). High physical accuracy is not a priority - performance and API is more important.
--}
-
-
-{-| A model of mass attached to a spring. The spring is anchored to a target. The mass is constant (1).
-
-Value represents the current position of the mass. It is re-calculated (together with velocity) by animate function.
-
-Strength regulates how strongly the spring pulls toward target. It is also called the stiffness but I find the former term more intuitive.
-
-Dampness is how resistant the spring is to change in it's stretch (both stretching out and contracting in). If dumpness is low relative to strength, then the animation will end in long period of vibration around the target value - in other words lowering dumpness will increase wobbliness. Setting dumpness to 0 will result in something like a sine wave oscilator (but it's not advise to depend on it's accuracy).
-
-Target is the value toward which the mass is pulled. Typically the spring will start in an equilibrium position (i.e. value == target) and later on (due to an event) the target will be changed and the value will follow according to the strength and dampness of the spring.
-
-Value is where the mass is. It can be extracted from the spring using `value` function and set (with `setValue` function - rarely useful).
-
-Velocity is an internal property that cannot be directly modified or read.
-
-Let's say we are creating a program that animates the position of an element toward last click position.
-
- type alias Model =
- { x : Spring
- , y : Spring
- }
-
--}
-type Spring
- = Spring
- { strength : Float
- , dampness : Float
- , target : Float
- , value : Float
- , velocity : Float
- }
-
-
-{-| Create a spring in an equilibrium state.
-
-Let's say we are creating a program that animates the position of an element toward last click position.
-
- type alias Model =
- { x : Spring
- , y : Spring
- }
-
- init : Flags -> ( Model, Cmd msg )
- init flags =
- ( { x = Spring.create 20 20
- , y = Spring.create 20 20
- }
- , Cmd.none
- )
-
--}
-create : Float -> Float -> Spring
-create strength dampness =
- Spring
- { strength = strength
- , dampness = dampness
- , target = 0
- , value = 0
- , velocity = 0
- }
-
-
-{-| Set a new target (relaxed value) for the spring.
-
-The current value and it's velocity will remain the same. Typically you would set a target in response to an event, e.g.:
-
- update : Msg -> Model -> ( Model, Cmd Msg )
- update msg model =
- case msg of
- Click x y ->
- ( { model
- | x = Spring.setTarget x model.x
- , y = Spring.setTarget y model.y
- }
- , Cmd.none
- )
-
--}
-setTarget : Float -> Spring -> Spring
-setTarget target_ (Spring spring) =
- Spring
- { spring
- | target =
- target_
- }
-
-
-{-| Update the spring
-
-Typically you would do it in response to an animation frame message, like this:
-
- subscriptions : Model -> Sub Msg
- subscriptions model =
- Browser.Events.onAnimationFrameDelta Animate
-
- update : Msg -> Model -> ( Model, Cmd Msg )
- update msg model =
- case msg of
- Animate delta ->
- ( { model
- | x = Spring.animate delta model.x
- , y = Spring.animate delta model.y
- }
- , Cmd.none
- )
-
--}
-animate : Float -> Spring -> Spring
-animate delta ((Spring spring) as this) =
- if atRest this then
- {- If it's in equilibrium, then let's just skip the whole calculation. Be lazy. -}
- Spring spring
-
- else
- let
- time =
- {- Low sampling rates often result in nasty errors.
-
- With this cap on delta, if the frame rate is lower than 30fps the animation will slow down and good sampling rate will be preserved.
- -}
- min delta 32 / 1000
-
- stretch =
- spring.target - spring.value
-
- force =
- stretch * spring.strength
-
- dampening =
- spring.velocity * spring.dampness
-
- acceleration =
- time * (force - dampening)
- in
- if
- (abs (spring.value - spring.target) < 0.001)
- && (abs spring.velocity < 0.001)
- then
- {- In reality the spring never stops vibrating, but at some point the vibration is lost in the background noise (uncertainity principle). In our case it's also a wasted computation. Let's just say that it is at rest already. -} {- Snap to ideal equilibrium -}
- Spring
- { spring
- | value = spring.target
- , velocity = 0
- }
-
- else
- Spring
- { spring
- | value =
- spring.value + (spring.velocity * time)
- , velocity =
- spring.velocity + acceleration
- }
-
-
-{-| Measure the value of the spring.
-
-Typically you want to access it in the view function, like this:
-
- Element.el
- [ Element.width (Element.px 20)
- , Element.height (Element.px 20)
- , Font.size 30
- , model.x
- |> Spring.value
- |> Element.moveRight
- , model.y
- |> Spring.value
- |> Element.moveDown
- ]
- (Element.text "\u{1F991}")
-
-Above we use Elm UI Elements and Attributes, but it's not difficult to implement same behavior using CSS transformations. Spring value is just a `Float`.
-
--}
-value : Spring -> Float
-value (Spring spring) =
- spring.value
-
-
-{-| Get current target of a spring
-
-Can be useful to see where the spring is going. Maybe you want to display something there?
-
--}
-target : Spring -> Float
-target (Spring spring) =
- spring.target
-
-
-{-| Check if the spring is at rest
-
-It indicates that no animation is running. Maybe you want to unsubscribe from animation frames? Or remove an element?
-
- subscriptions : Model -> Sub Msg
- subscriptions model =
- if Spring.atRest model.x && Spring.atRest model.y then
- Sub.none
-
- else
- Browser.Events.onAnimationFrameDelta Animate
-
--}
-atRest : Spring -> Bool
-atRest (Spring spring) =
- spring.value == spring.target && spring.velocity == 0.0
-
-
-{-| Forcefully set the value and interrupt the animation.
-
-It is useful when you set the spring for the first time (e.g. in init function) or you want to reset the animation.
-
- init : Flags -> ( Model, Cmd msg )
- init flags =
- ( { x =
- Spring.create 20 20
- |> Spring.setTarget 200
- |> Spring.jumpTo 200
- , y =
- Spring.create 20 20
- |> Spring.setTarget 200
- |> Spring.jumpTo 200
- }
- , Cmd.none
- )
-
--}
-jumpTo : Float -> Spring -> Spring
-jumpTo value_ (Spring spring) =
- Spring
- { spring
- | value = value_
- , velocity = 0.0
- }Remove Examples/Spring
On by
It lives at https://tad-lispy.gitlab.io/elm-springs/Squid.html now
deleted file mode 100644
index 690d5fb..0000000
--- a/src/Examples/Spring.elm
+++ /dev/null
@@ -1,139 +0,0 @@
-module Examples.Spring exposing
- ( Model
- , Msg
- , init
- , main
- , subscriptions
- , ui
- , update
- )
-
-import Browser
-import Browser.Events
-import Element exposing (Element)
-import Element.Background as Background
-import Element.Events as Events
-import Element.Font as Font
-import Element.Input as Input
-import Examples.Tree
-import FeatherIcons
-import Html exposing (Html)
-import Html.Events
-import Json.Decode as Decode exposing (Decoder)
-import Spring exposing (Spring)
-
-
-main : Program Flags Model Msg
-main =
- Browser.element
- { init = init
- , view = view
- , update = update
- , subscriptions = subscriptions
- }
-
-
-type alias Model =
- { x : Spring
- , y : Spring
- }
-
-
-type alias Flags =
- ()
-
-
-init : Flags -> ( Model, Cmd msg )
-init flags =
- let
- strength =
- 20
-
- dampness =
- 20
- in
- ( { x =
- Spring.create strength dampness
- |> Spring.setTarget 200
- , y =
- Spring.create strength dampness
- |> Spring.setTarget 200
- }
- , Cmd.none
- )
-
-
-ui : Model -> Element Msg
-ui model =
- Element.el
- [ Element.width (Element.px 20)
- , Element.height (Element.px 20)
- , Font.size 30
-
- -- , Background.color (Element.rgb 1 0 0)
- , model.x
- |> Spring.value
- |> Element.moveRight
- , model.y
- |> Spring.value
- |> Element.moveDown
- ]
- (Element.text "\u{1F991}")
-
-
-type Msg
- = Animate Float
- | Mouse Float Float
-
-
-view : Model -> Html Msg
-view model =
- model
- |> ui
- |> Element.layout
- [ Element.width Element.fill
- , Element.height Element.fill
- , mouseDecoder
- |> Html.Events.on "click"
- |> Element.htmlAttribute
- ]
-
-
-update : Msg -> Model -> ( Model, Cmd Msg )
-update msg model =
- case msg of
- Animate delta ->
- ( { model
- | x = Spring.animate delta model.x
- , y = Spring.animate delta model.y
- }
- , Cmd.none
- )
-
- Mouse x y ->
- ( { model
- | x = Spring.setTarget x model.x
- , y = Spring.setTarget y model.y
- }
- , Cmd.none
- )
-
-
-mouseDecoder : Decoder Msg
-mouseDecoder =
- Decode.map2 Mouse
- (Decode.field "clientX" Decode.float)
- (Decode.field "clientY" Decode.float)
- |> Decode.map (Debug.log "Msg")
-
-
-subscriptions : Model -> Sub Msg
-subscriptions model =
- [ if Spring.atRest model.x && Spring.atRest model.y then
- Sub.none
-
- else
- Browser.Events.onAnimationFrameDelta Animate
- , Browser.Events.onMouseMove mouseDecoder
- ]
- |> Sub.batchAdd a text on test-run.
On by
Informing students who can't register to reach out to us.
index 9a06486..39ca6b4 100644
--- a/content/test-run.txt
+++ b/content/test-run.txt
@@ -10,6 +10,8 @@ In collaboration with {Link|Career Services|url=https://students.uu.nl/en/career
= 07 March 2019: /Marinus Langeveld Room G230/ Time *16:00* - *19:00*
= 08 March 2019: /Marinus Ruppert Room 121/ Time *16:00* - *19:00*
=
+The workshop slots are almost filled. If you can't register and would like to attend in the future please, reach out to us. We will try to arrange another workshop with Utrecht University.
+
=| Emphasize
= {Link|Sign up now|url=https://fd21.formdesk.com/universiteitutrecht/CS-20190304-08-SoftwareGarden}
=Merge branch 'reg-wait-list' into 'master'
On by
Add a text on test-run.
See merge request software-garden/software-garden.gitlab.io!36
Merge branch 'springs' into 'master'
On by
Use tad-lispy/strings for better performance
See merge request software-garden/software-garden.gitlab.io!35
Create flash cards for day 1
On by
For now we can use markdown-preview-enhanced plugin for Atom to view them. Install the plugin, open the preview with:
Cmd-Shift-P > Markdown Preview Enhanced: Toggle
Then right click on the preview and chose:
Open in Browser
In the future we may implement something to build them automatically (based on Mume: https://github.com/shd101wyy/mume).
new file mode 100644
index 0000000..78a2ed5
--- /dev/null
+++ b/flash-cards.md
@@ -0,0 +1,235 @@
+---
+presentation:
+ enableSpeakerNotes: true
+ theme: solarized.css
+ height: 150%
+---
+
+<!-- slide -->
+
+# After day 1
+
+<!-- slide vertical -->
+
+On day one we have created a program that ...
+
+
+1. ... calculates the phase of the moon
+1. ... displays a dot in the middle of the screen <!-- .element: class="fragment grow" -->
+1. ... makes the screen pink
+
+
+<!-- slide vertical -->
+
+Consider the following code:
+
+```elm
+Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200" ]
+ [ Svg.circle
+ [ Svg.Attributes.cx "300"
+ , Svg.Attributes.cy "100"
+ , Svg.Attributes.r "10"
+ ]
+ []
+ ]
+```
+
+Where will the dot be?
+
+
+1. Visible on the screen
+1. To the left of the screen (invisible)
+1. To the right of the screen (invisible) <!-- .element: class="fragment grow" -->
+
+
+<!-- slide vertical -->
+
+What does it mean to format the code?
+
+
+1. To make it work when previously it was broken.
+1. To make it look good. <!-- .element: class="fragment grow" -->
+1. To write it from scratch.
+1. To take code created by somebody else and adapt it to our needs.
+
+
+<!-- slide vertical -->
+
+A way of describing a position of a point on flat a surface is called:
+
+1. the origin
+1. geography
+1. SVG
+1. cartesian coordinates system <!-- .element: class="fragment grow" -->
+1. X - Y system
+
+
+
+
+
+<!-- slide vertical -->
+
+In SVG, the greater the value of the y coordinate ...
+
+1. ... the lower the point is on the screen. <!-- .element: class="fragment grow" -->
+1. ... the higher the point is on the screen.
+1. ... the bigger the point is.
+
+
+<!-- slide vertical -->
+
+The value of `Svg.Attributes.r` affects:
+
+1. rotation of a circle
+1. roundness of a circle
+1. size of a circle <!-- .element: class="fragment grow" -->
+
+
+<!-- slide vertical -->
+
+The value of `Svg.Attributes.viewBox` controls:
+
+1. How big the screen is.
+1. At what fragment of a surface we are looking. <!-- .element: class="fragment grow" -->
+1. How big the SVG element is.
+1. How big the viewport is.
+
+
+<!-- slide vertical -->
+
+What is the origin?
+
+1. The first copy of a program.
+1. A number representing the size of a dot.
+1. A point in cartesian coordinates system. <!-- .element: class="fragment grow" -->
+
+
+<!-- slide vertical -->
+
+If the viewbox is
+
+```elm
+{ left = -200
+, top = -200
+, width = 300
+, height = 200
+}
+```
+
+then the point at the centre is:
+
+1. `{ x = 0, y = 0 }`
+1. `{ x = -50, y = -100 }` <!-- .element: class="fragment grow" -->
+1. `{ x = 150, y = 100 }`
+
+
+<!-- slide vertical -->
+
+To make sure that a given point is in the middle of the screen, we can:
+
+1. Move the viewport
+1. Move the viewbox <!-- .element: class="fragment grow" -->
+1. Move the screen
+1. Move the point
+
+
+<!-- slide vertical -->
+
+```elm
+main =
+ Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200" ]
+ [ Svg.rect
+ [ Svg.Attributes.x "-10"
+ , Svg.Attributes.y "-10"
+ , Svg.Attributes.width "20"
+ , Svg.Attributes.height "20"
+ ]
+ []
+ ]
+```
+
+Which output will this program produce?
+
+<div style="display: flex">
+ <svg viewbox="-100 -100 200 200" style=" background: white; border: solid 1px black; border-radius: 10px; margin: 30px;" class="fragment grow">
+ <rect x="-10" y="-10" width="20" height="20">
+ </svg>
+
+ <svg viewbox="-100 -100 200 200" style=" background: white; border: solid 1px black; border-radius: 10px; margin: 30px;">
+ <circle r="10">
+ </svg>
+
+ <svg viewbox="-100 -100 200 200" style=" background: white; border: solid 1px black; border-radius: 10px; margin: 30px;">
+ <rect x="-15" y="-10" width="30" height="20">
+ </svg>
+</div>
+
+
+<!-- slide vertical -->
+
+What is this code missing?
+
+```elm
+main =
+ Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200"
+ [ Svg.rect
+ [ x "-10"
+ , y "-10"
+ , width "20"
+ , height "20"
+ ]
+ []
+ ]
+```
+
+1. A closing quotation mark
+1. A closing square bracket <!-- .element: class="fragment grow" -->
+1. An equal sign
+
+
+<!-- slide vertical -->
+
+This fragment of code is broken:
+
+```elm
+Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200" ]
+ [ Svg.circle [ Svg.Attributes.r: "10" ] [] ]
+```
+
+Which one is the correct version?
+
+```elm
+Svg.svg
+ [ Svg.Attributes.viewBox = "-100 -100 200 200" ]
+ [ Svg.circle [ Svg.Attributes.r = "10" ] [] ]
+```
+
+```elm
+Svg.svg
+ [ Svg.Attributes.viewBox: "-100 -100 200 200" ]
+ [ Svg.circle [ Svg.Attributes.r: "10" ] [] ]
+```
+
+```elm { .fragment .grow}
+Svg.svg
+ [ Svg.Attributes.viewBox "-100 -100 200 200" ]
+ [ Svg.circle [ Svg.Attributes.r "10" ] [] ]
+```
+
+<!-- slide vertical -->
+
+## You are the best 👑
+
+
+<!-- slide -->
+
+# After day 2
+
+
+<!-- slide vertical -->
+
+> TODO: Flashcards for day 2Merge branch 'flash-cards' into 'master'
On by
Create flash cards for day 1
See merge request software-garden/software-garden.gitlab.io!37
Schedule a timeline for day 1
On by
new file mode 100644
index 0000000..a15227e
--- /dev/null
+++ b/TIMELINE.md
@@ -0,0 +1,120 @@
+# Timeline for the workshop
+
+
+## Day 1
+
+### 00:00
+
+ - 👋 👋🏾 Introduce ourselves
+
+ - Ask to write names on badges
+
+ - 🙋 Who read the material?
+
+ - Organizational stuff:
+
+ 5 minute break after 1h
+
+ 20 minutes after 2h
+
+ - 💬 Any questions?
+
+
+### 00:05
+
+ - Introduce the problem (Ellie)
+
+ - Are there any questions?
+
+### 00:10
+
+ - 💻 Introduce SVG
+
+ Installation
+
+ Import
+
+ Make a dot with radius only
+
+ 🙋 Is everyone having it working
+
+### 00:15
+
+ - 💬 Where is the dot?
+
+ - 💻 SVG Element - let's make it pink
+
+ - 🙋 Do you have it? Is it more or less clear?
+
+### 00:20
+
+ - 💻 Fill the viewport
+
+ Install Elm UI
+
+ Import
+
+ `svg |> html |> layout`
+
+ - 🙋 Do you have it working?
+
+ It's a technicality, so let's not focus too much on that right now.
+
+### 00:30
+
+ - Break (5 minutes)
+
+### 00:40
+
+ - Cartesian coordinates system
+
+ 🙋 Who knows what it is?
+
+ 👨🏫 Explain on the whiteboard ( ⇄ 📏 2d ☩ )
+
+ - Change the code to move the dot ( `cx` `cy` )
+
+ 🕞 Take 3 minutes to play with it
+
+ - 💬 Questions?
+
+
+### 00:50
+
+ - Screen size 💥
+
+ - 👨🏫 The infinite canvas and the viewbox into it
+
+ We know the position of a point and we can set the viewbox any way we like. How can we make it in the middle?
+
+ - 💻 Code
+
+ - 💬 Questions?
+
+### 01:10
+
+ - Color
+
+ 💬 What else is there about our dot?
+
+ 💻 `fill "skyblue"`
+
+ 🙋 Who is done?
+
+ 💬 Questions
+
+### 01:20
+
+ - Break 20 minutes
+
+### 01:40
+
+ - 💬 How did you like it?
+
+ Is there anything we could do better?
+
+ New questions, ideas?
+
+ Stashed questions
+
+ See you tomorrow 👋🏾 👋Merge branch 'timeline' into 'master'
On by
Schedule a timeline for day 1
See merge request software-garden/software-garden.gitlab.io!38