├── 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 |
https://github.com/lucamug/elm-playground-3d
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 | ![Floating City - Tokyo](https://elm-playground-3d.netlify.com/images/968315a2-4e5f-4038-bad9-795e5cadf254.png) 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 | ![Picture](https://elm-playground-3d.netlify.com/images/2d3d-1.png) 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 | ![Animation](https://elm-playground-3d.netlify.com/images/2d3d.gif) 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 | ![Game](https://elm-playground-3d.netlify.com/images/cube-animated.gif) 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 | [![Netlify Status](https://api.netlify.com/api/v1/badges/ee1a35aa-eaad-470c-89a5-94403ef3a1e8/deploy-status)](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 | --------------------------------------------------------------------------------