Convenience helpers for working with keyboard inputs.
Using Keyboard.Extra this way, you get all the help it can provide. Use either this approach, or the plain subscriptions and handle the state yourself.
Keyboard.Extra
's internal message type.
The subscriptions needed for the "Msg and Update" way.
Use this (or updateWithKeyChange
) to have the list of keys update.
If you need to know exactly what changed just now, have a look
at updateWithKeyChange
.
This alternate update function answers the question: "Did the pressed down keys in fact change just now?"
You might be wondering why this is a Maybe KeyChange
– it's because
keydown
events happen many times per second when you hold down a key. Thus,
not all incoming messages actually cause a change in the model.
Note This is provided for convenience, and may not perform well in real
programs. If you are experiencing slowness or jittering when using
updateWithKeyChange
, see if the regular update
makes it go away.
The second value updateWithKeyChange
may return, representing the actual
change that happened during the update.
Note: To find out if a key is being pressed, simply use
List.member key keyList
.
Record type used for arrows
and wasd
.
Both x
and y
can range from -1
to 1
, and are 0
if no keys are pressed.
Gives the arrow keys' pressed down state as follows:
arrows [] --> { x = 0, y = 0 }
arrows [ ArrowLeft ] --> { x = -1, y = 0 }
arrows [ ArrowUp, ArrowRight ] --> { x = 1, y = 1 }
arrows [ ArrowDown, ArrowLeft, ArrowRight ]
--> { x = 0, y = -1 }
Similar to arrows
, gives the W, A, S and D keys' pressed down state.
wasd [] --> { x = 0, y = 0 }
wasd [ CharA ] --> { x = -1, y = 0 }
wasd [ CharW, CharD ] --> { x = 1, y = 1 }
wasd [ CharA, CharS, CharD ] --> { x = 0, y = -1 }
Type representation of the arrows.
Gives the arrow keys' pressed down state as follows:
arrowsDirection [] --> NoDirection
arrowsDirection [ ArrowLeft ] --> West
arrowsDirection [ ArrowUp, ArrowRight ] --> NorthEast
arrowsDirection [ ArrowDown, ArrowLeft, ArrowRight ]
--> South
Similar to arrows
, gives the W, A, S and D keys' pressed down state.
wasdDirection [] --> NoDirection
wasdDirection [ CharA ] --> West
wasdDirection [ CharW, CharD ] --> NorthEast
wasdDirection [ CharA, CharS, CharD ] --> South
If you prefer to only get "the facts" and do your own handling, use these subscriptions. Otherwise, you may be more comfortable with the Msg and Update.
Subscription for key down events.
Note When the user presses and holds a key, there may or may not be many of these messages before the corresponding key up message.
Subscription for key up events.
A Json.Decoder
for grabbing event.keyCode
and turning it into a Key
import Json.Decode as Json
onKey : (Key -> msg) -> Attribute msg
onKey tagger =
on "keydown" (Json.map tagger targetKey)
Convert a key code into a Key
.
fromCode 13 --> Enter
Convert a Key
into a key code.
toCode Enter --> 13
These are all the keys that have names in Keyboard.Extra
.
module Keyboard.Extra
exposing
( Arrows
, Direction(..)
, Key(..)
, KeyChange(..)
, Msg
, arrows
, arrowsDirection
, downs
, fromCode
, subscriptions
, targetKey
, toCode
, update
, updateWithKeyChange
, ups
, wasd
, wasdDirection
)
{-| Convenience helpers for working with keyboard inputs.
# Msg and Update
Using Keyboard.Extra this way, you get all the help it can provide.
Use either this approach, or the plain subscriptions and handle the state yourself.
@docs Msg, subscriptions, update, updateWithKeyChange, KeyChange
## Helpers
> **Note:** To find out if a key is being pressed, simply use `List.member key keyList`.
@docs Arrows, arrows, wasd, Direction, arrowsDirection, wasdDirection
# Plain Subscriptions
If you prefer to only get "the facts" and do your own handling, use these
subscriptions. Otherwise, you may be more comfortable with the Msg and Update.
@docs downs, ups
# Decoder
@docs targetKey
# Low level
@docs fromCode, toCode
# Keyboard keys
@docs Key
-}
import Dict exposing (Dict)
import Json.Decode as Json
import Keyboard exposing (KeyCode)
{-| Subscription for key down events.
**Note** When the user presses and holds a key, there may or may not be many of
these messages before the corresponding key up message.
-}
downs : (Key -> msg) -> Sub msg
downs toMsg =
Keyboard.downs (toMsg << fromCode)
{-| Subscription for key up events.
-}
ups : (Key -> msg) -> Sub msg
ups toMsg =
Keyboard.ups (toMsg << fromCode)
{-| `Keyboard.Extra`'s internal message type.
-}
type Msg
= Down Key
| Up Key
{-| Record type used for `arrows` and `wasd`.
Both `x` and `y` can range from `-1` to `1`, and are `0` if no keys are pressed.
-}
type alias Arrows =
{ x : Int, y : Int }
{-| The subscriptions needed for the "Msg and Update" way.
-}
subscriptions : Sub Msg
subscriptions =
Sub.batch
[ Keyboard.downs (Down << fromCode)
, Keyboard.ups (Up << fromCode)
]
insert : Key -> List Key -> List Key
insert code list =
list
|> remove code
|> (::) code
remove : Key -> List Key -> List Key
remove code list =
list
|> List.filter ((/=) code)
{-| Use this (or `updateWithKeyChange`) to have the list of keys update.
_If you need to know exactly what changed just now, have a look
at `updateWithKeyChange`._
-}
update : Msg -> List Key -> List Key
update msg state =
case msg of
Down key ->
insert key state
Up key ->
remove key state
{-| The second value `updateWithKeyChange` may return, representing the actual
change that happened during the update.
-}
type KeyChange
= KeyDown Key
| KeyUp Key
{-| This alternate update function answers the question: "Did the pressed down
keys in fact change just now?"
You might be wondering why this is a `Maybe KeyChange` – it's because
`keydown` events happen many times per second when you hold down a key. Thus,
not all incoming messages actually cause a change in the model.
**Note** This is provided for convenience, and may not perform well in real
programs. If you are experiencing slowness or jittering when using
`updateWithKeyChange`, see if the regular `update` makes it go away.
-}
updateWithKeyChange : Msg -> List Key -> ( List Key, Maybe KeyChange )
updateWithKeyChange msg state =
case msg of
Down key ->
let
nextState =
insert key state
change =
if List.length nextState /= List.length state then
Just (KeyDown key)
else
Nothing
in
( nextState, change )
Up key ->
let
nextState =
remove key state
change =
if List.length nextState /= List.length state then
Just (KeyUp key)
else
Nothing
in
( nextState, change )
{-| Gives the arrow keys' pressed down state as follows:
arrows [] --> { x = 0, y = 0 }
arrows [ ArrowLeft ] --> { x = -1, y = 0 }
arrows [ ArrowUp, ArrowRight ] --> { x = 1, y = 1 }
arrows [ ArrowDown, ArrowLeft, ArrowRight ]
--> { x = 0, y = -1 }
-}
arrows : List Key -> Arrows
arrows keys =
let
toInt key =
keys
|> List.member key
|> boolToInt
x =
toInt ArrowRight - toInt ArrowLeft
y =
toInt ArrowUp - toInt ArrowDown
in
{ x = x, y = y }
{-| Similar to `arrows`, gives the W, A, S and D keys' pressed down state.
wasd [] --> { x = 0, y = 0 }
wasd [ CharA ] --> { x = -1, y = 0 }
wasd [ CharW, CharD ] --> { x = 1, y = 1 }
wasd [ CharA, CharS, CharD ] --> { x = 0, y = -1 }
-}
wasd : List Key -> Arrows
wasd keys =
let
toInt key =
keys
|> List.member key
|> boolToInt
x =
toInt CharD - toInt CharA
y =
toInt CharW - toInt CharS
in
{ x = x, y = y }
{-| Type representation of the arrows.
-}
type Direction
= North
| NorthEast
| East
| SouthEast
| South
| SouthWest
| West
| NorthWest
| NoDirection
{-| Gives the arrow keys' pressed down state as follows:
arrowsDirection [] --> NoDirection
arrowsDirection [ ArrowLeft ] --> West
arrowsDirection [ ArrowUp, ArrowRight ] --> NorthEast
arrowsDirection [ ArrowDown, ArrowLeft, ArrowRight ]
--> South
-}
arrowsDirection : List Key -> Direction
arrowsDirection =
arrowsToDir << arrows
{-| Similar to `arrows`, gives the W, A, S and D keys' pressed down state.
wasdDirection [] --> NoDirection
wasdDirection [ CharA ] --> West
wasdDirection [ CharW, CharD ] --> NorthEast
wasdDirection [ CharA, CharS, CharD ] --> South
-}
wasdDirection : List Key -> Direction
wasdDirection =
arrowsToDir << wasd
arrowsToDir : Arrows -> Direction
arrowsToDir { x, y } =
case ( x, y ) of
( 0, 1 ) ->
North
( 1, 1 ) ->
NorthEast
( 1, 0 ) ->
East
( 1, -1 ) ->
SouthEast
( 0, -1 ) ->
South
( -1, -1 ) ->
SouthWest
( -1, 0 ) ->
West
( -1, 1 ) ->
NorthWest
_ ->
NoDirection
{-| Convert a key code into a `Key`.
fromCode 13 --> Enter
-}
fromCode : KeyCode -> Key
fromCode code =
codeDict
|> Dict.get code
|> Maybe.withDefault Other
{-| Convert a `Key` into a key code.
toCode Enter --> 13
-}
toCode : Key -> KeyCode
toCode key =
codeBook
|> List.filter ((==) key << Tuple.second)
|> List.map Tuple.first
|> List.head
|> Maybe.withDefault 0
{-| A `Json.Decoder` for grabbing `event.keyCode` and turning it into a `Key`
import Json.Decode as Json
onKey : (Key -> msg) -> Attribute msg
onKey tagger =
on "keydown" (Json.map tagger targetKey)
-}
targetKey : Json.Decoder Key
targetKey =
Json.map fromCode (Json.field "keyCode" Json.int)
boolToInt : Bool -> Int
boolToInt bool =
if bool then
1
else
0
{-| These are all the keys that have names in `Keyboard.Extra`.
-}
type Key
= Cancel
| Help
| BackSpace
| Tab
| Clear
| Enter
| Shift
| Control
| Alt
| Pause
| CapsLock
| Escape
| Convert
| NonConvert
| Accept
| ModeChange
| Space
| PageUp
| PageDown
| End
| Home
| ArrowLeft
| ArrowUp
| ArrowRight
| ArrowDown
| Select
| Print
| Execute
| PrintScreen
| Insert
| Delete
| Number0
| Number1
| Number2
| Number3
| Number4
| Number5
| Number6
| Number7
| Number8
| Number9
| Colon
| Semicolon
| LessThan
| Equals
| GreaterThan
| QuestionMark
| At
| CharA
| CharB
| CharC
| CharD
| CharE
| CharF
| CharG
| CharH
| CharI
| CharJ
| CharK
| CharL
| CharM
| CharN
| CharO
| CharP
| CharQ
| CharR
| CharS
| CharT
| CharU
| CharV
| CharW
| CharX
| CharY
| CharZ
| Super
| ContextMenu
| Sleep
| Numpad0
| Numpad1
| Numpad2
| Numpad3
| Numpad4
| Numpad5
| Numpad6
| Numpad7
| Numpad8
| Numpad9
| Multiply
| Add
| Separator
| Subtract
| Decimal
| Divide
| F1
| F2
| F3
| F4
| F5
| F6
| F7
| F8
| F9
| F10
| F11
| F12
| F13
| F14
| F15
| F16
| F17
| F18
| F19
| F20
| F21
| F22
| F23
| F24
| NumLock
| ScrollLock
| Circumflex
| Exclamation
| DoubleQuote
| Hash
| Dollar
| Percent
| Ampersand
| Underscore
| OpenParen
| CloseParen
| Asterisk
| Plus
| Pipe
| HyphenMinus
| OpenCurlyBracket
| CloseCurlyBracket
| Tilde
| VolumeMute
| VolumeDown
| VolumeUp
| Comma
| Minus
| Period
| Slash
| BackQuote
| OpenBracket
| BackSlash
| CloseBracket
| Quote
| Meta
| Altgr
| Other
codeDict : Dict KeyCode Key
codeDict =
Dict.fromList codeBook
codeBook : List ( KeyCode, Key )
codeBook =
[ ( 3, Cancel )
, ( 6, Help )
, ( 8, BackSpace )
, ( 9, Tab )
, ( 12, Clear )
, ( 13, Enter )
, ( 16, Shift )
, ( 17, Control )
, ( 18, Alt )
, ( 19, Pause )
, ( 20, CapsLock )
, ( 27, Escape )
, ( 28, Convert )
, ( 29, NonConvert )
, ( 30, Accept )
, ( 31, ModeChange )
, ( 32, Space )
, ( 33, PageUp )
, ( 34, PageDown )
, ( 35, End )
, ( 36, Home )
, ( 37, ArrowLeft )
, ( 38, ArrowUp )
, ( 39, ArrowRight )
, ( 40, ArrowDown )
, ( 41, Select )
, ( 42, Print )
, ( 43, Execute )
, ( 44, PrintScreen )
, ( 45, Insert )
, ( 46, Delete )
, ( 48, Number0 )
, ( 49, Number1 )
, ( 50, Number2 )
, ( 51, Number3 )
, ( 52, Number4 )
, ( 53, Number5 )
, ( 54, Number6 )
, ( 55, Number7 )
, ( 56, Number8 )
, ( 57, Number9 )
, ( 58, Colon )
, ( 59, Semicolon )
, ( 60, LessThan )
, ( 61, Equals )
, ( 62, GreaterThan )
, ( 63, QuestionMark )
, ( 64, At )
, ( 65, CharA )
, ( 66, CharB )
, ( 67, CharC )
, ( 68, CharD )
, ( 69, CharE )
, ( 70, CharF )
, ( 71, CharG )
, ( 72, CharH )
, ( 73, CharI )
, ( 74, CharJ )
, ( 75, CharK )
, ( 76, CharL )
, ( 77, CharM )
, ( 78, CharN )
, ( 79, CharO )
, ( 80, CharP )
, ( 81, CharQ )
, ( 82, CharR )
, ( 83, CharS )
, ( 84, CharT )
, ( 85, CharU )
, ( 86, CharV )
, ( 87, CharW )
, ( 88, CharX )
, ( 89, CharY )
, ( 90, CharZ )
, ( 91, Super )
, ( 93, ContextMenu )
, ( 95, Sleep )
, ( 96, Numpad0 )
, ( 97, Numpad1 )
, ( 98, Numpad2 )
, ( 99, Numpad3 )
, ( 100, Numpad4 )
, ( 101, Numpad5 )
, ( 102, Numpad6 )
, ( 103, Numpad7 )
, ( 104, Numpad8 )
, ( 105, Numpad9 )
, ( 106, Multiply )
, ( 107, Add )
, ( 108, Separator )
, ( 109, Subtract )
, ( 110, Decimal )
, ( 111, Divide )
, ( 112, F1 )
, ( 113, F2 )
, ( 114, F3 )
, ( 115, F4 )
, ( 116, F5 )
, ( 117, F6 )
, ( 118, F7 )
, ( 119, F8 )
, ( 120, F9 )
, ( 121, F10 )
, ( 122, F11 )
, ( 123, F12 )
, ( 124, F13 )
, ( 125, F14 )
, ( 126, F15 )
, ( 127, F16 )
, ( 128, F17 )
, ( 129, F18 )
, ( 130, F19 )
, ( 131, F20 )
, ( 132, F21 )
, ( 133, F22 )
, ( 134, F23 )
, ( 135, F24 )
, ( 144, NumLock )
, ( 145, ScrollLock )
, ( 160, Circumflex )
, ( 161, Exclamation )
, ( 162, DoubleQuote )
, ( 163, Hash )
, ( 164, Dollar )
, ( 165, Percent )
, ( 166, Ampersand )
, ( 167, Underscore )
, ( 168, OpenParen )
, ( 169, CloseParen )
, ( 170, Asterisk )
, ( 171, Plus )
, ( 172, Pipe )
, ( 173, HyphenMinus )
, ( 174, OpenCurlyBracket )
, ( 175, CloseCurlyBracket )
, ( 176, Tilde )
, ( 181, VolumeMute )
, ( 182, VolumeDown )
, ( 183, VolumeUp )
, ( 186, Semicolon )
, ( 187, Equals )
, ( 188, Comma )
, ( 189, Minus )
, ( 190, Period )
, ( 191, Slash )
, ( 192, BackQuote )
, ( 219, OpenBracket )
, ( 220, BackSlash )
, ( 221, CloseBracket )
, ( 222, Quote )
, ( 224, Meta )
, ( 225, Altgr )
]