Commits: 17
Move AnimatedTree lower in the home page
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'
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
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
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.
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'
Make few corrections to day 4
See merge request software-garden/software-garden.gitlab.io!31
Merge branch 'custom-animated-tree' into 'master'
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
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
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'
Prevent scaling the viewport on mobile devices
See merge request software-garden/software-garden.gitlab.io!33
Set mobile scale to 0.75
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'
Set mobile scale to 0.75
See merge request software-garden/software-garden.gitlab.io!34
Make the text in Coupon element wrap
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'
Do not build examples in develop screipt
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
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
= }