2018-02-04 09:51:03 +01:00
|
|
|
// Copyright 2018 The Ebiten Authors
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
// Package inpututil provides utility functions of input like keyboard or mouse.
|
|
|
|
//
|
|
|
|
// Note: This package is experimental and API might be changed.
|
|
|
|
package inpututil
|
|
|
|
|
|
|
|
import (
|
2018-04-29 19:43:08 +02:00
|
|
|
"sort"
|
|
|
|
|
2018-02-04 09:51:03 +01:00
|
|
|
"github.com/hajimehoshi/ebiten"
|
|
|
|
"github.com/hajimehoshi/ebiten/internal/hooks"
|
|
|
|
"github.com/hajimehoshi/ebiten/internal/sync"
|
|
|
|
)
|
|
|
|
|
|
|
|
type inputState struct {
|
2018-04-29 19:19:53 +02:00
|
|
|
keyDurations map[ebiten.Key]int
|
|
|
|
prevKeyDurations map[ebiten.Key]int
|
2018-02-16 19:10:40 +01:00
|
|
|
|
2018-04-29 19:19:53 +02:00
|
|
|
mouseButtonDurations map[ebiten.MouseButton]int
|
|
|
|
prevMouseButtonDurations map[ebiten.MouseButton]int
|
2018-02-16 19:10:40 +01:00
|
|
|
|
2018-04-29 19:51:38 +02:00
|
|
|
gamepadIDs map[int]struct{}
|
|
|
|
prevGamepadIDs map[int]struct{}
|
|
|
|
|
2018-04-29 19:19:53 +02:00
|
|
|
gamepadButtonDurations map[int]map[ebiten.GamepadButton]int
|
|
|
|
prevGamepadButtonDurations map[int]map[ebiten.GamepadButton]int
|
2018-02-16 19:41:45 +01:00
|
|
|
|
2018-04-29 19:19:53 +02:00
|
|
|
touchDurations map[int]int
|
|
|
|
prevTouchDurations map[int]int
|
2018-02-04 09:51:03 +01:00
|
|
|
|
|
|
|
m sync.RWMutex
|
|
|
|
}
|
|
|
|
|
|
|
|
var theInputState = &inputState{
|
2018-04-29 19:19:53 +02:00
|
|
|
keyDurations: map[ebiten.Key]int{},
|
|
|
|
prevKeyDurations: map[ebiten.Key]int{},
|
2018-02-16 19:10:40 +01:00
|
|
|
|
2018-04-29 19:19:53 +02:00
|
|
|
mouseButtonDurations: map[ebiten.MouseButton]int{},
|
|
|
|
prevMouseButtonDurations: map[ebiten.MouseButton]int{},
|
2018-02-16 19:10:40 +01:00
|
|
|
|
2018-04-29 19:51:38 +02:00
|
|
|
gamepadIDs: map[int]struct{}{},
|
|
|
|
prevGamepadIDs: map[int]struct{}{},
|
|
|
|
|
2018-04-29 19:19:53 +02:00
|
|
|
gamepadButtonDurations: map[int]map[ebiten.GamepadButton]int{},
|
|
|
|
prevGamepadButtonDurations: map[int]map[ebiten.GamepadButton]int{},
|
2018-02-16 19:41:45 +01:00
|
|
|
|
2018-04-29 19:19:53 +02:00
|
|
|
touchDurations: map[int]int{},
|
|
|
|
prevTouchDurations: map[int]int{},
|
2018-02-04 09:51:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
2018-03-14 18:50:10 +01:00
|
|
|
hooks.AppendHookOnBeforeUpdate(func() error {
|
2018-02-04 09:51:03 +01:00
|
|
|
theInputState.update()
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *inputState) update() {
|
|
|
|
i.m.Lock()
|
|
|
|
defer i.m.Unlock()
|
|
|
|
|
2018-02-05 15:45:43 +01:00
|
|
|
// Keyboard
|
2018-02-04 15:17:49 +01:00
|
|
|
for k := ebiten.Key(0); k <= ebiten.KeyMax; k++ {
|
2018-04-29 19:19:53 +02:00
|
|
|
i.prevKeyDurations[k] = i.keyDurations[k]
|
2018-02-04 09:51:03 +01:00
|
|
|
if ebiten.IsKeyPressed(k) {
|
2018-04-29 19:19:53 +02:00
|
|
|
i.keyDurations[k]++
|
2018-02-04 09:51:03 +01:00
|
|
|
} else {
|
2018-04-29 19:19:53 +02:00
|
|
|
i.keyDurations[k] = 0
|
2018-02-04 09:51:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-05 15:45:43 +01:00
|
|
|
// Mouse
|
2018-02-04 09:51:03 +01:00
|
|
|
for _, b := range []ebiten.MouseButton{
|
|
|
|
ebiten.MouseButtonLeft,
|
|
|
|
ebiten.MouseButtonRight,
|
|
|
|
ebiten.MouseButtonMiddle,
|
|
|
|
} {
|
2018-04-29 19:19:53 +02:00
|
|
|
i.prevMouseButtonDurations[b] = i.mouseButtonDurations[b]
|
2018-02-04 09:51:03 +01:00
|
|
|
if ebiten.IsMouseButtonPressed(b) {
|
2018-04-29 19:19:53 +02:00
|
|
|
i.mouseButtonDurations[b]++
|
2018-02-04 09:51:03 +01:00
|
|
|
} else {
|
2018-04-29 19:19:53 +02:00
|
|
|
i.mouseButtonDurations[b] = 0
|
2018-02-04 09:51:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-05 15:45:43 +01:00
|
|
|
// Gamepads
|
2018-02-16 19:41:45 +01:00
|
|
|
|
2018-04-29 19:51:38 +02:00
|
|
|
// Copy the gamepad IDs
|
|
|
|
i.prevGamepadIDs = map[int]struct{}{}
|
|
|
|
for id := range i.gamepadIDs {
|
|
|
|
i.prevGamepadIDs[id] = struct{}{}
|
|
|
|
}
|
|
|
|
|
2018-02-16 19:41:45 +01:00
|
|
|
// Reset the previous states first since some gamepad IDs might be already gone.
|
2018-04-29 19:19:53 +02:00
|
|
|
for id := range i.prevGamepadButtonDurations {
|
|
|
|
for b := range i.prevGamepadButtonDurations[id] {
|
|
|
|
i.prevGamepadButtonDurations[id][b] = 0
|
2018-02-16 19:41:45 +01:00
|
|
|
}
|
|
|
|
}
|
2018-04-29 19:51:38 +02:00
|
|
|
|
|
|
|
i.gamepadIDs = map[int]struct{}{}
|
2018-02-04 09:51:03 +01:00
|
|
|
for _, id := range ebiten.GamepadIDs() {
|
2018-04-29 19:51:38 +02:00
|
|
|
i.gamepadIDs[id] = struct{}{}
|
2018-02-16 19:41:45 +01:00
|
|
|
|
2018-04-29 19:19:53 +02:00
|
|
|
if _, ok := i.prevGamepadButtonDurations[id]; !ok {
|
|
|
|
i.prevGamepadButtonDurations[id] = map[ebiten.GamepadButton]int{}
|
2018-02-16 19:41:45 +01:00
|
|
|
}
|
2018-04-29 19:19:53 +02:00
|
|
|
if _, ok := i.gamepadButtonDurations[id]; !ok {
|
|
|
|
i.gamepadButtonDurations[id] = map[ebiten.GamepadButton]int{}
|
2018-02-04 09:51:03 +01:00
|
|
|
}
|
2018-02-16 19:41:45 +01:00
|
|
|
|
2018-02-04 09:51:03 +01:00
|
|
|
n := ebiten.GamepadButtonNum(id)
|
|
|
|
for b := ebiten.GamepadButton(0); b < ebiten.GamepadButton(n); b++ {
|
2018-04-29 19:19:53 +02:00
|
|
|
i.prevGamepadButtonDurations[id][b] = i.gamepadButtonDurations[id][b]
|
2018-02-04 09:51:03 +01:00
|
|
|
if ebiten.IsGamepadButtonPressed(id, b) {
|
2018-04-29 19:19:53 +02:00
|
|
|
i.gamepadButtonDurations[id][b]++
|
2018-02-04 09:51:03 +01:00
|
|
|
} else {
|
2018-04-29 19:19:53 +02:00
|
|
|
i.gamepadButtonDurations[id][b] = 0
|
2018-02-04 09:51:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-02-05 15:45:43 +01:00
|
|
|
idsToDelete := []int{}
|
2018-04-29 19:19:53 +02:00
|
|
|
for id := range i.gamepadButtonDurations {
|
2018-04-29 19:51:38 +02:00
|
|
|
if _, ok := i.gamepadIDs[id]; !ok {
|
2018-02-05 15:45:43 +01:00
|
|
|
idsToDelete = append(idsToDelete, id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, id := range idsToDelete {
|
2018-04-29 19:19:53 +02:00
|
|
|
delete(i.gamepadButtonDurations, id)
|
2018-02-05 15:45:43 +01:00
|
|
|
}
|
2018-02-04 09:51:03 +01:00
|
|
|
|
2018-02-05 15:45:43 +01:00
|
|
|
// Touches
|
2018-04-29 19:51:38 +02:00
|
|
|
ids := map[int]struct{}{}
|
2018-02-16 19:58:19 +01:00
|
|
|
|
|
|
|
// Reset the previous states first since some gamepad IDs might be already gone.
|
2018-04-29 19:19:53 +02:00
|
|
|
for id := range i.prevTouchDurations {
|
|
|
|
i.prevTouchDurations[id] = 0
|
2018-02-16 19:58:19 +01:00
|
|
|
}
|
|
|
|
|
2018-02-04 09:51:03 +01:00
|
|
|
for _, t := range ebiten.Touches() {
|
|
|
|
ids[t.ID()] = struct{}{}
|
2018-04-29 19:19:53 +02:00
|
|
|
i.prevTouchDurations[t.ID()] = i.touchDurations[t.ID()]
|
|
|
|
i.touchDurations[t.ID()]++
|
2018-02-04 09:51:03 +01:00
|
|
|
}
|
2018-02-05 15:45:43 +01:00
|
|
|
idsToDelete = []int{}
|
2018-04-29 19:19:53 +02:00
|
|
|
for id := range i.touchDurations {
|
2018-02-04 09:51:03 +01:00
|
|
|
if _, ok := ids[id]; !ok {
|
|
|
|
idsToDelete = append(idsToDelete, id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, id := range idsToDelete {
|
2018-04-29 19:19:53 +02:00
|
|
|
delete(i.touchDurations, id)
|
2018-02-04 09:51:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsKeyJustPressed returns a boolean value indicating
|
|
|
|
// whether the given key is pressed just in the current frame.
|
2018-02-16 19:12:58 +01:00
|
|
|
//
|
|
|
|
// IsKeyJustPressed is concurrent safe.
|
2018-02-04 09:51:03 +01:00
|
|
|
func IsKeyJustPressed(key ebiten.Key) bool {
|
|
|
|
return KeyPressDuration(key) == 1
|
|
|
|
}
|
|
|
|
|
2018-02-16 19:10:40 +01:00
|
|
|
// IsKeyJustReleased returns a boolean value indicating
|
|
|
|
// whether the given key is released just in the current frame.
|
2018-02-16 19:12:58 +01:00
|
|
|
//
|
|
|
|
// IsKeyJustReleased is concurrent safe.
|
2018-02-16 19:10:40 +01:00
|
|
|
func IsKeyJustReleased(key ebiten.Key) bool {
|
|
|
|
theInputState.m.RLock()
|
2018-04-29 19:19:53 +02:00
|
|
|
r := theInputState.keyDurations[key] == 0 && theInputState.prevKeyDurations[key] > 0
|
2018-02-16 19:10:40 +01:00
|
|
|
theInputState.m.RUnlock()
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2018-02-04 09:51:03 +01:00
|
|
|
// KeyPressDuration returns how long the key is pressed in frames.
|
2018-02-16 19:12:58 +01:00
|
|
|
//
|
|
|
|
// KeyPressDuration is concurrent safe.
|
2018-02-04 09:51:03 +01:00
|
|
|
func KeyPressDuration(key ebiten.Key) int {
|
|
|
|
theInputState.m.RLock()
|
2018-04-29 19:19:53 +02:00
|
|
|
s := theInputState.keyDurations[key]
|
2018-02-04 09:51:03 +01:00
|
|
|
theInputState.m.RUnlock()
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsMouseButtonJustPressed returns a boolean value indicating
|
|
|
|
// whether the given mouse button is pressed just in the current frame.
|
2018-02-16 19:12:58 +01:00
|
|
|
//
|
|
|
|
// IsMouseButtonJustPressed is concurrent safe.
|
2018-02-04 09:51:03 +01:00
|
|
|
func IsMouseButtonJustPressed(button ebiten.MouseButton) bool {
|
|
|
|
return MouseButtonPressDuration(button) == 1
|
|
|
|
}
|
|
|
|
|
2018-02-16 19:10:40 +01:00
|
|
|
// IsMouseButtonJustReleased returns a boolean value indicating
|
|
|
|
// whether the given mouse button is released just in the current frame.
|
2018-02-16 19:12:58 +01:00
|
|
|
//
|
|
|
|
// IsMouseButtonJustReleased is concurrent safe.
|
2018-02-16 19:10:40 +01:00
|
|
|
func IsMouseButtonJustReleased(button ebiten.MouseButton) bool {
|
|
|
|
theInputState.m.RLock()
|
2018-04-29 19:19:53 +02:00
|
|
|
r := theInputState.mouseButtonDurations[button] == 0 &&
|
|
|
|
theInputState.prevMouseButtonDurations[button] > 0
|
2018-02-16 19:10:40 +01:00
|
|
|
theInputState.m.RUnlock()
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2018-02-04 09:51:03 +01:00
|
|
|
// MouseButtonPressDuration returns how long the mouse button is pressed in frames.
|
2018-02-16 19:12:58 +01:00
|
|
|
//
|
|
|
|
// MouseButtonPressDuration is concurrent safe.
|
2018-02-04 09:51:03 +01:00
|
|
|
func MouseButtonPressDuration(button ebiten.MouseButton) int {
|
|
|
|
theInputState.m.RLock()
|
2018-04-29 19:19:53 +02:00
|
|
|
s := theInputState.mouseButtonDurations[button]
|
2018-02-04 09:51:03 +01:00
|
|
|
theInputState.m.RUnlock()
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2018-04-29 19:52:56 +02:00
|
|
|
// JustConnectedGamepadIDs returns gamepad IDs that are connected just in the current frame.
|
|
|
|
//
|
2018-04-29 20:03:12 +02:00
|
|
|
// JustConnectedGamepadIDs might return nil when there is no connected gamepad.
|
|
|
|
//
|
2018-04-29 19:52:56 +02:00
|
|
|
// JustConnectedGamepadIDs is concurrent safe.
|
2018-04-29 19:51:38 +02:00
|
|
|
func JustConnectedGamepadIDs() []int {
|
2018-04-29 20:03:12 +02:00
|
|
|
var ids []int
|
2018-04-29 19:51:38 +02:00
|
|
|
theInputState.m.RLock()
|
|
|
|
for id := range theInputState.gamepadIDs {
|
|
|
|
if _, ok := theInputState.prevGamepadIDs[id]; !ok {
|
|
|
|
ids = append(ids, id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
theInputState.m.RUnlock()
|
|
|
|
sort.Ints(ids)
|
|
|
|
return ids
|
|
|
|
}
|
|
|
|
|
2018-04-29 19:52:56 +02:00
|
|
|
// JustDisconnectedGamepadIDs returns gamepad IDs that are disconnected just in the current frame.
|
|
|
|
//
|
2018-04-29 20:03:12 +02:00
|
|
|
// JustDisconnectedGamepadIDs might return nil when there is no disconnected gamepad.
|
|
|
|
//
|
2018-04-29 19:52:56 +02:00
|
|
|
// JustDisconnectedGamepadIDs is concurrent safe.
|
2018-04-29 19:51:38 +02:00
|
|
|
func JustDisconnectedGamepadIDs() []int {
|
2018-04-29 20:03:12 +02:00
|
|
|
var ids []int
|
2018-04-29 19:51:38 +02:00
|
|
|
theInputState.m.RLock()
|
|
|
|
for id := range theInputState.prevGamepadIDs {
|
|
|
|
if _, ok := theInputState.gamepadIDs[id]; !ok {
|
|
|
|
ids = append(ids, id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
theInputState.m.RUnlock()
|
|
|
|
sort.Ints(ids)
|
|
|
|
return ids
|
|
|
|
}
|
|
|
|
|
2018-02-04 09:51:03 +01:00
|
|
|
// IsGamepadButtonJustPressed returns a boolean value indicating
|
|
|
|
// whether the given gamepad button of the gamepad id is pressed just in the current frame.
|
2018-02-16 19:12:58 +01:00
|
|
|
//
|
|
|
|
// IsGamepadButtonJustPressed is concurrent safe.
|
2018-02-04 09:51:03 +01:00
|
|
|
func IsGamepadButtonJustPressed(id int, button ebiten.GamepadButton) bool {
|
|
|
|
return GamepadButtonPressDuration(id, button) == 1
|
|
|
|
}
|
|
|
|
|
2018-02-16 19:41:45 +01:00
|
|
|
// IsGamepadButtonJustReleased returns a boolean value indicating
|
|
|
|
// whether the given gamepad button of the gamepad id is released just in the current frame.
|
|
|
|
//
|
|
|
|
// IsGamepadButtonJustReleased is concurrent safe.
|
|
|
|
func IsGamepadButtonJustReleased(id int, button ebiten.GamepadButton) bool {
|
|
|
|
theInputState.m.RLock()
|
|
|
|
prev := 0
|
2018-04-29 19:19:53 +02:00
|
|
|
if _, ok := theInputState.prevGamepadButtonDurations[id]; ok {
|
|
|
|
prev = theInputState.prevGamepadButtonDurations[id][button]
|
2018-02-16 19:41:45 +01:00
|
|
|
}
|
|
|
|
current := 0
|
2018-04-29 19:19:53 +02:00
|
|
|
if _, ok := theInputState.gamepadButtonDurations[id]; ok {
|
|
|
|
current = theInputState.gamepadButtonDurations[id][button]
|
2018-02-16 19:41:45 +01:00
|
|
|
}
|
|
|
|
theInputState.m.RUnlock()
|
|
|
|
return current == 0 && prev > 0
|
|
|
|
}
|
|
|
|
|
2018-02-04 09:51:03 +01:00
|
|
|
// GamepadButtonPressDuration returns how long the gamepad button of the gamepad id is pressed in frames.
|
2018-02-16 19:12:58 +01:00
|
|
|
//
|
|
|
|
// GamepadButtonPressDuration is concurrent safe.
|
2018-02-04 09:51:03 +01:00
|
|
|
func GamepadButtonPressDuration(id int, button ebiten.GamepadButton) int {
|
|
|
|
theInputState.m.RLock()
|
|
|
|
s := 0
|
2018-04-29 19:19:53 +02:00
|
|
|
if _, ok := theInputState.gamepadButtonDurations[id]; ok {
|
|
|
|
s = theInputState.gamepadButtonDurations[id][button]
|
2018-02-04 09:51:03 +01:00
|
|
|
}
|
|
|
|
theInputState.m.RUnlock()
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2018-04-07 21:59:13 +02:00
|
|
|
// JustPressedTouches returns touch IDs that are created just in the current frame.
|
2018-02-16 19:12:58 +01:00
|
|
|
//
|
2018-04-29 20:03:12 +02:00
|
|
|
// JustPressedTouches might return nil when there is not touch.
|
|
|
|
//
|
2018-04-07 21:59:13 +02:00
|
|
|
// JustPressedTouches is concurrent safe.
|
|
|
|
func JustPressedTouches() []int {
|
2018-04-29 20:03:12 +02:00
|
|
|
var ids []int
|
2018-04-07 21:59:13 +02:00
|
|
|
theInputState.m.RLock()
|
2018-04-29 19:19:53 +02:00
|
|
|
for id, s := range theInputState.touchDurations {
|
2018-04-07 21:59:13 +02:00
|
|
|
if s == 1 {
|
|
|
|
ids = append(ids, id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
theInputState.m.RUnlock()
|
2018-04-29 19:43:08 +02:00
|
|
|
sort.Ints(ids)
|
2018-04-07 21:59:13 +02:00
|
|
|
return ids
|
2018-02-04 09:51:03 +01:00
|
|
|
}
|
|
|
|
|
2018-02-16 19:58:19 +01:00
|
|
|
// IsTouchJustReleased returns a boolean value indicating
|
|
|
|
// whether the given touch is released just in the current frame.
|
|
|
|
//
|
|
|
|
// IsTouchJustReleased is concurrent safe.
|
|
|
|
func IsTouchJustReleased(id int) bool {
|
|
|
|
theInputState.m.RLock()
|
2018-04-29 19:19:53 +02:00
|
|
|
r := theInputState.touchDurations[id] == 0 && theInputState.prevTouchDurations[id] > 0
|
2018-02-16 19:58:19 +01:00
|
|
|
theInputState.m.RUnlock()
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2018-02-04 09:51:03 +01:00
|
|
|
// TouchDuration returns how long the touch remains in frames.
|
2018-02-16 19:12:58 +01:00
|
|
|
//
|
|
|
|
// TouchDuration is concurrent safe.
|
2018-02-04 09:51:03 +01:00
|
|
|
func TouchDuration(id int) int {
|
|
|
|
theInputState.m.RLock()
|
2018-04-29 19:19:53 +02:00
|
|
|
s := theInputState.touchDurations[id]
|
2018-02-04 09:51:03 +01:00
|
|
|
theInputState.m.RUnlock()
|
|
|
|
return s
|
|
|
|
}
|