├── cmd
├── server-spa
├── start
├── reset
├── surge
└── build
├── docs
├── images
│ ├── 2d3d.gif
│ ├── cube.png
│ ├── 2d3d-1.png
│ ├── cube-animated.gif
│ ├── cube-animated.png
│ └── 968315a2-4e5f-4038-bad9-795e5cadf254.png
├── example3-game.html
├── example1-picture.html
├── example2-animation.html
├── examples.html
└── index.html
├── .gitignore
├── package.json
├── examples
├── Example1Picture.elm
├── Example2Animation.elm
├── Example3Game.elm
└── Examples.elm
├── elm.json
├── LICENSE
├── copy.json.OLD
├── README.md
├── src
└── Playground3d.elm
└── src-elm-playground
└── Playground.elm
/cmd/server-spa:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | http-server -p 8006
3 |
--------------------------------------------------------------------------------
/cmd/start:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | node_modules/.bin/elm reactor
5 |
--------------------------------------------------------------------------------
/cmd/reset:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | rm -rf elm-stuff ; rm ~/.elm/0.19.0/package/*/*/*/*.dat
3 |
--------------------------------------------------------------------------------
/docs/images/2d3d.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucamug/elm-playground-3d/HEAD/docs/images/2d3d.gif
--------------------------------------------------------------------------------
/docs/images/cube.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucamug/elm-playground-3d/HEAD/docs/images/cube.png
--------------------------------------------------------------------------------
/cmd/surge:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | node_modules/.bin/surge ./build elm-playground-3d.surge.sh
5 |
--------------------------------------------------------------------------------
/docs/images/2d3d-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucamug/elm-playground-3d/HEAD/docs/images/2d3d-1.png
--------------------------------------------------------------------------------
/docs/images/cube-animated.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucamug/elm-playground-3d/HEAD/docs/images/cube-animated.gif
--------------------------------------------------------------------------------
/docs/images/cube-animated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucamug/elm-playground-3d/HEAD/docs/images/cube-animated.png
--------------------------------------------------------------------------------
/docs/images/968315a2-4e5f-4038-bad9-795e5cadf254.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucamug/elm-playground-3d/HEAD/docs/images/968315a2-4e5f-4038-bad9-795e5cadf254.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated files
2 | docs/*.js
3 |
4 | # Dependency directories
5 | elm-stuff
6 | node_modules
7 |
8 | # Desktop Services Store on macOS
9 | .DS_Store
10 |
11 | build/
12 | Elmjutsu*
13 |
--------------------------------------------------------------------------------
/docs/example3-game.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Example 3 - Game - elm-playground-3d
6 |
7 |
8 |
13 |
14 |
15 |
16 |
17 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/docs/example1-picture.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Example 1 - Picture - elm-playground-3d
6 |
7 |
8 |
13 |
14 |
15 |
16 |
17 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/docs/example2-animation.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Example 2 - Animation - elm-playground-3d
6 |
7 |
8 |
13 |
14 |
15 |
16 |
17 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/docs/examples.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Example 3 - Game - elm-playground-3d
6 |
7 |
8 |
13 |
14 |
15 |
16 |
17 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "elm-playground-3d",
3 | "version": "1.0.0",
4 | "description": "A simple way to create three-dimensional pictures, animations, and games.",
5 | "main": "index.js",
6 | "repository": "https://github.com/lucamug/elm-playground-3d",
7 | "directories": {
8 | "doc": "docs"
9 | },
10 | "dependencies": {},
11 | "devDependencies": {
12 | "elm": "^0.19.1-3",
13 | "elm-live": "^4.0.1",
14 | "html-minifier": "^4.0.0",
15 | "surge": "^0.21.3",
16 | "uglifyjs": "^2.4.11"
17 | },
18 | "scripts": {
19 | "test": "echo \"Error: no test specified\" && exit 1"
20 | },
21 | "author": "Luca Mugnaini",
22 | "license": "BSD-3-Clause"
23 | }
24 |
--------------------------------------------------------------------------------
/examples/Example1Picture.elm:
--------------------------------------------------------------------------------
1 | module Example1Picture exposing (main)
2 |
3 | import Playground exposing (..)
4 | import Playground3d exposing (..)
5 |
6 |
7 | main : Program () Screen PictureMsg
8 | main =
9 | picture []
10 | [ group3d
11 | [ cube darkPurple purple lightPurple 600
12 | |> fade3d 0.5
13 | , words3d darkPurple "3D"
14 | |> scale3d 5
15 | ]
16 | |> shape3dto2d camera1
17 | |> moveLeft 180
18 | , group
19 | [ square darkPurple 300
20 | |> fade 0.5
21 | , words darkPurple "2D"
22 | |> scale 5
23 | ]
24 | |> moveRight 180
25 | ]
26 |
--------------------------------------------------------------------------------
/examples/Example2Animation.elm:
--------------------------------------------------------------------------------
1 | module Example2Animation exposing (main)
2 |
3 | import Playground exposing (..)
4 | import Playground3d exposing (..)
5 |
6 |
7 | main : Program () Animation Msg
8 | main =
9 | animation [] <|
10 | \time ->
11 | [ group3d
12 | [ cube lightPurple purple darkPurple 600
13 | |> fade3d 0.5
14 | , words3d darkPurple "3D"
15 | |> scale3d 5
16 | |> move3d 300 300 0
17 | ]
18 | |> rotate3d 0 0 (spin 3 time)
19 | |> shape3dto2d camera1
20 | |> rotate (spin 3 time)
21 | |> moveLeft 180
22 | , group
23 | [ square darkPurple 300
24 | |> fade 0.5
25 | , words darkPurple "2D"
26 | |> scale 5
27 | ]
28 | |> rotate (spin 3 time)
29 | |> moveRight 180
30 | ]
31 |
--------------------------------------------------------------------------------
/elm.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "application",
3 | "source-directories": [
4 | "src",
5 | "src-elm-playground"
6 | ],
7 | "elm-version": "0.19.1",
8 | "dependencies": {
9 | "direct": {
10 | "elm/browser": "1.0.1",
11 | "elm/core": "1.0.2",
12 | "elm/html": "1.0.0",
13 | "elm/json": "1.1.3",
14 | "elm/svg": "1.0.1",
15 | "elm/time": "1.0.0",
16 | "ianmackenzie/elm-3d-camera": "2.0.0",
17 | "ianmackenzie/elm-geometry": "2.0.0",
18 | "ianmackenzie/elm-units": "2.2.0",
19 | "mdgriffith/elm-ui": "1.1.0"
20 | },
21 | "indirect": {
22 | "elm/url": "1.0.0",
23 | "elm/virtual-dom": "1.0.2",
24 | "elm-explorations/linear-algebra": "1.0.3",
25 | "ianmackenzie/elm-1d-parameter": "1.0.1",
26 | "ianmackenzie/elm-float-extra": "1.1.0",
27 | "ianmackenzie/elm-geometry-linear-algebra-interop": "2.0.0",
28 | "ianmackenzie/elm-interval": "2.0.0",
29 | "ianmackenzie/elm-triangular-mesh": "1.0.2",
30 | "ianmackenzie/elm-units-interval": "1.0.0"
31 | }
32 | },
33 | "test-dependencies": {
34 | "direct": {},
35 | "indirect": {}
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2019-present, Luca Mugnaini. All rights reserved.
2 |
3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4 |
5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
7 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
8 |
9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
10 |
--------------------------------------------------------------------------------
/copy.json.OLD:
--------------------------------------------------------------------------------
1 | {
2 | "type": "application",
3 | "name": "lucamug/elm-playground-3d",
4 | "summary": "A simple way to create three-dimensional pictures, animations, and games.",
5 | "author": "Luca Mugnaini",
6 | "license": "BSD-3-Clause",
7 | "version": "1.0.0",
8 | "source-directories": [
9 | "src"
10 | ],
11 | "elm-version": "0.19.0",
12 | "dependencies": {
13 | "direct": {
14 | "elm/browser": "1.0.1",
15 | "elm/core": "1.0.2",
16 | "elm/html": "1.0.0",
17 | "evancz/elm-playground": "1.0.2",
18 | "ianmackenzie/elm-3d-camera": "2.0.0",
19 | "ianmackenzie/elm-geometry": "2.0.0",
20 | "ianmackenzie/elm-units": "2.2.0"
21 | },
22 | "indirect": {
23 | "elm/json": "1.1.3",
24 | "elm/svg": "1.0.1",
25 | "elm/time": "1.0.0",
26 | "elm/url": "1.0.0",
27 | "elm/virtual-dom": "1.0.2",
28 | "elm-explorations/linear-algebra": "1.0.3",
29 | "ianmackenzie/elm-1d-parameter": "1.0.1",
30 | "ianmackenzie/elm-float-extra": "1.1.0",
31 | "ianmackenzie/elm-geometry-linear-algebra-interop": "2.0.0",
32 | "ianmackenzie/elm-interval": "2.0.0",
33 | "ianmackenzie/elm-triangular-mesh": "1.0.2",
34 | "ianmackenzie/elm-units-interval": "1.0.0"
35 | }
36 | },
37 | "test-dependencies": {
38 | "direct": {},
39 | "indirect": {}
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | elm-playground-3d
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
32 |
33 |
34 |
35 | elm-playground-3d
36 |
37 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # elm-playground-3d
2 |
3 | ## Detailed post in Medium: [Basic 3D rendering in SVG: elm-playground-3d](https://medium.com/@l.mugnaini/basic-3d-rendering-in-svg-elm-playground-3d-d1e8846cd06e)
4 |
5 | 
6 |
7 | This is an experimental library built on top of [elm-playground](https://package.elm-lang.org/packages/evancz/elm-playground/latest/) to draw simple three-dimensional objects.
8 |
9 | Example 1: [Picture](https://elm-playground-3d.netlify.com/example1-picture.html)
10 |
11 | [Code](https://github.com/lucamug/elm-playground-3d/blob/master/examples/Example1Picture.elm)
12 |
13 | 
14 |
15 | Example 2: [Animation](https://elm-playground-3d.netlify.com/example2-animation.html)
16 |
17 | [Code](https://github.com/lucamug/elm-playground-3d/blob/master/examples/Example2Animation.elm)
18 |
19 | 
20 |
21 | Example 3: [Game](https://elm-playground-3d.netlify.com/example3-game.html)
22 |
23 | [Code](https://github.com/lucamug/elm-playground-3d/blob/master/examples/Example3Game.elm)
24 |
25 | 
26 |
27 | For more serious 3D stuff in Elm, have a look at
28 |
29 | * [elm-explorations/webgl](https://package.elm-lang.org/packages/elm-explorations/webgl/latest/)
30 | * [ianmackenzie/elm-3d-scene](https://github.com/ianmackenzie/elm-3d-scene)
31 | * [unsoundscapes/cubik](https://unsoundscapes.itch.io/cubik)
32 | * [w0rm/elm-physics](https://package.elm-lang.org/packages/w0rm/elm-physics/latest)
33 | * [3D Vector Animations](https://medium.com/ninjaconcept/3d-vector-animations-in-elm-58703993d144)
34 | * [Talk: "A 3D Rendering Engine for Elm" by Ian Mackenzie](https://www.youtube.com/watch?time_continue=1668&v=Htqc64s5qYU)
35 | * [Talk: "Fighting the Law of Physics with Elm" by Andrey Kuzmin](https://www.youtube.com/watch?v=WXz-I1_yFic)
36 | * [Talk: "Bringing the fun to graphics programming" by Andrey Kuzmin](https://www.youtube.com/watch?v=Z-6ETEBNlMs)
37 | * [Talk: "Rendering text with WebGL" by Andrey Kuzmin](https://www.youtube.com/watch?v=qasFxsOCfpA)
38 |
39 | [](https://app.netlify.com/sites/elm-playground-3d/deploys)
40 |
--------------------------------------------------------------------------------
/cmd/build:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | cmd/reset | true
5 |
6 | printf "\n\e[0;32m🍣 Removing build folder...\e[0m\n\n"
7 |
8 | rm -rf build
9 | mkdir build
10 | mkdir build/TEMP
11 |
12 | printf "\n\e[0;32m🍣 Copying files...\e[0m\n\n"
13 |
14 | cp -r docs/images build/
15 |
16 | cp docs/index.html build/TEMP/temp.html
17 | node_modules/.bin/html-minifier -o build/index.html build/TEMP/temp.html --remove-comments --collapse-whitespace --minify-css --minify-js --preserve-line-breaks
18 |
19 | cp docs/example1-picture.html build/TEMP/temp.html
20 | node_modules/.bin/html-minifier -o build/example1-picture.html build/TEMP/temp.html --remove-comments --collapse-whitespace --minify-css --minify-js --preserve-line-breaks
21 |
22 | cp docs/example2-animation.html build/TEMP/temp.html
23 | node_modules/.bin/html-minifier -o build/example2-animation.html build/TEMP/temp.html --remove-comments --collapse-whitespace --minify-css --minify-js --preserve-line-breaks
24 |
25 | cp docs/example3-game.html build/TEMP/temp.html
26 | node_modules/.bin/html-minifier -o build/example3-game.html build/TEMP/temp.html --remove-comments --collapse-whitespace --minify-css --minify-js --preserve-line-breaks
27 |
28 | cp docs/examples.html build/TEMP/temp.html
29 | node_modules/.bin/html-minifier -o build/examples.html build/TEMP/temp.html --remove-comments --collapse-whitespace --minify-css --minify-js --preserve-line-breaks
30 |
31 | node_modules/.bin/elm make examples/Example1Picture.elm --output=build/TEMP/temp.js --optimize
32 | node_modules/.bin/uglifyjs build/TEMP/temp.js --compress 'pure_funcs="F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9",pure_getters,keep_fargs=false,unsafe_comps,unsafe' | node_modules/.bin/uglifyjs --mangle --output=build/example1-picture.js
33 |
34 | node_modules/.bin/elm make examples/Example2Animation.elm --output=build/TEMP/temp.js --optimize
35 | node_modules/.bin/uglifyjs build/TEMP/temp.js --compress 'pure_funcs="F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9",pure_getters,keep_fargs=false,unsafe_comps,unsafe' | node_modules/.bin/uglifyjs --mangle --output=build/example2-animation.js
36 |
37 | node_modules/.bin/elm make examples/Example3Game.elm --output=build/TEMP/temp.js --optimize
38 | node_modules/.bin/uglifyjs build/TEMP/temp.js --compress 'pure_funcs="F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9",pure_getters,keep_fargs=false,unsafe_comps,unsafe' | node_modules/.bin/uglifyjs --mangle --output=build/example3-game.js
39 |
40 | node_modules/.bin/elm make examples/Examples.elm --output=build/TEMP/temp.js --optimize
41 | node_modules/.bin/uglifyjs build/TEMP/temp.js --compress 'pure_funcs="F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9",pure_getters,keep_fargs=false,unsafe_comps,unsafe' | node_modules/.bin/uglifyjs --mangle --output=build/examples.js
42 |
43 | printf "\n\e[0;32m🍣 Cleaning up...\e[0m\n\n"
44 |
45 | rm -rf build/TEMP
46 |
--------------------------------------------------------------------------------
/examples/Example3Game.elm:
--------------------------------------------------------------------------------
1 | module Example3Game exposing (main)
2 |
3 | import Playground exposing (..)
4 | import Playground3d exposing (..)
5 | import Set
6 |
7 |
8 | attributes : List Attributes
9 | attributes =
10 | []
11 |
12 |
13 | type alias Memory =
14 | ( Float, Float )
15 |
16 |
17 | init : Memory
18 | init =
19 | ( 0, 0 )
20 |
21 |
22 | update : Computer -> Memory -> Memory
23 | update computer ( x, y ) =
24 | let
25 | ( dx, dy ) =
26 | toXY computer.keyboard
27 | in
28 | ( x + dx, y + dy )
29 |
30 |
31 | view : Computer -> Memory -> List Shape
32 | view computer ( x, y ) =
33 | [ shape3dto2d camera2 <|
34 | fade3d 0.8 <|
35 | rotate3d 0 0 (computer.mouse.x / 3) <|
36 | group3d
37 | [ polygon3d lightGray
38 | [ ( 0, 0, 0 )
39 | , ( 0, 1000, 0 )
40 | , ( 0, 1000, 1000 )
41 | , ( 0, 0, 1000 )
42 | ]
43 | , polygon3d grey
44 | [ ( 0, 0, 0 )
45 | , ( 1000, 0, 0 )
46 | , ( 1000, 0, 1000 )
47 | , ( 0, 0, 1000 )
48 | ]
49 | , polygon3d darkGray
50 | [ ( 0, 0, 0 )
51 | , ( 0, 1000, 0 )
52 | , ( 1000, 1000, 0 )
53 | , ( 1000, 0, 0 )
54 | ]
55 | , (if Set.member "d" computer.keyboard.keys then
56 | move3d (clamp 100 900 (500 - x * 30)) (clamp 100 900 (500 + y * 30)) 100
57 |
58 | else
59 | move3d (500 - x * 30) (500 - y * 30) (500 - computer.mouse.y * 2)
60 | )
61 | <|
62 | (if Set.member "s" computer.keyboard.keys then
63 | identity
64 |
65 | else
66 | rotate3d (spin 5 computer.time) (spin 6 computer.time) (spin 7 computer.time)
67 | )
68 | <|
69 | cube darkPurple purple lightPurple 200
70 | , polygon3d black
71 | [ ( 0, 1070, 0 )
72 | , ( 1, 1070, 0 )
73 | , ( 1, 0, 0 )
74 | , ( 0, 0, 0 )
75 | ]
76 | , polygon3d black
77 | [ ( 0, 1, 0 )
78 | , ( 1070, 1, 0 )
79 | , ( 1070, 0, 0 )
80 | , ( 0, 0, 0 )
81 | ]
82 | , polygon3d black
83 | [ ( 0, 1, 0 )
84 | , ( 0, 1, 1070 )
85 | , ( 0, 0, 1070 )
86 | , ( 0, 0, 0 )
87 | ]
88 | , move3d 1100 0 0 <| words3d black "X"
89 | , move3d 0 1100 0 <| words3d black "Y"
90 | , move3d 0 0 1100 <| words3d black "Z"
91 | ]
92 | , moveDown (computer.screen.top - 30) <| words black <| "Code at https://github.com/lucamug/elm-playground-3d"
93 | , moveDown (computer.screen.top - 10) <| words black <| "D = bring cube Down, S = Stop the animation, Mouse ⬌ = rotate all, Mouse ⬍ = move cube vertically, Arrow keys = move cube horizontally"
94 | ]
95 |
96 |
97 | main : Program () (Game Memory) Msg
98 | main =
99 | game attributes view update init
100 |
--------------------------------------------------------------------------------
/src/Playground3d.elm:
--------------------------------------------------------------------------------
1 | module Playground3d exposing
2 | ( Point2d
3 | , Point3d
4 | , camera
5 | , camera1
6 | , camera2
7 | , camera3
8 | , camera4
9 | , cube
10 | , fade3d
11 | , group3d
12 | , move3d
13 | , polygon3d
14 | , rotate3d
15 | , scale3d
16 | , shape3dto2d
17 | , words3d
18 | )
19 |
20 | import Angle
21 | import Axis3d
22 | import Camera3d
23 | import Direction3d
24 | import Length
25 | import Playground
26 | import Point2d
27 | import Point3d
28 | import Point3d.Projection
29 | import Rectangle2d
30 | import Vector3d
31 | import Viewpoint3d
32 |
33 |
34 | type alias Triplet a =
35 | ( a, a, a )
36 |
37 |
38 | type alias Position =
39 | Triplet Playground.Number
40 |
41 |
42 | type alias Delta3d =
43 | Triplet Playground.Number
44 |
45 |
46 | type alias Fade =
47 | Playground.Number
48 |
49 |
50 | type alias Scale =
51 | Playground.Number
52 |
53 |
54 | type Shape3d
55 | = Shape3d Fade Form3d
56 |
57 |
58 | type Form3d
59 | = Polygon3d Playground.Color (List Position)
60 | | Words3d Playground.Color Position Scale String
61 | | Group3d (List Shape3d)
62 |
63 |
64 | polygon3d : Playground.Color -> List Position -> Shape3d
65 | polygon3d color points =
66 | Shape3d 1 (Polygon3d color points)
67 |
68 |
69 | words3d : Playground.Color -> String -> Shape3d
70 | words3d color string =
71 | Shape3d 1 (Words3d color ( 0, 0, 0 ) 1 string)
72 |
73 |
74 | group3d : List Shape3d -> Shape3d
75 | group3d shapes =
76 | Shape3d 1 (Group3d shapes)
77 |
78 |
79 | cube : Playground.Color -> Playground.Color -> Playground.Color -> Playground.Number -> Shape3d
80 | cube color1 color2 color3 size =
81 | scale3d size <|
82 | move3d -0.5 -0.5 -0.5 <|
83 | group3d
84 | [ polygon3d color1 [ ( 0, 0, 0 ), ( 1, 0, 0 ), ( 1, 1, 0 ), ( 0, 1, 0 ) ]
85 | , polygon3d color2 [ ( 0, 0, 0 ), ( 0, 1, 0 ), ( 0, 1, 1 ), ( 0, 0, 1 ) ]
86 | , polygon3d color3 [ ( 0, 0, 0 ), ( 1, 0, 0 ), ( 1, 0, 1 ), ( 0, 0, 1 ) ]
87 | , polygon3d color1 [ ( 0, 0, 1 ), ( 1, 0, 1 ), ( 1, 1, 1 ), ( 0, 1, 1 ) ]
88 | , polygon3d color2 [ ( 0, 1, 0 ), ( 1, 1, 0 ), ( 1, 1, 1 ), ( 0, 1, 1 ) ]
89 | , polygon3d color3 [ ( 1, 0, 0 ), ( 1, 1, 0 ), ( 1, 1, 1 ), ( 1, 0, 1 ) ]
90 | ]
91 |
92 |
93 | geometryMove : Delta3d -> Point3d a -> Point3d a
94 | geometryMove ( dx, dy, dz ) point3d =
95 | Point3d.translateBy (Vector3d.meters dx dy dz) point3d
96 |
97 |
98 | geometryScale : Delta3d -> Point3d a -> Point3d a
99 | geometryScale ( dx, _, _ ) point3d =
100 | Point3d.scaleAbout (Point3d.meters 0 0 0) dx point3d
101 |
102 |
103 | geometryRotate : Delta3d -> Point3d a -> Point3d a
104 | geometryRotate ( dx, dy, dz ) point3d =
105 | point3d
106 | |> Point3d.rotateAround Axis3d.x (Angle.degrees dx)
107 | |> Point3d.rotateAround Axis3d.y (Angle.degrees dy)
108 | |> Point3d.rotateAround Axis3d.z (Angle.degrees dz)
109 |
110 |
111 | tripletTransformation :
112 | (Delta3d -> Point3d a -> Point3d a)
113 | -> Float
114 | -> Float
115 | -> Float
116 | -> Position
117 | -> Position
118 | tripletTransformation geometryTransform dx dy dz ( px, py, pz ) =
119 | Point3d.toTuple Length.inMeters <|
120 | geometryTransform ( dx, dy, dz ) (Point3d.meters px py pz)
121 |
122 |
123 | move3d : Playground.Number -> Playground.Number -> Playground.Number -> Shape3d -> Shape3d
124 | move3d dx dy dz (Shape3d o f) =
125 | Shape3d o <|
126 | case f of
127 | Polygon3d color positions ->
128 | Polygon3d color <| List.map (tripletTransformation geometryMove dx dy dz) positions
129 |
130 | Words3d color position scale string ->
131 | Words3d color (tripletTransformation geometryMove dx dy dz position) scale string
132 |
133 | Group3d shapes ->
134 | Group3d <| List.map (move3d dx dy dz) shapes
135 |
136 |
137 | scale3d : Playground.Number -> Shape3d -> Shape3d
138 | scale3d dx (Shape3d o f) =
139 | Shape3d o <|
140 | case f of
141 | Polygon3d color position ->
142 | Polygon3d color <| List.map (tripletTransformation geometryScale dx dx dx) position
143 |
144 | Words3d color position scale string ->
145 | Words3d color position (scale * dx) string
146 |
147 | Group3d shapes ->
148 | Group3d <| List.map (\shape -> scale3d dx shape) shapes
149 |
150 |
151 | rotate3d : Playground.Number -> Playground.Number -> Playground.Number -> Shape3d -> Shape3d
152 | rotate3d dx dy dz (Shape3d o f) =
153 | Shape3d o <|
154 | case f of
155 | Polygon3d color positions ->
156 | Polygon3d color <| List.map (tripletTransformation geometryRotate dx dy dz) positions
157 |
158 | Words3d color position scale string ->
159 | Words3d color (tripletTransformation geometryRotate dx dy dz position) scale string
160 |
161 | Group3d shapes ->
162 | Group3d <| List.map (\shape -> rotate3d dx dy dz shape) shapes
163 |
164 |
165 | fade3d : Fade -> Shape3d -> Shape3d
166 | fade3d o (Shape3d _ f) =
167 | case f of
168 | Polygon3d _ _ ->
169 | Shape3d o f
170 |
171 | Words3d _ _ _ _ ->
172 | Shape3d o f
173 |
174 | Group3d shapes ->
175 | Shape3d o <| Group3d <| List.map (fade3d o) shapes
176 |
177 |
178 | type alias Point3d a =
179 | Point3d.Point3d Length.Meters a
180 |
181 |
182 | type alias Point2d a =
183 | Point2d.Point2d Length.Meters a
184 |
185 |
186 | point3dto2d : (Point3d a -> Maybe (Point2d a)) -> Position -> Maybe ( Float, Float )
187 | point3dto2d camera_ ( x, y, z ) =
188 | Point3d.meters x y z
189 | |> camera_
190 | |> Maybe.map (Point2d.toTuple Length.inMeters)
191 |
192 |
193 | removeMaybeType : Maybe a -> List a -> List a
194 | removeMaybeType item list =
195 | case item of
196 | Nothing ->
197 | list
198 |
199 | Just v ->
200 | v :: list
201 |
202 |
203 | shape3dto2d : (Point3d a -> Maybe (Point2d a)) -> Shape3d -> Playground.Shape
204 | shape3dto2d camera_ (Shape3d o f) =
205 | case f of
206 | Polygon3d color listPoints ->
207 | Playground.polygon color
208 | (listPoints
209 | |> List.map (point3dto2d camera_)
210 | |> List.foldr removeMaybeType []
211 | )
212 | |> Playground.fade o
213 |
214 | Words3d color position scale string ->
215 | let
216 | ( x, y ) =
217 | case point3dto2d camera_ position of
218 | Just point ->
219 | point
220 |
221 | Nothing ->
222 | ( 0, 0 )
223 | in
224 | Playground.words color string
225 | |> Playground.move x (0 - y)
226 | |> Playground.scale scale
227 | |> Playground.fade o
228 |
229 | Group3d shapes ->
230 | Playground.group <| List.map (shape3dto2d camera_) shapes
231 |
232 |
233 |
234 | -- ██████ █████ ███ ███ ███████ ██████ █████
235 | -- ██ ██ ██ ████ ████ ██ ██ ██ ██ ██
236 | -- ██ ███████ ██ ████ ██ █████ ██████ ███████
237 | -- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
238 | -- ██████ ██ ██ ██ ██ ███████ ██ ██ ██ ██
239 |
240 |
241 | type alias EyePoint =
242 | Triplet Float
243 |
244 |
245 | type alias FocalPoint =
246 | Triplet Float
247 |
248 |
249 | camera : EyePoint -> FocalPoint -> Point3d a -> Maybe (Point2d a)
250 | camera ( x, y, z ) ( fx, fy, fz ) point3d =
251 | let
252 | cameraViewpoint : Viewpoint3d.Viewpoint3d Length.Meters a
253 | cameraViewpoint =
254 | Viewpoint3d.lookAt
255 | { eyePoint = Point3d.meters x y z
256 | , focalPoint = Point3d.meters fx fy fz
257 | , upDirection = Direction3d.positiveZ
258 | }
259 |
260 | perspectiveCamera : Camera3d.Camera3d Length.Meters a
261 | perspectiveCamera =
262 | Camera3d.perspective
263 | { viewpoint = cameraViewpoint
264 | , verticalFieldOfView = Angle.degrees 25
265 | , clipDepth = Length.meters 1
266 | }
267 |
268 | rectOfView : Rectangle2d.Rectangle2d Length.Meters a
269 | rectOfView =
270 | Rectangle2d.with
271 | { x1 = Length.meters 0
272 | , y1 = Length.meters 600
273 | , x2 = Length.meters 1
274 | , y2 = Length.meters 0
275 | }
276 | in
277 | Point3d.Projection.toScreenSpace perspectiveCamera rectOfView point3d
278 |
279 |
280 | camera1 : Point3d a -> Maybe (Point2d a)
281 | camera1 =
282 | camera ( 3000, 2500, 1450 ) ( 0, 0, -1100 )
283 |
284 |
285 | camera2 : Point3d a -> Maybe (Point2d a)
286 | camera2 =
287 | camera ( 2300, 2300, 800 ) ( 0, 0, -450 )
288 |
289 |
290 | camera3 : Point3d a -> Maybe (Point2d a)
291 | camera3 =
292 | camera ( 2000, 2000, 1500 ) ( 0, 1.6, -908 )
293 |
294 |
295 | camera4 : Point3d a -> Maybe (Point2d a)
296 | camera4 =
297 | camera ( 50, 50, 50 ) ( -50, -50, -50 )
298 |
--------------------------------------------------------------------------------
/examples/Examples.elm:
--------------------------------------------------------------------------------
1 | module Examples exposing (main)
2 |
3 | import Browser
4 | import Element exposing (..)
5 | import Element.Background as Background
6 | import Element.Border as Border
7 | import Element.Font as Font
8 | import Html
9 | import Html.Attributes
10 | import Playground exposing (..)
11 | import Playground3d exposing (..)
12 | import Set
13 |
14 |
15 | fontXxxLarge : Int
16 | fontXxxLarge =
17 | 30
18 |
19 |
20 | attrsTitle : List (Attr () msg)
21 | attrsTitle =
22 | [ Font.size fontXxxLarge
23 | , Font.color colorFontTitle
24 | , Font.bold
25 | ]
26 |
27 |
28 | colorBackgroundCard : Element.Color
29 | colorBackgroundCard =
30 | Element.rgb 1 1 1
31 |
32 |
33 | colorFontTitle : Element.Color
34 | colorFontTitle =
35 | Element.rgb 0 0.4 0.5
36 |
37 |
38 | colorBorder : Element.Color
39 | colorBorder =
40 | Element.rgba 0 0 0 0.2
41 |
42 |
43 | colorFontNormal : Element.Color
44 | colorFontNormal =
45 | Element.rgba 0 0 0 0.8
46 |
47 |
48 | cardBase : List (Attribute msg)
49 | cardBase =
50 | [ padding 30
51 | , Background.color colorBackgroundCard
52 | , Border.rounded 10
53 | , Border.width 1
54 | , Border.color colorBorder
55 | , width fill
56 | , height fill
57 | , htmlAttribute <| Html.Attributes.style "transition" "all 0.2s"
58 | , Font.color colorFontNormal
59 | ]
60 |
61 |
62 | shadowHigh : Attr decorative msg
63 | shadowHigh =
64 | Border.shadow
65 | { color = rgba255 0 0 0 0.3
66 | , offset = ( 0, 10 )
67 | , blur = 10
68 | , size = 1
69 | }
70 |
71 |
72 | shadowLow : Attr decorative msg
73 | shadowLow =
74 | Border.shadow
75 | { color = rgba255 0 0 0 0.1
76 | , offset = ( 0, 0 )
77 | , blur = 3
78 | , size = 1
79 | }
80 |
81 |
82 | cardNormal : List (Attribute msg)
83 | cardNormal =
84 | cardBase
85 | ++ [ shadowLow, mouseOver [ shadowHigh ] ]
86 |
87 |
88 | paddingNormal : Int
89 | paddingNormal =
90 | 15
91 |
92 |
93 | spacingNormal : Int
94 | spacingNormal =
95 | 15
96 |
97 |
98 | view3d1 : (Playground.Msg -> msg) -> Playground.Game memory -> Element msg
99 | view3d1 msgGraph modelGraph =
100 | row []
101 | [ paragraph [ paddingEach { top = paddingNormal, right = 0, bottom = 0, left = paddingNormal } ] [ text "3D Example 1" ]
102 | , el
103 | (cardNormal
104 | ++ [ padding 0
105 | , width fill
106 | ]
107 | )
108 | <|
109 | html <|
110 | Html.map msgGraph <|
111 | Html.div [ Html.Attributes.style "cursor" "move" ]
112 | (.body <|
113 | let
114 | colorCube =
115 | Playground3d.cube (Playground.rgb 250 86 128) Playground.purple (Playground.rgb 290 126 168)
116 | in
117 | Playground.gameView
118 | (\computer a ->
119 | [ [ colorCube 300
120 | , colorCube 300 |> Playground3d.move3d 0 300 0
121 | , colorCube 300 |> Playground3d.move3d 0 300 300
122 | , colorCube 300 |> Playground3d.move3d 0 600 0
123 | , colorCube 300 |> Playground3d.move3d 0 600 300
124 | , colorCube 300 |> Playground3d.move3d 0 600 600
125 | ]
126 | |> Playground3d.group3d
127 | |> Playground3d.move3d 0 -300 -200
128 | |> (if computer.mouse.down then
129 | Playground3d.rotate3d computer.mouse.y computer.mouse.x 0
130 |
131 | else
132 | Playground3d.rotate3d 0 0 (Playground.spin 15 computer.time)
133 | )
134 | |> Playground3d.fade3d 0.5
135 | |> Playground3d.shape3dto2d Playground3d.camera1
136 | |> Playground.scale 1.3
137 | ]
138 | )
139 | modelGraph
140 | )
141 | ]
142 |
143 |
144 | view3d2 : (Playground.Msg -> msg) -> Playground.Game memory -> Element msg
145 | view3d2 msgGraph modelGraph =
146 | row []
147 | [ paragraph [ paddingEach { top = paddingNormal, right = 0, bottom = 0, left = paddingNormal } ] [ text "3D Example 2" ]
148 | , el (cardNormal ++ [ padding 0, width fill ]) <|
149 | html <|
150 | Html.map msgGraph <|
151 | Html.div [ Html.Attributes.style "cursor" "move" ]
152 | (.body <|
153 | let
154 | colorCube =
155 | Playground3d.cube Playground.darkGreen Playground.green Playground.yellow
156 | in
157 | Playground.gameView
158 | (\computer a ->
159 | [ colorCube 300
160 | |> Playground3d.scale3d 2
161 | |> Playground3d.rotate3d 0 0 computer.mouse.x
162 | |> Playground3d.fade3d 0.5
163 | |> Playground3d.shape3dto2d Playground3d.camera1
164 | ]
165 | )
166 | modelGraph
167 | )
168 | ]
169 |
170 |
171 | view3d3 : (Playground.Msg -> msg) -> Playground.Game memory -> Element msg
172 | view3d3 msgGraph modelGraph =
173 | row []
174 | [ paragraph [ paddingEach { top = paddingNormal, right = 0, bottom = 0, left = paddingNormal } ] [ text "3D Example 2" ]
175 | , el (cardNormal ++ [ padding 0, width fill ]) <|
176 | html <|
177 | Html.map msgGraph <|
178 | Html.div [ Html.Attributes.style "cursor" "move" ]
179 | (.body <|
180 | let
181 | colorCube =
182 | Playground3d.cube Playground.darkGreen Playground.green Playground.yellow
183 | in
184 | Playground.gameView
185 | (\computer a ->
186 | [ Playground.square Playground.green 200
187 | |> Playground.rotate (spin 4 computer.time)
188 | ]
189 | )
190 | modelGraph
191 | )
192 | ]
193 |
194 |
195 | type alias Model =
196 | { graph1 : Playground.Game ()
197 | , graph2 : Playground.Game ()
198 | , graph3 : Playground.Game ()
199 | , isFocused : Bool
200 | , isVisible : Bool
201 | , isMoving : Bool
202 | }
203 |
204 |
205 | type Msg
206 | = Graph1 Playground.Msg
207 | | Graph2 Playground.Msg
208 | | Graph3 Playground.Msg
209 |
210 |
211 | main : Program () Model Msg
212 | main =
213 | Browser.element
214 | { init = init
215 | , update = update
216 | , subscriptions = subscriptions
217 | , view = view
218 | }
219 |
220 |
221 | mode : Attributes
222 | mode =
223 | fixedScreen ( 600, 600 )
224 |
225 |
226 | init : () -> ( Model, Cmd Msg )
227 | init _ =
228 | let
229 | ( model, cmd ) =
230 | Playground.gameInit [ mode ] () ()
231 | in
232 | ( { graph1 = model
233 | , graph2 = model
234 | , graph3 = model
235 | , isFocused = True
236 | , isVisible = True
237 | , isMoving = True
238 | }
239 | , Cmd.batch
240 | [ Cmd.map Graph1 cmd
241 | , Cmd.map Graph2 cmd
242 | , Cmd.map Graph3 cmd
243 | ]
244 | )
245 |
246 |
247 | update : Msg -> Model -> ( Model, Cmd Msg )
248 | update msg model =
249 | case msg of
250 | Graph1 msgGraph ->
251 | let
252 | updateMemory computer memory =
253 | memory
254 |
255 | ( graphModel, graphCmd ) =
256 | Playground.gameUpdate updateMemory msgGraph model.graph1
257 | in
258 | ( { model | graph1 = graphModel }, graphCmd )
259 |
260 | Graph2 msgGraph ->
261 | let
262 | updateMemory computer memory =
263 | memory
264 |
265 | ( graphModel, graphCmd ) =
266 | Playground.gameUpdate updateMemory msgGraph model.graph2
267 | in
268 | ( { model | graph2 = graphModel }, graphCmd )
269 |
270 | Graph3 msgGraph ->
271 | let
272 | updateMemory computer memory =
273 | memory
274 |
275 | ( graphModel, graphCmd ) =
276 | Playground.gameUpdate updateMemory msgGraph model.graph3
277 | in
278 | ( { model | graph3 = graphModel }, graphCmd )
279 |
280 |
281 | subscriptions : Model -> Sub Msg
282 | subscriptions model =
283 | if model.isFocused && model.isVisible && model.isMoving then
284 | Sub.batch
285 | [ Sub.map Graph1 <| Playground.gameSubscriptions [ mode ] model.graph1
286 | , Sub.map Graph2 <| Playground.gameSubscriptions [ mode ] model.graph2
287 | , Sub.map Graph3 <| Playground.gameSubscriptions [ mode ] model.graph3
288 | ]
289 |
290 | else
291 | Sub.none
292 |
293 |
294 | twoD : String -> List Shape -> Element msg
295 | twoD code shapes =
296 | row [ width <| px 400 ]
297 | [ paragraph [] [ text code ]
298 | , el [ alignRight, width <| px 200 ] <|
299 | html <|
300 | Html.div [] <|
301 | .body
302 | (pictureView
303 | [ group
304 | [ circle gray 4
305 | , circle white 3.8
306 | , rectangle gray 10 0.2 |> Playground.rotate 90 |> move -5 0
307 | , rectangle gray 10 0.1 |> Playground.rotate 90 |> move -4 0
308 | , rectangle gray 10 0.1 |> Playground.rotate 90 |> move -3 0
309 | , rectangle gray 10 0.1 |> Playground.rotate 90 |> move -2 0
310 | , rectangle gray 10 0.1 |> Playground.rotate 90 |> move -1 0
311 | , rectangle gray 10 0.2 |> Playground.rotate 90
312 | , rectangle gray 10 0.1 |> Playground.rotate 90 |> move 1 0
313 | , rectangle gray 10 0.1 |> Playground.rotate 90 |> move 2 0
314 | , rectangle gray 10 0.1 |> Playground.rotate 90 |> move 3 0
315 | , rectangle gray 10 0.1 |> Playground.rotate 90 |> move 4 0
316 | , rectangle gray 10 0.2 |> Playground.rotate 90 |> move 5 0
317 | , rectangle gray 10 0.2 |> move 0 -5
318 | , rectangle gray 10 0.1 |> move 0 -4
319 | , rectangle gray 10 0.1 |> move 0 -3
320 | , rectangle gray 10 0.1 |> move 0 -2
321 | , rectangle gray 10 0.1 |> move 0 -1
322 | , rectangle gray 10 0.2
323 | , rectangle gray 10 0.1 |> move 0 1
324 | , rectangle gray 10 0.1 |> move 0 2
325 | , rectangle gray 10 0.1 |> move 0 3
326 | , rectangle gray 10 0.1 |> move 0 4
327 | , rectangle gray 10 0.2 |> move 0 5
328 | , words gray "-5,-5" |> Playground.scale 0.06 |> move -4.5 -5.8
329 | , words gray "-5, 5" |> Playground.scale 0.06 |> move -4.5 5.8
330 | , words gray " 5, 5" |> Playground.scale 0.06 |> move 4.5 5.8
331 | , words gray " 5,-5" |> Playground.scale 0.06 |> move 4.5 -5.8
332 | ]
333 | , group shapes |> fade 0.7
334 | ]
335 | (toScreen 14 14)
336 | )
337 | ]
338 |
339 |
340 |
341 | -- TODO
342 | --
343 | -- Need to convert the grid into 3D so I can put object in 3D
344 | -- Create Rectangle3d
345 |
346 |
347 | grid =
348 | [ circle gray 4
349 | , circle white 3.8
350 | , rectangle gray 10 0.2 |> Playground.rotate 90 |> move -5 0
351 | , rectangle gray 10 0.1 |> Playground.rotate 90 |> move -4 0
352 | , rectangle gray 10 0.1 |> Playground.rotate 90 |> move -3 0
353 | , rectangle gray 10 0.1 |> Playground.rotate 90 |> move -2 0
354 | , rectangle gray 10 0.1 |> Playground.rotate 90 |> move -1 0
355 | , rectangle gray 10 0.2 |> Playground.rotate 90
356 | , rectangle gray 10 0.1 |> Playground.rotate 90 |> move 1 0
357 | , rectangle gray 10 0.1 |> Playground.rotate 90 |> move 2 0
358 | , rectangle gray 10 0.1 |> Playground.rotate 90 |> move 3 0
359 | , rectangle gray 10 0.1 |> Playground.rotate 90 |> move 4 0
360 | , rectangle gray 10 0.2 |> Playground.rotate 90 |> move 5 0
361 | , rectangle gray 10 0.2 |> move 0 -5
362 | , rectangle gray 10 0.1 |> move 0 -4
363 | , rectangle gray 10 0.1 |> move 0 -3
364 | , rectangle gray 10 0.1 |> move 0 -2
365 | , rectangle gray 10 0.1 |> move 0 -1
366 | , rectangle gray 10 0.2
367 | , rectangle gray 10 0.1 |> move 0 1
368 | , rectangle gray 10 0.1 |> move 0 2
369 | , rectangle gray 10 0.1 |> move 0 3
370 | , rectangle gray 10 0.1 |> move 0 4
371 | , rectangle gray 10 0.2 |> move 0 5
372 | , words gray "-5,-5" |> Playground.scale 0.06 |> move -4.5 -5.8
373 | , words gray "-5, 5" |> Playground.scale 0.06 |> move -4.5 5.8
374 | , words gray " 5, 5" |> Playground.scale 0.06 |> move 4.5 5.8
375 | , words gray " 5,-5" |> Playground.scale 0.06 |> move 4.5 -5.8
376 | ]
377 |
378 |
379 | threeD : String -> List Shape -> Element msg
380 | threeD code shapes =
381 | row [ width <| px 400 ]
382 | [ paragraph [] [ text code ]
383 | , el [ alignRight, width <| px 200 ] <|
384 | html <|
385 | Html.div [] <|
386 | .body
387 | (pictureView
388 | [ group grid
389 | , group shapes |> fade 0.7
390 | ]
391 | (toScreen 14 14)
392 | )
393 | ]
394 |
395 |
396 | view : Model -> Html.Html Msg
397 | view model =
398 | let
399 | contentShape3d1 =
400 | view3d1 Graph1 model.graph1
401 |
402 | contentShape3d2 =
403 | view3d2 Graph2 model.graph2
404 |
405 | contentShape3d3 =
406 | view3d3 Graph3 model.graph3
407 | in
408 | layout [ padding paddingNormal ] <|
409 | column [ spacing spacingNormal, width fill ]
410 | [ el attrsTitle <| text "Example"
411 | , column [ spacing spacingNormal, width fill ]
412 | [ twoD "circle blue 4" [ circle blue 4 ]
413 | , twoD "oval blue 8 6" [ oval blue 8 6 ]
414 | , twoD "square blue 8" [ square blue 8 ]
415 | , twoD "rectangle blue 8 6" [ rectangle blue 8 6 ]
416 | , twoD "triangle blue 4" [ triangle blue 4 ]
417 | , twoD "pentagon blue 4" [ pentagon blue 4 ]
418 | , twoD "hexagon blue 4" [ hexagon blue 4 ]
419 | , twoD "octagon blue 4" [ octagon blue 4 ]
420 | , twoD "polygon blue [ (-4, 0), (-1, -1), (0, -4), (1, -1), (4, 0), (1, 1), (0, 4), (-1, 1) ]"
421 | [ polygon blue
422 | [ ( -4, 0 )
423 | , ( -1, -1 )
424 | , ( 0, -4 )
425 | , ( 1, -1 )
426 | , ( 4, 0 )
427 | , ( 1, 1 )
428 | , ( 0, 4 )
429 | , ( -1, 1 )
430 | ]
431 | ]
432 | , twoD "words \"test\"" [ words blue "test" |> Playground.scale 0.3 ]
433 | , threeD "3D test" [ cube lightBlue blue darkBlue 8 |> shape3dto2d camera3 ]
434 | , threeD "3D test" <|
435 | let
436 | size =
437 | 40
438 | in
439 | [ [ polygon3d darkGray [ ( 0, 0, 0 ), ( size, 0, 0 ), ( size, 0, size ), ( 0, 0, size ) ]
440 |
441 | -- , polygon3d gray [ ( 0, 0, 0 ), ( 0, size, 0 ), ( 0, size, size ), ( 0, 0, size ) ]
442 | -- , polygon3d lightGray [ ( 0, 0, 0 ), ( size, 0, 0 ), ( size, size, 0 ), ( 0, size, 0 ) ]
443 | ]
444 | |> group3d
445 | |> shape3dto2d camera3
446 | ]
447 | , contentShape3d1
448 | , contentShape3d2
449 | , contentShape3d3
450 | ]
451 | ]
452 |
--------------------------------------------------------------------------------
/src-elm-playground/Playground.elm:
--------------------------------------------------------------------------------
1 | module Playground exposing
2 | ( picture, animation, game
3 | , Shape, circle, oval, square, rectangle, triangle, pentagon, hexagon, octagon, polygon
4 | , words
5 | , image
6 | , move, moveUp, moveDown, moveLeft, moveRight, moveX, moveY
7 | , scale, rotate, fade
8 | , group
9 | , Time, spin, wave, zigzag
10 | , Computer, Mouse, Screen, Keyboard, toX, toY, toXY
11 | , Color, rgb, red, orange, yellow, green, blue, purple, brown
12 | , lightRed, lightOrange, lightYellow, lightGreen, lightBlue, lightPurple, lightBrown
13 | , darkRed, darkOrange, darkYellow, darkGreen, darkBlue, darkPurple, darkBrown
14 | , white, lightGrey, grey, darkGrey, lightCharcoal, charcoal, darkCharcoal, black
15 | , lightGray, gray, darkGray
16 | , Number
17 | , Animation, Attributes, Game, Msg, PictureMsg, changeMemory, fixedScreen, fullScreen, gameInit, gameSubscriptions, gameUpdate, gameView, getMemory, pictureInit, pictureSubscriptions, pictureUpdate, pictureView, toScreen
18 | )
19 |
20 | {-|
21 |
22 |
23 | # Playgrounds
24 |
25 | @docs picture, animation, game
26 |
27 |
28 | # Shapes
29 |
30 | @docs Shape, circle, oval, square, rectangle, triangle, pentagon, hexagon, octagon, polygon
31 |
32 |
33 | # Words
34 |
35 | @docs words
36 |
37 |
38 | # Images
39 |
40 | @docs image
41 |
42 |
43 | # Move Shapes
44 |
45 | @docs move, moveUp, moveDown, moveLeft, moveRight, moveX, moveY
46 |
47 |
48 | # Customize Shapes
49 |
50 | @docs scale, rotate, fade
51 |
52 |
53 | # Groups
54 |
55 | @docs group
56 |
57 |
58 | # Time
59 |
60 | @docs Time, spin, wave, zigzag
61 |
62 |
63 | # Computer
64 |
65 | @docs Computer, Mouse, Screen, Keyboard, toX, toY, toXY
66 |
67 |
68 | # Colors
69 |
70 | @docs Color, rgb, red, orange, yellow, green, blue, purple, brown
71 |
72 |
73 | ### Light Colors
74 |
75 | @docs lightRed, lightOrange, lightYellow, lightGreen, lightBlue, lightPurple, lightBrown
76 |
77 |
78 | ### Dark Colors
79 |
80 | @docs darkRed, darkOrange, darkYellow, darkGreen, darkBlue, darkPurple, darkBrown
81 |
82 |
83 | ### Shades of Grey
84 |
85 | @docs white, lightGrey, grey, darkGrey, lightCharcoal, charcoal, darkCharcoal, black
86 |
87 |
88 | ### Alternate Spellings of Gray
89 |
90 | @docs lightGray, gray, darkGray
91 |
92 |
93 | ### Numbers
94 |
95 | @docs Number
96 |
97 | -}
98 |
99 | import Browser
100 | import Browser.Dom as Dom
101 | import Browser.Events as E
102 | import Html
103 | import Html.Attributes
104 | import Json.Decode as D
105 | import Set
106 | import Svg exposing (..)
107 | import Svg.Attributes exposing (..)
108 | import Svg.Events
109 | import Task
110 | import Time
111 |
112 |
113 |
114 | -- PICTURE
115 |
116 |
117 | {-| Make a picture! Here is a picture of a triangle with an eyeball:
118 |
119 | import Playground exposing (..)
120 |
121 | main =
122 | picture
123 | [ triangle green 150
124 | , circle white 40
125 | , circle black 10
126 | ]
127 |
128 | -}
129 | pictureView : List Shape -> Screen -> Browser.Document msg
130 | pictureView shapes screen =
131 | { title = "Playground"
132 | , body =
133 | [ render
134 | { screen = screen
135 | , maybeMsg = Nothing
136 | , shapes = shapes
137 | }
138 | ]
139 | }
140 |
141 |
142 | pictureInit : List Attributes -> () -> ( Screen, Cmd PictureMsg )
143 | pictureInit attributes () =
144 | case attributeSvgType attributes of
145 | FixedScreen ( x, y ) ->
146 | ( toScreen x y
147 | , Cmd.none
148 | )
149 |
150 | _ ->
151 | ( initScreen
152 | , Task.perform PictureMsgGotViewport Dom.getViewport
153 | )
154 |
155 |
156 | pictureUpdate : PictureMsg -> a -> ( Screen, Cmd msg )
157 | pictureUpdate msg _ =
158 | case msg of
159 | PictureMsgResized width height ->
160 | ( toScreen (toFloat width) (toFloat height)
161 | , Cmd.none
162 | )
163 |
164 | PictureMsgGotViewport { viewport } ->
165 | ( toScreen viewport.width viewport.height
166 | , Cmd.none
167 | )
168 |
169 |
170 | pictureSubscriptions : List Attributes -> a -> Sub PictureMsg
171 | pictureSubscriptions attributes _ =
172 | case attributeSvgType attributes of
173 | FullScreen ->
174 | E.onResize PictureMsgResized
175 |
176 | _ ->
177 | Sub.none
178 |
179 |
180 | type PictureMsg
181 | = PictureMsgResized Int Int
182 | | PictureMsgGotViewport Dom.Viewport
183 |
184 |
185 | type Attributes
186 | = FixedScreen ( Float, Float )
187 | | FullScreen -- Default value
188 |
189 |
190 | attributeSvgType : List Attributes -> Attributes
191 | attributeSvgType attributes =
192 | List.foldl
193 | (\attribute acc ->
194 | case attribute of
195 | FixedScreen _ ->
196 | attribute
197 |
198 | _ ->
199 | acc
200 | )
201 | FullScreen
202 | attributes
203 |
204 |
205 | fixedScreen : ( Float, Float ) -> Attributes
206 | fixedScreen =
207 | FixedScreen
208 |
209 |
210 | fullScreen : Attributes
211 | fullScreen =
212 | FullScreen
213 |
214 |
215 | picture : List Attributes -> List Shape -> Program () Screen PictureMsg
216 | picture attributes shapes =
217 | Browser.document
218 | { init = pictureInit attributes
219 | , view = pictureView shapes
220 | , update = pictureUpdate
221 | , subscriptions = pictureSubscriptions attributes
222 | }
223 |
224 |
225 |
226 | -- COMPUTER
227 |
228 |
229 | {-| When writing a [`game`](#game), you can look up all sorts of information
230 | about your computer:
231 |
232 | - [`Mouse`](#Mouse) - Where is the mouse right now?
233 | - [`Keyboard`](#Keyboard) - Are the arrow keys down?
234 | - [`Screen`](#Screen) - How wide is the screen?
235 | - [`Time`](#Time) - What time is it right now?
236 |
237 | So you can use expressions like `computer.mouse.x` and `computer.keyboard.enter`
238 | in games where you want some mouse or keyboard interaction.
239 |
240 | -}
241 | type alias Computer =
242 | { mouse : Mouse
243 | , keyboard : Keyboard
244 | , screen : Screen
245 | , time : Time
246 | }
247 |
248 |
249 |
250 | -- MOUSE
251 |
252 |
253 | {-| Figure out what is going on with the mouse.
254 |
255 | You could draw a circle around the mouse with a program like this:
256 |
257 | import Playground exposing (..)
258 |
259 | main =
260 | game view update 0
261 |
262 | view computer memory =
263 | [ circle yellow 40
264 | |> moveX computer.mouse.x
265 | |> moveY computer.mouse.y
266 | ]
267 |
268 | update computer memory =
269 | memory
270 |
271 | You could also use `computer.mouse.down` to change the color of the circle
272 | while the mouse button is down.
273 |
274 | -}
275 | type alias Mouse =
276 | { x : Number
277 | , y : Number
278 | , down : Bool
279 | , click : Bool
280 | , over : Bool
281 | }
282 |
283 |
284 | {-| A number like `1` or `3.14` or `-120`.
285 | -}
286 | type alias Number =
287 | Float
288 |
289 |
290 |
291 | -- KEYBOARD
292 |
293 |
294 | {-| Figure out what is going on with the keyboard.
295 |
296 | If someone is pressing the UP and RIGHT arrows, you will see a value like this:
297 |
298 | { up = True
299 | , down = False
300 | , left = False
301 | , right = True
302 | , space = False
303 | , enter = False
304 | , shift = False
305 | , backspace = False
306 | , keys = Set.fromList [ "ArrowUp", "ArrowRight" ]
307 | }
308 |
309 | So if you want to move a character based on arrows, you could write an update
310 | like this:
311 |
312 | update computer y =
313 | if computer.keyboard.up then
314 | y + 1
315 |
316 | else
317 | y
318 |
319 | Check out [`toX`](#toX) and [`toY`](#toY) which make this even easier!
320 |
321 | **Note:** The `keys` set will be filled with the name of all keys which are
322 | down right now. So you will see things like `"a"`, `"b"`, `"c"`, `"1"`, `"2"`,
323 | `"Space"`, and `"Control"` in there. Check out [this list][list] to see the
324 | names used for all the different special keys! From there, you can use
325 | [`Set.member`][member] to check for whichever key you want. E.g.
326 | `Set.member "Control" computer.keyboard.keys`.
327 |
328 | [list]: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
329 | [member]: /packages/elm/core/latest/Set#member
330 |
331 | -}
332 | type alias Keyboard =
333 | { up : Bool
334 | , down : Bool
335 | , left : Bool
336 | , right : Bool
337 | , space : Bool
338 | , enter : Bool
339 | , shift : Bool
340 | , backspace : Bool
341 | , keys : Set.Set String
342 | }
343 |
344 |
345 | {-| Turn the LEFT and RIGHT arrows into a number.
346 |
347 | toX { left = False, right = False, ... } == 0
348 | toX { left = True , right = False, ... } == -1
349 | toX { left = False, right = True , ... } == 1
350 | toX { left = True , right = True , ... } == 0
351 |
352 | So to make a square move left and right based on the arrow keys, we could say:
353 |
354 | import Playground exposing (..)
355 |
356 | main =
357 | game view update 0
358 |
359 | view computer x =
360 | [ square green 40
361 | |> moveX x
362 | ]
363 |
364 | update computer x =
365 | x + toX computer.keyboard
366 |
367 | -}
368 | toX : Keyboard -> Number
369 | toX keyboard =
370 | (if keyboard.right then
371 | 1
372 |
373 | else
374 | 0
375 | )
376 | - (if keyboard.left then
377 | 1
378 |
379 | else
380 | 0
381 | )
382 |
383 |
384 | {-| Turn the UP and DOWN arrows into a number.
385 |
386 | toY { up = False, down = False, ... } == 0
387 | toY { up = True , down = False, ... } == 1
388 | toY { up = False, down = True , ... } == -1
389 | toY { up = True , down = True , ... } == 0
390 |
391 | This can be used to move characters around in games just like [`toX`](#toX):
392 |
393 | import Playground exposing (..)
394 |
395 | main =
396 | game view update ( 0, 0 )
397 |
398 | view computer ( x, y ) =
399 | [ square blue 40
400 | |> move x y
401 | ]
402 |
403 | update computer ( x, y ) =
404 | ( x + toX computer.keyboard
405 | , y + toY computer.keyboard
406 | )
407 |
408 | -}
409 | toY : Keyboard -> Number
410 | toY keyboard =
411 | (if keyboard.up then
412 | 1
413 |
414 | else
415 | 0
416 | )
417 | - (if keyboard.down then
418 | 1
419 |
420 | else
421 | 0
422 | )
423 |
424 |
425 | {-| If you just use `toX` and `toY`, you will move diagonal too fast. You will go
426 | right at 1 pixel per update, but you will go up/right at 1.41421 pixels per
427 | update.
428 |
429 | So `toXY` turns the arrow keys into an `(x,y)` pair such that the distance is
430 | normalized:
431 |
432 | toXY { up = True , down = False, left = False, right = False, ... } == (1, 0)
433 | toXY { up = True , down = False, left = False, right = True , ... } == (0.707, 0.707)
434 | toXY { up = False, down = False, left = False, right = True , ... } == (0, 1)
435 |
436 | Now when you go up/right, you are still going 1 pixel per update.
437 |
438 | import Playground exposing (..)
439 |
440 | main =
441 | game view update ( 0, 0 )
442 |
443 | view computer ( x, y ) =
444 | [ square green 40
445 | |> move x y
446 | ]
447 |
448 | update computer ( x, y ) =
449 | let
450 | ( dx, dy ) =
451 | toXY computer.keyboard
452 | in
453 | ( x + dx, y + dy )
454 |
455 | -}
456 | toXY : Keyboard -> ( Number, Number )
457 | toXY keyboard =
458 | let
459 | x =
460 | toX keyboard
461 |
462 | y =
463 | toY keyboard
464 | in
465 | if x /= 0 && y /= 0 then
466 | ( x / squareRootOfTwo, y / squareRootOfTwo )
467 |
468 | else
469 | ( x, y )
470 |
471 |
472 | squareRootOfTwo : Number
473 | squareRootOfTwo =
474 | sqrt 2
475 |
476 |
477 |
478 | -- SCREEN
479 |
480 |
481 | {-| Get the dimensions of the screen. If the screen is 800 by 600, you will see
482 | a value like this:
483 |
484 | { width = 800
485 | , height = 600
486 | , top = 300
487 | , left = -400
488 | , right = 400
489 | , bottom = -300
490 | }
491 |
492 | This can be nice when used with [`moveY`](#moveY) if you want to put something
493 | on the bottom of the screen, no matter the dimensions.
494 |
495 | -}
496 | type alias Screen =
497 | { width : Number
498 | , height : Number
499 | , top : Number
500 | , left : Number
501 | , right : Number
502 | , bottom : Number
503 | }
504 |
505 |
506 |
507 | -- TIME
508 |
509 |
510 | {-| The current time.
511 |
512 | Helpful when making an [`animation`](#animation) with functions like
513 | [`spin`](#spin), [`wave`](#wave), and [`zigzag`](#zigzag).
514 |
515 | -}
516 | type Time
517 | = Time Time.Posix
518 |
519 |
520 | {-| Create an angle that cycles from 0 to 360 degrees over time.
521 |
522 | Here is an [`animation`](#animation) with a spinning triangle:
523 |
524 | import Playground exposing (..)
525 |
526 | main =
527 | animation view
528 |
529 | view time =
530 | [ triangle orange 50
531 | |> rotate (spin 8 time)
532 | ]
533 |
534 | It will do a full rotation once every eight seconds. Try changing the `8` to
535 | a `2` to make it do a full rotation every two seconds. It moves a lot faster!
536 |
537 | -}
538 | spin : Number -> Time -> Number
539 | spin period time =
540 | 360 * toFrac period time
541 |
542 |
543 | {-| Smoothly wave between two numbers.
544 |
545 | Here is an [`animation`](#animation) with a circle that resizes:
546 |
547 | import Playground exposing (..)
548 |
549 | main =
550 | animation view
551 |
552 | view time =
553 | [ circle lightBlue (wave 50 90 7 time)
554 | ]
555 |
556 | The radius of the circle will cycles between 50 and 90 every seven seconds.
557 | It kind of looks like it is breathing.
558 |
559 | -}
560 | wave : Number -> Number -> Number -> Time -> Number
561 | wave lo hi period time =
562 | lo + (hi - lo) * (1 + cos (turns (toFrac period time))) / 2
563 |
564 |
565 | {-| Zig zag between two numbers.
566 |
567 | Here is an [`animation`](#animation) with a rectangle that tips back and forth:
568 |
569 | import Playground exposing (..)
570 |
571 | main =
572 | animation view
573 |
574 | view time =
575 | [ rectangle lightGreen 20 100
576 | |> rotate (zigzag -20 20 4 time)
577 | ]
578 |
579 | It gets rotated by an angle. The angle cycles from -20 degrees to 20 degrees
580 | every four seconds.
581 |
582 | -}
583 | zigzag : Number -> Number -> Number -> Time -> Number
584 | zigzag lo hi period time =
585 | lo + (hi - lo) * abs (2 * toFrac period time - 1)
586 |
587 |
588 | toFrac : Float -> Time -> Float
589 | toFrac period (Time posix) =
590 | let
591 | ms =
592 | Time.posixToMillis posix
593 |
594 | p =
595 | period * 1000
596 | in
597 | toFloat (modBy (round p) ms) / p
598 |
599 |
600 |
601 | -- ANIMATION
602 |
603 |
604 | {-| Create an animation!
605 |
606 | Once you get comfortable using [`picture`](#picture) to layout shapes, you can
607 | try out an `animation`. Here is square that zigzags back and forth:
608 |
609 | import Playground exposing (..)
610 |
611 | main =
612 | animation view
613 |
614 | view time =
615 | [ square blue 40
616 | |> moveX (zigzag -100 100 2 time)
617 | ]
618 |
619 | We need to define a `view` to make our animation work.
620 |
621 | Within `view` we can use functions like [`spin`](#spin), [`wave`](#wave),
622 | and [`zigzag`](#zigzag) to move and rotate our shapes.
623 |
624 | -}
625 | animation : List Attributes -> (Time -> List Shape) -> Program () Animation Msg
626 | animation attributes viewFrame =
627 | Browser.document
628 | { init = animationInit attributes
629 | , view = animationView viewFrame
630 | , update = animationUpdate
631 | , subscriptions = animationSubscriptions attributes
632 | }
633 |
634 |
635 | type Animation
636 | = Animation E.Visibility Screen Time
637 |
638 |
639 | animationView :
640 | (Time -> List Shape)
641 | -> Animation
642 | -> { body : List (Html.Html msg), title : String }
643 | animationView viewFrame (Animation _ screen time) =
644 | { title = "Playground"
645 | , body =
646 | [ render
647 | { screen = screen
648 | , maybeMsg = Nothing
649 | , shapes = viewFrame time
650 | }
651 | ]
652 | }
653 |
654 |
655 | animationInit : List Attributes -> () -> ( Animation, Cmd Msg )
656 | animationInit attributes () =
657 | case attributeSvgType attributes of
658 | FixedScreen ( x, y ) ->
659 | ( Animation E.Visible (toScreen x y) (Time (Time.millisToPosix 0))
660 | , Cmd.none
661 | )
662 |
663 | _ ->
664 | ( Animation E.Visible initScreen (Time (Time.millisToPosix 0))
665 | , Task.perform GotViewport Dom.getViewport
666 | )
667 |
668 |
669 | animationUpdate : Msg -> Animation -> ( Animation, Cmd msg )
670 | animationUpdate msg ((Animation v s t) as state) =
671 | ( case msg of
672 | Tick posix ->
673 | Animation v s (Time posix)
674 |
675 | VisibilityChanged vis ->
676 | Animation vis s t
677 |
678 | GotViewport { viewport } ->
679 | Animation v (toScreen viewport.width viewport.height) t
680 |
681 | Resized w h ->
682 | Animation v (toScreen (toFloat w) (toFloat h)) t
683 |
684 | KeyChanged _ _ ->
685 | state
686 |
687 | MouseMove _ _ ->
688 | state
689 |
690 | MouseButtonUp ->
691 | state
692 |
693 | SvgMouseClick ->
694 | state
695 |
696 | SvgMouseButtonDown ->
697 | state
698 |
699 | SvgMouseOver ->
700 | state
701 |
702 | SvgMouseOut ->
703 | state
704 | , Cmd.none
705 | )
706 |
707 |
708 | initScreen : Screen
709 | initScreen =
710 | toScreen 600 600
711 |
712 |
713 | animationSubscriptions : List Attributes -> Animation -> Sub Msg
714 | animationSubscriptions attributes (Animation visibility _ _) =
715 | let
716 | fullScreenMode =
717 | attributeSvgType attributes == FullScreen
718 | in
719 | --
720 | -- TODO Need to detect mouse over and then start onAnimationFrame subscription
721 | --
722 | -- The problem is that animation doesn't have info about the mouse
723 | -- so for the moment we keep the animation always active, ignoring
724 | -- the attributes directives
725 | --
726 | case visibility of
727 | E.Hidden ->
728 | E.onVisibilityChange VisibilityChanged
729 |
730 | E.Visible ->
731 | Sub.batch
732 | [ E.onResize Resized
733 | , E.onAnimationFrame Tick
734 | , E.onVisibilityChange VisibilityChanged
735 | ]
736 |
737 |
738 |
739 | -- GAME
740 |
741 |
742 | {-| Create a game!
743 |
744 | Once you get comfortable with [`animation`](#animation), you can try making a
745 | game with the keyboard and mouse. Here is an example of a green square that
746 | just moves to the right:
747 |
748 | import Playground exposing (..)
749 |
750 | main =
751 | game view update 0
752 |
753 | view computer offset =
754 | [ square green 40
755 | |> moveRight offset
756 | ]
757 |
758 | update computer offset =
759 | offset + 0.03
760 |
761 | This shows the three important parts of a game:
762 |
763 | 1. `memory` - makes it possible to store information. So with our green square,
764 | we save the `offset` in memory. It starts out at `0`.
765 | 2. `view` - lets us say which shapes to put on screen. So here we move our
766 | square right by the `offset` saved in memory.
767 | 3. `update` - lets us update the memory. We are incrementing the `offset` by
768 | a tiny amount on each frame.
769 |
770 | The `update` function is called about 60 times per second, so our little
771 | changes to `offset` start to add up pretty quickly!
772 |
773 | This game is not very fun though! Making a `game` also gives you access to the
774 | [`Computer`](#Computer), so you can use information about the [`Mouse`](#Mouse)
775 | and [`Keyboard`](#Keyboard) to make it interactive! So here is a red square that
776 | moves based on the arrow keys:
777 |
778 | import Playground exposing (..)
779 |
780 | main =
781 | game view update ( 0, 0 )
782 |
783 | view computer ( x, y ) =
784 | [ square red 40
785 | |> move x y
786 | ]
787 |
788 | update computer ( x, y ) =
789 | ( x + toX computer.keyboard
790 | , y + toY computer.keyboard
791 | )
792 |
793 | Notice that in the `update` we use information from the keyboard to update the
794 | `x` and `y` values. These building blocks let you make pretty fancy games!
795 |
796 | -}
797 | game : List Attributes -> (Computer -> memory -> List Shape) -> (Computer -> memory -> memory) -> memory -> Program () (Game memory) Msg
798 | game attributes viewMemory updateMemory initialMemory =
799 | Browser.document
800 | { init = gameInit attributes initialMemory
801 | , view = gameView viewMemory
802 | , update = gameUpdate updateMemory
803 | , subscriptions = gameSubscriptions attributes
804 | }
805 |
806 |
807 | type Game memory
808 | = Game E.Visibility memory Computer
809 |
810 |
811 | gameInit : List Attributes -> memory -> () -> ( Game memory, Cmd Msg )
812 | gameInit attributes initialMemory _ =
813 | case attributeSvgType attributes of
814 | FixedScreen ( x, y ) ->
815 | ( Game E.Visible
816 | initialMemory
817 | { mouse = Mouse 0 0 False False False
818 | , keyboard = emptyKeyboard
819 | , screen = toScreen x y
820 | , time = Time (Time.millisToPosix 0)
821 | }
822 | , Cmd.none
823 | )
824 |
825 | _ ->
826 | ( Game E.Visible
827 | initialMemory
828 | { mouse = Mouse 0 0 False False False
829 | , keyboard = emptyKeyboard
830 | , screen = initScreen
831 | , time = Time (Time.millisToPosix 0)
832 | }
833 | , Task.perform GotViewport Dom.getViewport
834 | )
835 |
836 |
837 | gameView : (Computer -> memory -> List Shape) -> Game memory -> Browser.Document Msg
838 | gameView viewMemory (Game _ memory computer) =
839 | { title = "Playground"
840 | , body =
841 | [ render
842 | { screen = computer.screen
843 | , maybeMsg =
844 | Just
845 | { mouseButtonDown = SvgMouseButtonDown
846 | , onClick = SvgMouseClick
847 | , onMouseOver = SvgMouseOver
848 | , onMouseOut = SvgMouseOut
849 | }
850 | , shapes = viewMemory computer memory
851 | }
852 | ]
853 | }
854 |
855 |
856 | gameUpdate : (Computer -> memory -> memory) -> Msg -> Game memory -> ( Game memory, Cmd msg )
857 | gameUpdate updateMemory msg (Game vis memory computer) =
858 | ( case msg of
859 | Tick time ->
860 | Game vis (updateMemory computer memory) <|
861 | if computer.mouse.click then
862 | { computer | time = Time time, mouse = mouseClick False computer.mouse }
863 |
864 | else
865 | { computer | time = Time time }
866 |
867 | GotViewport { viewport } ->
868 | Game vis memory { computer | screen = toScreen viewport.width viewport.height }
869 |
870 | Resized w h ->
871 | Game vis memory { computer | screen = toScreen (toFloat w) (toFloat h) }
872 |
873 | KeyChanged isDown key ->
874 | Game vis memory { computer | keyboard = updateKeyboard isDown key computer.keyboard }
875 |
876 | MouseMove pageX pageY ->
877 | let
878 | x =
879 | computer.screen.left + pageX
880 |
881 | y =
882 | computer.screen.top - pageY
883 | in
884 | Game vis memory { computer | mouse = mouseMove x y computer.mouse }
885 |
886 | MouseButtonUp ->
887 | Game vis memory { computer | mouse = mouseDown False computer.mouse }
888 |
889 | SvgMouseClick ->
890 | Game vis memory { computer | mouse = mouseClick True computer.mouse }
891 |
892 | SvgMouseButtonDown ->
893 | Game vis memory { computer | mouse = mouseDown True computer.mouse }
894 |
895 | SvgMouseOver ->
896 | Game vis memory { computer | mouse = mouseOver True computer.mouse }
897 |
898 | SvgMouseOut ->
899 | Game vis memory { computer | mouse = mouseOver False computer.mouse }
900 |
901 | VisibilityChanged visibility ->
902 | Game visibility
903 | memory
904 | { computer
905 | | keyboard = emptyKeyboard
906 | , mouse = Mouse computer.mouse.x computer.mouse.y False False False
907 | }
908 | , Cmd.none
909 | )
910 |
911 |
912 | gameSubscriptions : List Attributes -> Game memory -> Sub Msg
913 | gameSubscriptions attributes (Game visibility _ computer) =
914 | let
915 | fullScreenMode =
916 | attributeSvgType attributes == FullScreen
917 | in
918 | Sub.batch
919 | [ E.onVisibilityChange VisibilityChanged
920 | , case visibility of
921 | E.Hidden ->
922 | Sub.none
923 |
924 | E.Visible ->
925 | Sub.batch
926 | [ if computer.mouse.down then
927 | E.onMouseUp (D.succeed MouseButtonUp)
928 |
929 | else
930 | Sub.none
931 | , if fullScreenMode || (computer.mouse.over || computer.mouse.down) then
932 | Sub.batch
933 | [ E.onMouseMove (D.map2 MouseMove (D.field "pageX" D.float) (D.field "pageY" D.float))
934 | , E.onKeyUp (D.map (KeyChanged False) (D.field "key" D.string))
935 | , E.onKeyDown (D.map (KeyChanged True) (D.field "key" D.string))
936 | , E.onAnimationFrame Tick
937 | , E.onResize Resized
938 | ]
939 |
940 | else
941 | Sub.none
942 | ]
943 | ]
944 |
945 |
946 |
947 | -- GAME HELPERS
948 |
949 |
950 | changeMemory : Game memory -> (memory -> memory) -> Game memory
951 | changeMemory (Game visibility memory computer) memoryChanger =
952 | Game visibility (memoryChanger memory) computer
953 |
954 |
955 | getMemory : Game memory -> memory
956 | getMemory (Game visibility memory computer) =
957 | memory
958 |
959 |
960 | type Msg
961 | = KeyChanged Bool String
962 | | Tick Time.Posix
963 | | GotViewport Dom.Viewport
964 | | Resized Int Int
965 | | VisibilityChanged E.Visibility
966 | | MouseMove Float Float
967 | | MouseButtonUp
968 | | SvgMouseClick
969 | | SvgMouseButtonDown
970 | | SvgMouseOver
971 | | SvgMouseOut
972 |
973 |
974 |
975 | -- SCREEN HELPERS
976 |
977 |
978 | toScreen : Float -> Float -> Screen
979 | toScreen width height =
980 | { width = width
981 | , height = height
982 | , top = height / 2
983 | , left = -width / 2
984 | , right = width / 2
985 | , bottom = -height / 2
986 | }
987 |
988 |
989 |
990 | -- MOUSE HELPERS
991 |
992 |
993 | mouseClick : Bool -> Mouse -> Mouse
994 | mouseClick bool mouse =
995 | { mouse | click = bool }
996 |
997 |
998 | mouseDown : Bool -> Mouse -> Mouse
999 | mouseDown bool mouse =
1000 | { mouse | down = bool }
1001 |
1002 |
1003 | mouseOver : Bool -> Mouse -> Mouse
1004 | mouseOver bool mouse =
1005 | { mouse | over = bool }
1006 |
1007 |
1008 | mouseMove : Float -> Float -> Mouse -> Mouse
1009 | mouseMove x y mouse =
1010 | { mouse | x = x, y = y }
1011 |
1012 |
1013 |
1014 | -- KEYBOARD HELPERS
1015 |
1016 |
1017 | emptyKeyboard : Keyboard
1018 | emptyKeyboard =
1019 | { up = False
1020 | , down = False
1021 | , left = False
1022 | , right = False
1023 | , space = False
1024 | , enter = False
1025 | , shift = False
1026 | , backspace = False
1027 | , keys = Set.empty
1028 | }
1029 |
1030 |
1031 | updateKeyboard : Bool -> String -> Keyboard -> Keyboard
1032 | updateKeyboard isDown key keyboard =
1033 | let
1034 | keys =
1035 | if isDown then
1036 | Set.insert key keyboard.keys
1037 |
1038 | else
1039 | Set.remove key keyboard.keys
1040 | in
1041 | case key of
1042 | " " ->
1043 | { keyboard | keys = keys, space = isDown }
1044 |
1045 | "Enter" ->
1046 | { keyboard | keys = keys, enter = isDown }
1047 |
1048 | "Shift" ->
1049 | { keyboard | keys = keys, shift = isDown }
1050 |
1051 | "Backspace" ->
1052 | { keyboard | keys = keys, backspace = isDown }
1053 |
1054 | "ArrowUp" ->
1055 | { keyboard | keys = keys, up = isDown }
1056 |
1057 | "ArrowDown" ->
1058 | { keyboard | keys = keys, down = isDown }
1059 |
1060 | "ArrowLeft" ->
1061 | { keyboard | keys = keys, left = isDown }
1062 |
1063 | "ArrowRight" ->
1064 | { keyboard | keys = keys, right = isDown }
1065 |
1066 | _ ->
1067 | { keyboard | keys = keys }
1068 |
1069 |
1070 |
1071 | -- SHAPES
1072 |
1073 |
1074 | {-| Shapes help you make a `picture`, `animation`, or `game`.
1075 |
1076 | Read on to see examples of [`circle`](#circle), [`rectangle`](#rectangle),
1077 | [`words`](#words), [`image`](#image), and many more!
1078 |
1079 | -}
1080 | type Shape
1081 | = Shape
1082 | Number
1083 | -- x
1084 | Number
1085 | -- y
1086 | Number
1087 | -- angle
1088 | Number
1089 | -- scale
1090 | Number
1091 | -- alpha
1092 | Form
1093 |
1094 |
1095 | type Form
1096 | = Circle Color Number
1097 | | Oval Color Number Number
1098 | | Rectangle Color Number Number
1099 | | Ngon Color Int Number
1100 | | Polygon Color (List ( Number, Number ))
1101 | | Image Number Number String
1102 | | Words Color String
1103 | | Group (List Shape)
1104 |
1105 |
1106 | {-| Make circles:
1107 |
1108 | dot =
1109 | circle red 10
1110 |
1111 | sun =
1112 | circle yellow 300
1113 |
1114 | You give a color and then the radius. So the higher the number, the larger
1115 | the circle.
1116 |
1117 | -}
1118 | circle : Color -> Number -> Shape
1119 | circle color radius =
1120 | Shape 0 0 0 1 1 (Circle color radius)
1121 |
1122 |
1123 | {-| Make ovals:
1124 |
1125 | football =
1126 | oval brown 200 100
1127 |
1128 | You give the color, and then the width and height. So our `football` example
1129 | is 200 pixels wide and 100 pixels tall.
1130 |
1131 | -}
1132 | oval : Color -> Number -> Number -> Shape
1133 | oval color width height =
1134 | Shape 0 0 0 1 1 (Oval color width height)
1135 |
1136 |
1137 | {-| Make squares. Here are two squares combined to look like an empty box:
1138 |
1139 | import Playground exposing (..)
1140 |
1141 | main =
1142 | picture
1143 | [ square purple 80
1144 | , square white 60
1145 | ]
1146 |
1147 | The number you give is the dimension of each side. So that purple square would
1148 | be 80 pixels by 80 pixels.
1149 |
1150 | -}
1151 | square : Color -> Number -> Shape
1152 | square color n =
1153 | Shape 0 0 0 1 1 (Rectangle color n n)
1154 |
1155 |
1156 | {-| Make rectangles. This example makes a red cross:
1157 |
1158 | import Playground exposing (..)
1159 |
1160 | main =
1161 | picture
1162 | [ rectangle red 20 60
1163 | , rectangle red 60 20
1164 | ]
1165 |
1166 | You give the color, width, and then height. So the first shape is vertical
1167 | part of the cross, the thinner and taller part.
1168 |
1169 | -}
1170 | rectangle : Color -> Number -> Number -> Shape
1171 | rectangle color width height =
1172 | Shape 0 0 0 1 1 (Rectangle color width height)
1173 |
1174 |
1175 | {-| Make triangles. So if you wanted to draw the Egyptian pyramids, you could
1176 | do a simple version like this:
1177 |
1178 | import Playground exposing (..)
1179 |
1180 | main =
1181 | picture
1182 | [ triangle darkYellow 200
1183 | ]
1184 |
1185 | The number is the "radius", so the distance from the center to each point of
1186 | the pyramid is `200`. Pretty big!
1187 |
1188 | -}
1189 | triangle : Color -> Number -> Shape
1190 | triangle color radius =
1191 | Shape 0 0 0 1 1 (Ngon color 3 radius)
1192 |
1193 |
1194 | {-| Make pentagons:
1195 |
1196 | import Playground exposing (..)
1197 |
1198 | main =
1199 | picture
1200 | [ pentagon darkGrey 100
1201 | ]
1202 |
1203 | You give the color and then the radius. So the distance from the center to each
1204 | of the five points is 100 pixels.
1205 |
1206 | -}
1207 | pentagon : Color -> Number -> Shape
1208 | pentagon color radius =
1209 | Shape 0 0 0 1 1 (Ngon color 5 radius)
1210 |
1211 |
1212 | {-| Make hexagons:
1213 |
1214 | import Playground exposing (..)
1215 |
1216 | main =
1217 | picture
1218 | [ hexagon lightYellow 50
1219 | ]
1220 |
1221 | The number is the radius, the distance from the center to each point.
1222 |
1223 | If you made more hexagons, you could [`move`](#move) them around to make a
1224 | honeycomb pattern!
1225 |
1226 | -}
1227 | hexagon : Color -> Number -> Shape
1228 | hexagon color radius =
1229 | Shape 0 0 0 1 1 (Ngon color 6 radius)
1230 |
1231 |
1232 | {-| Make octogons:
1233 |
1234 | import Playground exposing (..)
1235 |
1236 | main =
1237 | picture
1238 | [ octagon red 100
1239 | ]
1240 |
1241 | You give the color and radius, so each point of this stop sign is 100 pixels
1242 | from the center.
1243 |
1244 | -}
1245 | octagon : Color -> Number -> Shape
1246 | octagon color radius =
1247 | Shape 0 0 0 1 1 (Ngon color 8 radius)
1248 |
1249 |
1250 | {-| Make any shape you want! Here is a very thin triangle:
1251 |
1252 | import Playground exposing (..)
1253 |
1254 | main =
1255 | picture
1256 | [ polygon [ ( -10, -20 ), ( 0, 100 ), ( 10, -20 ) ]
1257 | ]
1258 |
1259 | **Note:** If you [`rotate`](#rotate) a polygon, it will always rotate around
1260 | `(0,0)`. So it is best to build your shapes around that point, and then use
1261 | [`move`](#move) or [`group`](#group) so that rotation makes more sense.
1262 |
1263 | -}
1264 | polygon : Color -> List ( Number, Number ) -> Shape
1265 | polygon color points =
1266 | Shape 0 0 0 1 1 (Polygon color points)
1267 |
1268 |
1269 | {-| Add some image from the internet:
1270 |
1271 | import Playground exposing (..)
1272 |
1273 | main =
1274 | picture
1275 | [ image 96 96 "https://elm-lang.org/images/turtle.gif"
1276 | ]
1277 |
1278 | You provide the width, height, and then the URL of the image you want to show.
1279 |
1280 | -}
1281 | image : Number -> Number -> String -> Shape
1282 | image w h src =
1283 | Shape 0 0 0 1 1 (Image w h src)
1284 |
1285 |
1286 | {-| Show some words!
1287 |
1288 | import Playground exposing (..)
1289 |
1290 | main =
1291 | picture
1292 | [ words black "Hello! How are you?"
1293 | ]
1294 |
1295 | You can use [`scale`](#scale) to make the words bigger or smaller.
1296 |
1297 | -}
1298 | words : Color -> String -> Shape
1299 | words color string =
1300 | Shape 0 0 0 1 1 (Words color string)
1301 |
1302 |
1303 | {-| Put shapes together so you can [`move`](#move) and [`rotate`](#rotate)
1304 | them as a group. Maybe you want to put a bunch of stars in the sky:
1305 |
1306 | import Playground exposing (..)
1307 |
1308 | main =
1309 | picture
1310 | [ star
1311 | |> move 100 100
1312 | |> rotate 5
1313 | , star
1314 | |> move -120 40
1315 | |> rotate 20
1316 | , star
1317 | |> move 80 -150
1318 | |> rotate 32
1319 | , star
1320 | |> move -90 -30
1321 | |> rotate -16
1322 | ]
1323 |
1324 | star =
1325 | group
1326 | [ triangle yellow 20
1327 | , triangle yellow 20
1328 | |> rotate 180
1329 | ]
1330 |
1331 | -}
1332 | group : List Shape -> Shape
1333 | group shapes =
1334 | Shape 0 0 0 1 1 (Group shapes)
1335 |
1336 |
1337 |
1338 | -- TRANSFORMS
1339 |
1340 |
1341 | {-| Move a shape by some number of pixels:
1342 |
1343 | import Playground exposing (..)
1344 |
1345 | main =
1346 | picture
1347 | [ square red 100
1348 | |> move -60 60
1349 | , square yellow 100
1350 | |> move 60 60
1351 | , square green 100
1352 | |> move 60 -60
1353 | , square blue 100
1354 | |> move -60 -60
1355 | ]
1356 |
1357 | -}
1358 | move : Number -> Number -> Shape -> Shape
1359 | move dx dy (Shape x y a s o f) =
1360 | Shape (x + dx) (y + dy) a s o f
1361 |
1362 |
1363 | {-| Move a shape up by some number of pixels. So if you wanted to make a tree
1364 | you could move the leaves up above the trunk:
1365 |
1366 | import Playground exposing (..)
1367 |
1368 | main =
1369 | picture
1370 | [ rectangle brown 40 200
1371 | , circle green 100
1372 | |> moveUp 180
1373 | ]
1374 |
1375 | -}
1376 | moveUp : Number -> Shape -> Shape
1377 | moveUp =
1378 | moveY
1379 |
1380 |
1381 | {-| Move a shape down by some number of pixels. So if you wanted to put the sky
1382 | above the ground, you could move the sky up and the ground down:
1383 |
1384 | import Playground exposing (..)
1385 |
1386 | main =
1387 | picture
1388 | [ rectangle lightBlue 200 100
1389 | |> moveUp 50
1390 | , rectangle lightGreen 200 100
1391 | |> moveDown 50
1392 | ]
1393 |
1394 | -}
1395 | moveDown : Number -> Shape -> Shape
1396 | moveDown dy (Shape x y a s o f) =
1397 | Shape x (y - dy) a s o f
1398 |
1399 |
1400 | {-| Move shapes to the left.
1401 |
1402 | import Playground exposing (..)
1403 |
1404 | main =
1405 | picture
1406 | [ circle yellow 10
1407 | |> moveLeft 80
1408 | |> moveUp 30
1409 | ]
1410 |
1411 | -}
1412 | moveLeft : Number -> Shape -> Shape
1413 | moveLeft dx (Shape x y a s o f) =
1414 | Shape (x - dx) y a s o f
1415 |
1416 |
1417 | {-| Move shapes to the right.
1418 |
1419 | import Playground exposing (..)
1420 |
1421 | main =
1422 | picture
1423 | [ square purple 20
1424 | |> moveRight 80
1425 | |> moveDown 100
1426 | ]
1427 |
1428 | -}
1429 | moveRight : Number -> Shape -> Shape
1430 | moveRight =
1431 | moveX
1432 |
1433 |
1434 | {-| Move the `x` coordinate of a shape by some amount. Here is a square that
1435 | moves back and forth:
1436 |
1437 | import Playground exposing (..)
1438 |
1439 | main =
1440 | animation view
1441 |
1442 | view time =
1443 | [ square purple 20
1444 | |> moveX (wave 4 -200 200 time)
1445 | ]
1446 |
1447 | Using `moveX` feels a bit nicer here because the movement may be positive or negative.
1448 |
1449 | -}
1450 | moveX : Number -> Shape -> Shape
1451 | moveX dx (Shape x y a s o f) =
1452 | Shape (x + dx) y a s o f
1453 |
1454 |
1455 | {-| Move the `y` coordinate of a shape by some amount. Maybe you want to make
1456 | grass along the bottom of the screen:
1457 |
1458 | import Playground exposing (..)
1459 |
1460 | main =
1461 | game view update 0
1462 |
1463 | update computer memory =
1464 | memory
1465 |
1466 | view computer count =
1467 | [ rectangle green computer.screen.width 100
1468 | |> moveY computer.screen.bottom
1469 | ]
1470 |
1471 | Using `moveY` feels a bit nicer when setting things relative to the bottom or
1472 | top of the screen, since the values are negative sometimes.
1473 |
1474 | -}
1475 | moveY : Number -> Shape -> Shape
1476 | moveY dy (Shape x y a s o f) =
1477 | Shape x (y + dy) a s o f
1478 |
1479 |
1480 | {-| Make a shape bigger or smaller. So if you wanted some [`words`](#words) to
1481 | be larger, you could say:
1482 |
1483 | import Playground exposing (..)
1484 |
1485 | main =
1486 | picture
1487 | [ words black "Hello, nice to see you!"
1488 | |> scale 3
1489 | ]
1490 |
1491 | -}
1492 | scale : Number -> Shape -> Shape
1493 | scale ns (Shape x y a s o f) =
1494 | Shape x y a (s * ns) o f
1495 |
1496 |
1497 | {-| Rotate shapes in degrees.
1498 |
1499 | import Playground exposing (..)
1500 |
1501 | main =
1502 | picture
1503 | [ words black "These words are tilted!"
1504 | |> rotate 10
1505 | ]
1506 |
1507 | The degrees go **counter-clockwise** to match the direction of the
1508 | [unit circle](https://en.wikipedia.org/wiki/Unit_circle).
1509 |
1510 | -}
1511 | rotate : Number -> Shape -> Shape
1512 | rotate da (Shape x y a s o f) =
1513 | Shape x y (a + da) s o f
1514 |
1515 |
1516 | {-| Fade a shape. This lets you make shapes see-through or even completely
1517 | invisible. Here is a shape that fades in and out:
1518 |
1519 | import Playground exposing (..)
1520 |
1521 | main =
1522 | animation view
1523 |
1524 | view time =
1525 | [ square orange 30
1526 | , square blue 200
1527 | |> fade (zigzag 0 1 3 time)
1528 | ]
1529 |
1530 | The number has to be between `0` and `1`, where `0` is totally transparent
1531 | and `1` is completely solid.
1532 |
1533 | -}
1534 | fade : Number -> Shape -> Shape
1535 | fade o (Shape x y a s _ f) =
1536 | Shape x y a s o f
1537 |
1538 |
1539 |
1540 | -- COLOR
1541 |
1542 |
1543 | {-| Represents a color.
1544 |
1545 | The colors below, like `red` and `green`, come from the [Tango palette][tango].
1546 | It provides a bunch of aesthetically reasonable colors. Each color comes with a
1547 | light and dark version, so you always get a set like `lightYellow`, `yellow`,
1548 | and `darkYellow`.
1549 |
1550 | [tango]: https://en.wikipedia.org/wiki/Tango_Desktop_Project
1551 |
1552 | -}
1553 | type Color
1554 | = Hex String
1555 | | Rgb Int Int Int
1556 |
1557 |
1558 | {-| -}
1559 | lightYellow : Color
1560 | lightYellow =
1561 | Hex "#fce94f"
1562 |
1563 |
1564 | {-| -}
1565 | yellow : Color
1566 | yellow =
1567 | Hex "#edd400"
1568 |
1569 |
1570 | {-| -}
1571 | darkYellow : Color
1572 | darkYellow =
1573 | Hex "#c4a000"
1574 |
1575 |
1576 | {-| -}
1577 | lightOrange : Color
1578 | lightOrange =
1579 | Hex "#fcaf3e"
1580 |
1581 |
1582 | {-| -}
1583 | orange : Color
1584 | orange =
1585 | Hex "#f57900"
1586 |
1587 |
1588 | {-| -}
1589 | darkOrange : Color
1590 | darkOrange =
1591 | Hex "#ce5c00"
1592 |
1593 |
1594 | {-| -}
1595 | lightBrown : Color
1596 | lightBrown =
1597 | Hex "#e9b96e"
1598 |
1599 |
1600 | {-| -}
1601 | brown : Color
1602 | brown =
1603 | Hex "#c17d11"
1604 |
1605 |
1606 | {-| -}
1607 | darkBrown : Color
1608 | darkBrown =
1609 | Hex "#8f5902"
1610 |
1611 |
1612 | {-| -}
1613 | lightGreen : Color
1614 | lightGreen =
1615 | Hex "#8ae234"
1616 |
1617 |
1618 | {-| -}
1619 | green : Color
1620 | green =
1621 | Hex "#73d216"
1622 |
1623 |
1624 | {-| -}
1625 | darkGreen : Color
1626 | darkGreen =
1627 | Hex "#4e9a06"
1628 |
1629 |
1630 | {-| -}
1631 | lightBlue : Color
1632 | lightBlue =
1633 | Hex "#729fcf"
1634 |
1635 |
1636 | {-| -}
1637 | blue : Color
1638 | blue =
1639 | Hex "#3465a4"
1640 |
1641 |
1642 | {-| -}
1643 | darkBlue : Color
1644 | darkBlue =
1645 | Hex "#204a87"
1646 |
1647 |
1648 | {-| -}
1649 | lightPurple : Color
1650 | lightPurple =
1651 | Hex "#ad7fa8"
1652 |
1653 |
1654 | {-| -}
1655 | purple : Color
1656 | purple =
1657 | Hex "#75507b"
1658 |
1659 |
1660 | {-| -}
1661 | darkPurple : Color
1662 | darkPurple =
1663 | Hex "#5c3566"
1664 |
1665 |
1666 | {-| -}
1667 | lightRed : Color
1668 | lightRed =
1669 | Hex "#ef2929"
1670 |
1671 |
1672 | {-| -}
1673 | red : Color
1674 | red =
1675 | Hex "#cc0000"
1676 |
1677 |
1678 | {-| -}
1679 | darkRed : Color
1680 | darkRed =
1681 | Hex "#a40000"
1682 |
1683 |
1684 | {-| -}
1685 | lightGrey : Color
1686 | lightGrey =
1687 | Hex "#eeeeec"
1688 |
1689 |
1690 | {-| -}
1691 | grey : Color
1692 | grey =
1693 | Hex "#d3d7cf"
1694 |
1695 |
1696 | {-| -}
1697 | darkGrey : Color
1698 | darkGrey =
1699 | Hex "#babdb6"
1700 |
1701 |
1702 | {-| -}
1703 | lightCharcoal : Color
1704 | lightCharcoal =
1705 | Hex "#888a85"
1706 |
1707 |
1708 | {-| -}
1709 | charcoal : Color
1710 | charcoal =
1711 | Hex "#555753"
1712 |
1713 |
1714 | {-| -}
1715 | darkCharcoal : Color
1716 | darkCharcoal =
1717 | Hex "#2e3436"
1718 |
1719 |
1720 | {-| -}
1721 | white : Color
1722 | white =
1723 | Hex "#FFFFFF"
1724 |
1725 |
1726 | {-| -}
1727 | black : Color
1728 | black =
1729 | Hex "#000000"
1730 |
1731 |
1732 |
1733 | -- ALTERNATE SPELLING GREYS
1734 |
1735 |
1736 | {-| -}
1737 | lightGray : Color
1738 | lightGray =
1739 | Hex "#eeeeec"
1740 |
1741 |
1742 | {-| -}
1743 | gray : Color
1744 | gray =
1745 | Hex "#d3d7cf"
1746 |
1747 |
1748 | {-| -}
1749 | darkGray : Color
1750 | darkGray =
1751 | Hex "#babdb6"
1752 |
1753 |
1754 |
1755 | -- CUSTOM COLORS
1756 |
1757 |
1758 | {-| RGB stands for Red-Green-Blue. With these three parts, you can create any
1759 | color you want. For example:
1760 |
1761 | brightBlue =
1762 | rgb 18 147 216
1763 |
1764 | brightGreen =
1765 | rgb 119 244 8
1766 |
1767 | brightPurple =
1768 | rgb 94 28 221
1769 |
1770 | Each number needs to be between 0 and 255.
1771 |
1772 | It can be hard to figure out what numbers to pick, so try using a color picker
1773 | like [paletton] to find colors that look nice together. Once you find nice
1774 | colors, click on the color previews to get their RGB values.
1775 |
1776 | [paletton]: http://paletton.com/
1777 |
1778 | -}
1779 | rgb : Number -> Number -> Number -> Color
1780 | rgb r g b =
1781 | Rgb (colorClamp r) (colorClamp g) (colorClamp b)
1782 |
1783 |
1784 | colorClamp : Number -> Int
1785 | colorClamp number =
1786 | clamp 0 255 (round number)
1787 |
1788 |
1789 |
1790 | -- RENDER
1791 | -- render :
1792 | -- Screen
1793 | -- ->
1794 | -- Maybe
1795 | -- { mouseButtonDown : msg
1796 | -- , onClick : msg
1797 | -- , onMouseOver : msg
1798 | -- , onMouseOut : msg
1799 | -- }
1800 | -- -> List Shape
1801 | -- -> Html.Html msg
1802 | -- render screen maybeMsg shapes =
1803 |
1804 |
1805 | render :
1806 | { c
1807 | | maybeMsg :
1808 | Maybe
1809 | { a
1810 | | mouseButtonDown : msg
1811 | , onClick : msg
1812 | , onMouseOut : msg
1813 | , onMouseOver : msg
1814 | }
1815 | , screen : { b | bottom : Float, height : Float, left : Float, width : Float }
1816 | , shapes : List Shape
1817 | }
1818 | -> Html.Html msg
1819 | render { screen, maybeMsg, shapes } =
1820 | let
1821 | w =
1822 | String.fromFloat screen.width
1823 |
1824 | h =
1825 | String.fromFloat screen.height
1826 |
1827 | x =
1828 | String.fromFloat screen.left
1829 |
1830 | y =
1831 | String.fromFloat screen.bottom
1832 | in
1833 | svg
1834 | ([ viewBox (x ++ " " ++ y ++ " " ++ w ++ " " ++ h)
1835 | , Svg.Attributes.style "display: block"
1836 | , width "100%"
1837 | , height "100%"
1838 | ]
1839 | ++ (case maybeMsg of
1840 | Just msg ->
1841 | [ Svg.Events.onClick msg.onClick
1842 | , Svg.Events.onMouseDown msg.mouseButtonDown
1843 | , Svg.Events.onMouseOver msg.onMouseOver
1844 | , Svg.Events.onMouseOut msg.onMouseOut
1845 | ]
1846 |
1847 | Nothing ->
1848 | []
1849 | )
1850 | )
1851 | (List.map renderShape shapes)
1852 |
1853 |
1854 |
1855 | -- TODO try adding Svg.Lazy to renderShape
1856 | --
1857 |
1858 |
1859 | renderShape : Shape -> Svg msg
1860 | renderShape (Shape x y angle s alpha form) =
1861 | case form of
1862 | Circle color radius ->
1863 | renderCircle color radius x y angle s alpha
1864 |
1865 | Oval color width height ->
1866 | renderOval color width height x y angle s alpha
1867 |
1868 | Rectangle color width height ->
1869 | renderRectangle color width height x y angle s alpha
1870 |
1871 | Ngon color n radius ->
1872 | renderNgon color n radius x y angle s alpha
1873 |
1874 | Polygon color points ->
1875 | renderPolygon color points x y angle s alpha
1876 |
1877 | Image width height src ->
1878 | renderImage width height src x y angle s alpha
1879 |
1880 | Words color string ->
1881 | renderWords color string x y angle s alpha
1882 |
1883 | Group shapes ->
1884 | g (transform (renderTransform x y angle s) :: renderAlpha alpha)
1885 | (List.map renderShape shapes)
1886 |
1887 |
1888 |
1889 | -- RENDER CIRCLE AND OVAL
1890 |
1891 |
1892 | renderCircle : Color -> Number -> Number -> Number -> Number -> Number -> Number -> Svg msg
1893 | renderCircle color radius x y angle s alpha =
1894 | Svg.circle
1895 | (r (String.fromFloat radius)
1896 | :: fill (renderColor color)
1897 | :: transform (renderTransform x y angle s)
1898 | :: renderAlpha alpha
1899 | )
1900 | []
1901 |
1902 |
1903 | renderOval : Color -> Number -> Number -> Number -> Number -> Number -> Number -> Number -> Svg msg
1904 | renderOval color width height x y angle s alpha =
1905 | ellipse
1906 | (rx (String.fromFloat (width / 2))
1907 | :: ry (String.fromFloat (height / 2))
1908 | :: fill (renderColor color)
1909 | :: transform (renderTransform x y angle s)
1910 | :: renderAlpha alpha
1911 | )
1912 | []
1913 |
1914 |
1915 |
1916 | -- RENDER RECTANGLE AND IMAGE
1917 |
1918 |
1919 | renderRectangle : Color -> Number -> Number -> Number -> Number -> Number -> Number -> Number -> Svg msg
1920 | renderRectangle color w h x y angle s alpha =
1921 | rect
1922 | (width (String.fromFloat w)
1923 | :: height (String.fromFloat h)
1924 | :: fill (renderColor color)
1925 | :: transform (renderRectTransform w h x y angle s)
1926 | :: renderAlpha alpha
1927 | )
1928 | []
1929 |
1930 |
1931 | renderRectTransform : Number -> Number -> Number -> Number -> Number -> Number -> String
1932 | renderRectTransform width height x y angle s =
1933 | renderTransform x y angle s
1934 | ++ " translate("
1935 | ++ String.fromFloat (-width / 2)
1936 | ++ ","
1937 | ++ String.fromFloat (-height / 2)
1938 | ++ ")"
1939 |
1940 |
1941 | renderImage : Number -> Number -> String -> Number -> Number -> Number -> Number -> Number -> Svg msg
1942 | renderImage w h src x y angle s alpha =
1943 | Svg.image
1944 | (xlinkHref src
1945 | :: width (String.fromFloat w)
1946 | :: height (String.fromFloat h)
1947 | :: transform (renderRectTransform w h x y angle s)
1948 | :: renderAlpha alpha
1949 | )
1950 | []
1951 |
1952 |
1953 |
1954 | -- RENDER NGON
1955 |
1956 |
1957 | renderNgon : Color -> Int -> Number -> Number -> Number -> Number -> Number -> Number -> Svg msg
1958 | renderNgon color n radius x y angle s alpha =
1959 | Svg.polygon
1960 | (points (toNgonPoints 0 n radius "")
1961 | :: fill (renderColor color)
1962 | :: transform (renderTransform x y angle s)
1963 | :: renderAlpha alpha
1964 | )
1965 | []
1966 |
1967 |
1968 | toNgonPoints : Int -> Int -> Float -> String -> String
1969 | toNgonPoints i n radius string =
1970 | if i == n then
1971 | string
1972 |
1973 | else
1974 | let
1975 | a =
1976 | turns (toFloat i / toFloat n - 0.25)
1977 |
1978 | x =
1979 | radius * cos a
1980 |
1981 | y =
1982 | radius * sin a
1983 | in
1984 | toNgonPoints (i + 1) n radius (string ++ String.fromFloat x ++ "," ++ String.fromFloat y ++ " ")
1985 |
1986 |
1987 |
1988 | -- RENDER POLYGON
1989 |
1990 |
1991 | renderPolygon : Color -> List ( Number, Number ) -> Number -> Number -> Number -> Number -> Number -> Svg msg
1992 | renderPolygon color coordinates x y angle s alpha =
1993 | Svg.polygon
1994 | (points (List.foldl addPoint "" coordinates)
1995 | :: fill (renderColor color)
1996 | :: transform (renderTransform x y angle s)
1997 | :: renderAlpha alpha
1998 | )
1999 | []
2000 |
2001 |
2002 | addPoint : ( Float, Float ) -> String -> String
2003 | addPoint ( x, y ) str =
2004 | str ++ String.fromFloat x ++ "," ++ String.fromFloat y ++ " "
2005 |
2006 |
2007 |
2008 | -- RENDER WORDS
2009 |
2010 |
2011 | renderWords : Color -> String -> Number -> Number -> Number -> Number -> Number -> Svg msg
2012 | renderWords color string x y angle s alpha =
2013 | text_
2014 | (textAnchor "middle"
2015 | :: dominantBaseline "central"
2016 | :: fill (renderColor color)
2017 | :: transform (renderTransform x y angle s)
2018 | :: renderAlpha alpha
2019 | )
2020 | [ text string
2021 | ]
2022 |
2023 |
2024 |
2025 | -- RENDER COLOR
2026 |
2027 |
2028 | renderColor : Color -> String
2029 | renderColor color =
2030 | case color of
2031 | Hex str ->
2032 | str
2033 |
2034 | Rgb r g b ->
2035 | "rgb(" ++ String.fromInt r ++ "," ++ String.fromInt g ++ "," ++ String.fromInt b ++ ")"
2036 |
2037 |
2038 |
2039 | -- RENDER ALPHA
2040 |
2041 |
2042 | renderAlpha : Number -> List (Svg.Attribute msg)
2043 | renderAlpha alpha =
2044 | if alpha == 1 then
2045 | []
2046 |
2047 | else
2048 | [ opacity (String.fromFloat (clamp 0 1 alpha)) ]
2049 |
2050 |
2051 |
2052 | -- RENDER TRANFORMS
2053 |
2054 |
2055 | renderTransform : Number -> Number -> Number -> Number -> String
2056 | renderTransform x y a s =
2057 | if a == 0 then
2058 | if s == 1 then
2059 | "translate(" ++ String.fromFloat x ++ "," ++ String.fromFloat -y ++ ")"
2060 |
2061 | else
2062 | "translate(" ++ String.fromFloat x ++ "," ++ String.fromFloat -y ++ ") scale(" ++ String.fromFloat s ++ ")"
2063 |
2064 | else if s == 1 then
2065 | "translate(" ++ String.fromFloat x ++ "," ++ String.fromFloat -y ++ ") rotate(" ++ String.fromFloat -a ++ ")"
2066 |
2067 | else
2068 | "translate(" ++ String.fromFloat x ++ "," ++ String.fromFloat -y ++ ") rotate(" ++ String.fromFloat -a ++ ") scale(" ++ String.fromFloat s ++ ")"
2069 |
--------------------------------------------------------------------------------