-
Notifications
You must be signed in to change notification settings - Fork 70
Expand file tree
/
Copy pathEvents.elm
More file actions
394 lines (259 loc) · 9.46 KB
/
Events.elm
File metadata and controls
394 lines (259 loc) · 9.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
effect module Browser.Events where { subscription = MySub } exposing
( onAnimationFrame, onAnimationFrameDelta
, onKeyPress, onKeyDown, onKeyUp
, onClick, onMouseMove, onMouseDown, onMouseUp
, onPointerMove, onPointerDown, onPointerUp
, onResize, onVisibilityChange, Visibility(..)
)
{-| In JavaScript, information about the root of an HTML document is held in
the `document` and `window` objects. This module lets you create event
listeners on those objects for the following topics: [animation](#animation),
[keyboard](#keyboard), [mouse](#mouse), and [window](#window).
If there is something else you need, use [ports] to do it in JavaScript!
[ports]: https://guide.elm-lang.org/interop/ports.html
# Animation
@docs onAnimationFrame, onAnimationFrameDelta
# Keyboard
@docs onKeyPress, onKeyDown, onKeyUp
# Mouse
@docs onClick, onMouseMove, onMouseDown, onMouseUp
# Pointer
@docs onPointerMove, onPointerDown, onPointerUp
# Window
@docs onResize, onVisibilityChange, Visibility
-}
import Browser.AnimationManager as AM
import Dict
import Elm.Kernel.Browser
import Json.Decode as Decode
import Process
import Task exposing (Task)
import Time
-- ANIMATION
{-| An animation frame triggers about 60 times per second. Get the POSIX time
on each frame. (See [`elm/time`](/packages/elm/time/latest) for more info on
POSIX times.)
**Note:** Browsers have their own render loop, repainting things as fast as
possible. If you want smooth animations in your application, it is helpful to
sync up with the browsers natural refresh rate. This hooks into JavaScript's
`requestAnimationFrame` function.
-}
onAnimationFrame : (Time.Posix -> msg) -> Sub msg
onAnimationFrame =
AM.onAnimationFrame
{-| Just like `onAnimationFrame`, except message is the time in milliseconds
since the previous frame. So you should get a sequence of values all around
`1000 / 60` which is nice for stepping animations by a time delta.
-}
onAnimationFrameDelta : (Float -> msg) -> Sub msg
onAnimationFrameDelta =
AM.onAnimationFrameDelta
-- KEYBOARD
{-| Subscribe to key presses that normally produce characters. So you should
not rely on this for arrow keys.
**Note:** Check out [this advice][note] to learn more about decoding key codes.
It is more complicated than it should be.
[note]: https://github.com/elm/browser/blob/1.0.2/notes/keyboard.md
-}
onKeyPress : Decode.Decoder msg -> Sub msg
onKeyPress =
on Document "keypress"
{-| Subscribe to get codes whenever a key goes down. This can be useful for
creating games. Maybe you want to know if people are pressing `w`, `a`, `s`,
or `d` at any given time.
**Note:** Check out [this advice][note] to learn more about decoding key codes.
It is more complicated than it should be.
[note]: https://github.com/elm/browser/blob/1.0.2/notes/keyboard.md
-}
onKeyDown : Decode.Decoder msg -> Sub msg
onKeyDown =
on Document "keydown"
{-| Subscribe to get codes whenever a key goes up. Often used in combination
with [`onVisibilityChange`](#onVisibilityChange) to be sure keys do not appear
to down and never come back up.
-}
onKeyUp : Decode.Decoder msg -> Sub msg
onKeyUp =
on Document "keyup"
-- MOUSE
{-| Subscribe to mouse clicks anywhere on screen. Maybe you need to create a
custom drop down. You could listen for clicks when it is open, letting you know
if someone clicked out of it:
import Browser.Events as Events
import Json.Decode as D
type Msg
= ClickOut
subscriptions : Model -> Sub Msg
subscriptions model =
case model.dropDown of
Closed _ ->
Sub.none
Open _ ->
Events.onClick (D.succeed ClickOut)
-}
onClick : Decode.Decoder msg -> Sub msg
onClick =
on Document "click"
{-| Subscribe to mouse moves anywhere on screen.
You could use this to implement resizable panels like in Elm's online code
editor. Check out the example imprementation [here][drag].
[drag]: https://github.com/elm/browser/blob/1.0.2/examples/src/Drag.elm
**Note:** Unsubscribe if you do not need these events! Running code on every
single mouse movement can be very costly, and it is recommended to only
subscribe when absolutely necessary.
-}
onMouseMove : Decode.Decoder msg -> Sub msg
onMouseMove =
on Document "mousemove"
{-| Subscribe to get mouse information whenever the mouse button goes down.
-}
onMouseDown : Decode.Decoder msg -> Sub msg
onMouseDown =
on Document "mousedown"
{-| Subscribe to get mouse information whenever the mouse button goes up.
Often used in combination with [`onVisibilityChange`](#onVisibilityChange)
to be sure keys do not appear to down and never come back up.
-}
onMouseUp : Decode.Decoder msg -> Sub msg
onMouseUp =
on Document "mouseup"
-- POINTER
{-| Subscribe to pointer moves anywhere on screen. This could be of any
pointer type (including mouse and touch).
**Note:** Unsubscribe if you do not need these events! Running code on every
single pointer movement can be very costly, and it is recommended to only
subscribe when absolutely necessary.
-}
onPointerMove : Decode.Decoder msg -> Sub msg
onPointerMove =
on Document "pointermove"
{-| Subscribe to get pointer information whenever the pointer goes down.
-}
onPointerDown : Decode.Decoder msg -> Sub msg
onPointerDown =
on Document "pointerdown"
{-| Subscribe to get pointer information whenever the pointer goes up.
Often used in combination with [`onVisibilityChange`](#onVisibilityChange)
to be sure keys do not appear to down and never come back up.
-}
onPointerUp : Decode.Decoder msg -> Sub msg
onPointerUp =
on Document "pointerup"
-- WINDOW
{-| Subscribe to any changes in window size.
For example, you could track the current width by saying:
import Browser.Events as E
type Msg
= GotNewWidth Int
subscriptions : model -> Cmd Msg
subscriptions _ =
E.onResize (\w h -> GotNewWidth w)
**Note:** This is equivalent to getting events from [`window.onresize`][resize].
[resize]: https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onresize
-}
onResize : (Int -> Int -> msg) -> Sub msg
onResize func =
on Window "resize" <|
Decode.field "target" <|
Decode.map2 func
(Decode.field "innerWidth" Decode.int)
(Decode.field "innerHeight" Decode.int)
{-| Subscribe to any visibility changes, like if the user switches to a
different tab or window. When the user looks away, you may want to:
- Pause a timer.
- Pause an animation.
- Pause video or audio.
- Pause an image carousel.
- Stop polling a server for new information.
- Stop waiting for an [`onKeyUp`](#onKeyUp) event.
-}
onVisibilityChange : (Visibility -> msg) -> Sub msg
onVisibilityChange func =
let
info = Elm.Kernel.Browser.visibilityInfo ()
in
on Document info.change <|
Decode.map (withHidden func) <|
Decode.field "target" <|
Decode.field info.hidden Decode.bool
withHidden : (Visibility -> msg) -> Bool -> msg
withHidden func isHidden =
func (if isHidden then Hidden else Visible)
{-| Value describing whether the page is hidden or visible.
-}
type Visibility
= Visible
| Hidden
-- SUBSCRIPTIONS
type Node
= Document
| Window
on : Node -> String -> Decode.Decoder msg -> Sub msg
on node name decoder =
subscription (MySub node name decoder)
type MySub msg
= MySub Node String (Decode.Decoder msg)
subMap : (a -> b) -> MySub a -> MySub b
subMap func (MySub node name decoder) =
MySub node name (Decode.map func decoder)
-- EFFECT MANAGER
type alias State msg =
{ subs : List ( String, MySub msg )
, pids : Dict.Dict String Process.Id
}
init : Task Never (State msg)
init =
Task.succeed (State [] Dict.empty)
type alias Event =
{ key : String
, event : Decode.Value
}
onSelfMsg : Platform.Router msg Event -> Event -> State msg -> Task Never (State msg)
onSelfMsg router { key, event } state =
let
toMessage ( subKey, MySub node name decoder ) =
if subKey == key then
Elm.Kernel.Browser.decodeEvent decoder event
else
Nothing
messages = List.filterMap toMessage state.subs
in
Task.sequence (List.map (Platform.sendToApp router) messages)
|> Task.andThen (\_ -> Task.succeed state)
onEffects : Platform.Router msg Event -> List (MySub msg) -> State msg -> Task Never (State msg)
onEffects router subs state =
let
newSubs = List.map addKey subs
stepLeft _ pid (deads, lives, news) =
(pid :: deads, lives, news)
stepBoth key pid _ (deads, lives, news) =
(deads, Dict.insert key pid lives, news)
stepRight key sub (deads, lives, news) =
(deads, lives, spawn router key sub :: news)
(deadPids, livePids, makeNewPids) =
Dict.merge stepLeft stepBoth stepRight state.pids (Dict.fromList newSubs) ([], Dict.empty, [])
in
Task.sequence (List.map Process.kill deadPids)
|> Task.andThen (\_ -> Task.sequence makeNewPids)
|> Task.andThen (\pids -> Task.succeed (State newSubs (Dict.union livePids (Dict.fromList pids))))
-- TO KEY
addKey : MySub msg -> ( String, MySub msg )
addKey ((MySub node name _) as sub) =
(nodeToKey node ++ name, sub)
nodeToKey : Node -> String
nodeToKey node =
case node of
Document -> "d_"
Window -> "w_"
-- SPAWN
spawn : Platform.Router msg Event -> String -> MySub msg -> Task Never ( String, Process.Id )
spawn router key (MySub node name _) =
let
actualNode =
case node of
Document -> Elm.Kernel.Browser.doc
Window -> Elm.Kernel.Browser.window
in
Task.map (\value -> ( key, value )) <|
Elm.Kernel.Browser.on actualNode name <|
\event -> Platform.sendToSelf router (Event key event)