├── .gitignore
├── README.md
├── elm-package.json
├── webpack.config.js
├── package.json
├── index.html
└── src
├── board-driver
├── index.js
└── Board.elm
├── keyboard-driver
└── index.js
└── index.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | elm-stuff
3 | elm.js
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Demo showing using Elm to draw stuff controlled by a Cycle.js driver
2 |
3 | Demo here: http://justinwoo.github.io/cycle-elm-etch-sketch
4 |
5 | I'll write up a post about this if there's interest (or I feel like it'd be fun to write)
6 |
--------------------------------------------------------------------------------
/elm-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0.0",
3 | "summary": "helpful summary of your project, less than 80 characters",
4 | "repository": "https://github.com/user/project.git",
5 | "license": "BSD3",
6 | "source-directories": [
7 | "."
8 | ],
9 | "exposed-modules": [],
10 | "dependencies": {
11 | "elm-lang/core": "3.0.0 <= v < 4.0.0"
12 | },
13 | "elm-version": "0.16.0 <= v < 0.17.0"
14 | }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 |
4 | module.exports = {
5 | entry: [
6 | './src/index'
7 | ],
8 | output: {
9 | filename: 'build/index.js'
10 | },
11 | module: {
12 | loaders: [{
13 | test: /\.js$/,
14 | loaders: ['babel'],
15 | exclude: /node_modules/,
16 | include: __dirname
17 | }, {
18 | test: /\.elm$/,
19 | loaders: ['elm-simple-loader'],
20 | exclude: /node_modules/
21 | }]
22 | }
23 | };
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cycleelmfun",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "webpack -wdc --progress",
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "author": "",
11 | "license": "MIT",
12 | "devDependencies": {
13 | "@cycle/core": "^4.0.0",
14 | "@cycle/dom": "^6.0.0",
15 | "babel-core": "^5.8.25",
16 | "babel-loader": "^5.3.2",
17 | "elm-simple-loader": "^1.0.1",
18 | "webpack": "^1.12.2"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Cycle-Elm fun
6 |
29 |
30 |
31 |
32 | Press Up/Down/Left/Right or H/J/K/L to move the cursor! Double click the screen to clear your picture! :)
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/board-driver/index.js:
--------------------------------------------------------------------------------
1 | import Rx from 'rx';
2 |
3 | import Elm from './Board.elm';
4 |
5 | export default function makeBoardDriver() {
6 | return function boardDriver(model$) {
7 | let board;
8 | let screenClick$ = new Rx.Subject();
9 | let requestScreenClear$ = screenClick$
10 | .buffer(function() {
11 | return screenClick$.debounce(250);
12 | })
13 | .map(function (events) {
14 | return events.length;
15 | })
16 | .filter(function (clicks) {
17 | return clicks >= 2;
18 | });
19 |
20 | model$.first().subscribe(function (model) {
21 | board = Elm.fullscreen(Elm.Board, {
22 | model
23 | });
24 |
25 | board.ports.mouseClicks.subscribe(function () {
26 | screenClick$.onNext();
27 | });
28 | });
29 |
30 | model$.subscribe(function (model) {
31 | board.ports.model.send(model);
32 | });
33 |
34 | return {
35 | requestScreenClear$
36 | };
37 | };
38 | }
39 |
--------------------------------------------------------------------------------
/src/keyboard-driver/index.js:
--------------------------------------------------------------------------------
1 | import Rx from 'rx';
2 |
3 | export const UP = 'up';
4 | export const DOWN = 'down';
5 | export const LEFT = 'left';
6 | export const RIGHT = 'right';
7 |
8 | const UP_INPUTS = [38, 75];
9 | const DOWN_INPUTS = [40, 74];
10 | const LEFT_INPUTS = [37, 72];
11 | const RIGHT_INPUTS = [39, 76];
12 |
13 | const MAPPINGS = [
14 | [UP_INPUTS, UP],
15 | [DOWN_INPUTS, DOWN],
16 | [LEFT_INPUTS, LEFT],
17 | [RIGHT_INPUTS, RIGHT],
18 | ];
19 |
20 | export default function makeKeyboardDriver() {
21 | return function keyboardDriver() {
22 | const directionInput$ = Rx.Observable.fromEvent(window, 'keydown')
23 | .map(function ({keyCode}) {
24 | for (let i = 0; i < MAPPINGS.length; i++) {
25 | const [inputs, direction] = MAPPINGS[i];
26 |
27 | if (inputs.indexOf(keyCode) !== -1) {
28 | return direction;
29 | }
30 | }
31 | });
32 |
33 | return {
34 | directionInput$
35 | };
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/board-driver/Board.elm:
--------------------------------------------------------------------------------
1 | module Board where
2 |
3 | import Color exposing (Color, rgb)
4 | import Graphics.Collage exposing (Form, collage, rect, filled, move)
5 | import Graphics.Element exposing (Element)
6 | import Window
7 | import Mouse
8 |
9 | -- CONSTANTS
10 | cellSize : Float
11 | cellSize = 16
12 |
13 | -- MODEL
14 | -- 2-dimensional coordinates here
15 | type alias Coordinates = (Int, Int)
16 |
17 | type alias Model =
18 | {
19 | points: List Coordinates,
20 | cursor: Coordinates
21 | }
22 |
23 | -- VIEW
24 | drawPoint : Color -> Coordinates -> Form
25 | drawPoint color coords =
26 | let
27 | (x, y) = coords
28 | in
29 | rect cellSize cellSize
30 | |> filled color
31 | |> move (toFloat x * cellSize, toFloat y * cellSize)
32 |
33 | view : (Int, Int) -> Model -> Element
34 | view (w, h) model =
35 | let
36 | points =
37 | List.map (drawPoint (rgb 50 50 50)) model.points
38 | cursor =
39 | drawPoint (rgb 0 0 0) model.cursor
40 | in
41 | List.append points [cursor]
42 | |> collage w h
43 |
44 | -- SIGNALS
45 | port mouseClicks : Signal ()
46 | port mouseClicks = Mouse.clicks
47 |
48 | port model : Signal Model
49 |
50 | main : Signal Element
51 | main =
52 | Signal.map2 view Window.dimensions model
53 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import Rx from 'rx';
2 | import Cycle from '@cycle/core';
3 |
4 | import makeBoardDriver from './board-driver';
5 | import makeKeyboardDriver, {
6 | UP, DOWN, LEFT, RIGHT
7 | } from './keyboard-driver';
8 |
9 | function deduplicatePoints(points) {
10 | let newPoints = [];
11 | const pointsObject = points.reduce(function (aggregate, point) {
12 | aggregate[point.join(',')] = point;
13 | return aggregate;
14 | }, {});
15 |
16 | for (let key in pointsObject) {
17 | newPoints.push(pointsObject[key]);
18 | }
19 |
20 | return newPoints;
21 | }
22 |
23 | function addPoint(model) {
24 | let points = model.points.slice();
25 |
26 | points.push(model.cursor);
27 | return points;
28 | }
29 |
30 | function main(drivers) {
31 | const INITIAL_STATE = {
32 | points: [],
33 | cursor: [0, 0]
34 | };
35 |
36 | const moveCursor$ = drivers.keyboard.directionInput$
37 | .map(function (direction) {
38 | return function (model) {
39 | if (!direction) return model;
40 |
41 | const points = deduplicatePoints(addPoint(model));
42 | let [cursorX, cursorY] = model.cursor;
43 |
44 | switch (direction) {
45 | case UP:
46 | cursorY++;
47 | break;
48 | case DOWN:
49 | cursorY--;
50 | break;
51 | case LEFT:
52 | cursorX--;
53 | break;
54 | case RIGHT:
55 | cursorX++;
56 | break;
57 | }
58 |
59 | return {
60 | points,
61 | cursor: [cursorX, cursorY]
62 | };
63 | };
64 | });
65 |
66 | const clearScreen$ = drivers.board.requestScreenClear$
67 | .map(function () {
68 | return function (model) {
69 | return {
70 | points: [],
71 | cursor: model.cursor
72 | };
73 | };
74 | });
75 |
76 | const state$ = Rx.Observable
77 | .merge(
78 | moveCursor$,
79 | clearScreen$
80 | )
81 | .startWith(INITIAL_STATE)
82 | .scan(function (state, mapper) {
83 | return mapper(state);
84 | })
85 |
86 | return {
87 | board: state$
88 | };
89 | }
90 |
91 | let drivers = {
92 | keyboard: makeKeyboardDriver(),
93 | board: makeBoardDriver()
94 | };
95 |
96 | Cycle.run(main, drivers);
97 |
--------------------------------------------------------------------------------