zorldo

Goofing around with Ebiten
git clone git://bsandro.tech/zorldo
Log | Files | Refs | README

inpututil.go (14024B)


      1 // Copyright 2018 The Ebiten Authors
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //     http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 // Package inpututil provides utility functions of input like keyboard or mouse.
     16 package inpututil
     17 
     18 import (
     19 	"sort"
     20 	"sync"
     21 
     22 	"github.com/hajimehoshi/ebiten/v2"
     23 	"github.com/hajimehoshi/ebiten/v2/internal/hooks"
     24 )
     25 
     26 type inputState struct {
     27 	keyDurations     []int
     28 	prevKeyDurations []int
     29 
     30 	mouseButtonDurations     map[ebiten.MouseButton]int
     31 	prevMouseButtonDurations map[ebiten.MouseButton]int
     32 
     33 	gamepadIDs     map[ebiten.GamepadID]struct{}
     34 	prevGamepadIDs map[ebiten.GamepadID]struct{}
     35 
     36 	gamepadButtonDurations     map[ebiten.GamepadID][]int
     37 	prevGamepadButtonDurations map[ebiten.GamepadID][]int
     38 
     39 	standardGamepadButtonDurations     map[ebiten.GamepadID][]int
     40 	prevStandardGamepadButtonDurations map[ebiten.GamepadID][]int
     41 
     42 	touchIDs           map[ebiten.TouchID]struct{}
     43 	touchDurations     map[ebiten.TouchID]int
     44 	prevTouchDurations map[ebiten.TouchID]int
     45 
     46 	gamepadIDsBuf []ebiten.GamepadID
     47 	touchIDsBuf   []ebiten.TouchID
     48 
     49 	m sync.RWMutex
     50 }
     51 
     52 var theInputState = &inputState{
     53 	keyDurations:     make([]int, ebiten.KeyMax+1),
     54 	prevKeyDurations: make([]int, ebiten.KeyMax+1),
     55 
     56 	mouseButtonDurations:     map[ebiten.MouseButton]int{},
     57 	prevMouseButtonDurations: map[ebiten.MouseButton]int{},
     58 
     59 	gamepadIDs:     map[ebiten.GamepadID]struct{}{},
     60 	prevGamepadIDs: map[ebiten.GamepadID]struct{}{},
     61 
     62 	gamepadButtonDurations:     map[ebiten.GamepadID][]int{},
     63 	prevGamepadButtonDurations: map[ebiten.GamepadID][]int{},
     64 
     65 	standardGamepadButtonDurations:     map[ebiten.GamepadID][]int{},
     66 	prevStandardGamepadButtonDurations: map[ebiten.GamepadID][]int{},
     67 
     68 	touchIDs:           map[ebiten.TouchID]struct{}{},
     69 	touchDurations:     map[ebiten.TouchID]int{},
     70 	prevTouchDurations: map[ebiten.TouchID]int{},
     71 }
     72 
     73 func init() {
     74 	hooks.AppendHookOnBeforeUpdate(func() error {
     75 		theInputState.update()
     76 		return nil
     77 	})
     78 }
     79 
     80 func (i *inputState) update() {
     81 	i.m.Lock()
     82 	defer i.m.Unlock()
     83 
     84 	// Keyboard
     85 	copy(i.prevKeyDurations[:], i.keyDurations[:])
     86 	for k := ebiten.Key(0); k <= ebiten.KeyMax; k++ {
     87 		if ebiten.IsKeyPressed(k) {
     88 			i.keyDurations[k]++
     89 		} else {
     90 			i.keyDurations[k] = 0
     91 		}
     92 	}
     93 
     94 	// Mouse
     95 	for _, b := range []ebiten.MouseButton{
     96 		ebiten.MouseButtonLeft,
     97 		ebiten.MouseButtonRight,
     98 		ebiten.MouseButtonMiddle,
     99 	} {
    100 		i.prevMouseButtonDurations[b] = i.mouseButtonDurations[b]
    101 		if ebiten.IsMouseButtonPressed(b) {
    102 			i.mouseButtonDurations[b]++
    103 		} else {
    104 			i.mouseButtonDurations[b] = 0
    105 		}
    106 	}
    107 
    108 	// Gamepads
    109 
    110 	// Copy the gamepad IDs.
    111 	for id := range i.prevGamepadIDs {
    112 		delete(i.prevGamepadIDs, id)
    113 	}
    114 	for id := range i.gamepadIDs {
    115 		i.prevGamepadIDs[id] = struct{}{}
    116 	}
    117 
    118 	// Copy the gamepad button durations.
    119 	for id := range i.prevGamepadButtonDurations {
    120 		delete(i.prevGamepadButtonDurations, id)
    121 	}
    122 	for id, ds := range i.gamepadButtonDurations {
    123 		i.prevGamepadButtonDurations[id] = append([]int{}, ds...)
    124 	}
    125 
    126 	for id := range i.prevStandardGamepadButtonDurations {
    127 		delete(i.prevStandardGamepadButtonDurations, id)
    128 	}
    129 	for id, ds := range i.standardGamepadButtonDurations {
    130 		i.prevStandardGamepadButtonDurations[id] = append([]int{}, ds...)
    131 	}
    132 
    133 	for id := range i.gamepadIDs {
    134 		delete(i.gamepadIDs, id)
    135 	}
    136 	i.gamepadIDsBuf = ebiten.AppendGamepadIDs(i.gamepadIDsBuf[:0])
    137 	for _, id := range i.gamepadIDsBuf {
    138 		i.gamepadIDs[id] = struct{}{}
    139 
    140 		if _, ok := i.gamepadButtonDurations[id]; !ok {
    141 			i.gamepadButtonDurations[id] = make([]int, ebiten.GamepadButtonMax+1)
    142 		}
    143 		n := ebiten.GamepadButtonNum(id)
    144 		for b := ebiten.GamepadButton(0); b < ebiten.GamepadButton(n); b++ {
    145 			if ebiten.IsGamepadButtonPressed(id, b) {
    146 				i.gamepadButtonDurations[id][b]++
    147 			} else {
    148 				i.gamepadButtonDurations[id][b] = 0
    149 			}
    150 		}
    151 
    152 		if _, ok := i.standardGamepadButtonDurations[id]; !ok {
    153 			i.standardGamepadButtonDurations[id] = make([]int, ebiten.StandardGamepadButtonMax+1)
    154 		}
    155 		for b := ebiten.StandardGamepadButton(0); b <= ebiten.StandardGamepadButtonMax; b++ {
    156 			if ebiten.IsStandardGamepadButtonPressed(id, b) {
    157 				i.standardGamepadButtonDurations[id][b]++
    158 			} else {
    159 				i.standardGamepadButtonDurations[id][b] = 0
    160 			}
    161 		}
    162 	}
    163 	for id := range i.gamepadButtonDurations {
    164 		if _, ok := i.gamepadIDs[id]; !ok {
    165 			delete(i.gamepadButtonDurations, id)
    166 		}
    167 	}
    168 	for id := range i.standardGamepadButtonDurations {
    169 		if _, ok := i.gamepadIDs[id]; !ok {
    170 			delete(i.standardGamepadButtonDurations, id)
    171 		}
    172 	}
    173 
    174 	// Touches
    175 
    176 	// Copy the touch durations.
    177 	for id := range i.prevTouchDurations {
    178 		delete(i.prevTouchDurations, id)
    179 	}
    180 	for id := range i.touchDurations {
    181 		i.prevTouchDurations[id] = i.touchDurations[id]
    182 	}
    183 
    184 	for id := range i.touchIDs {
    185 		delete(i.touchIDs, id)
    186 	}
    187 	i.touchIDsBuf = ebiten.AppendTouchIDs(i.touchIDsBuf[:0])
    188 	for _, id := range i.touchIDsBuf {
    189 		i.touchIDs[id] = struct{}{}
    190 		i.touchDurations[id]++
    191 	}
    192 	for id := range i.touchDurations {
    193 		if _, ok := i.touchIDs[id]; !ok {
    194 			delete(i.touchDurations, id)
    195 		}
    196 	}
    197 }
    198 
    199 // AppendPressedKeys append currently pressed keyboard keys to keys and returns the extended buffer.
    200 // Giving a slice that already has enough capacity works efficiently.
    201 //
    202 // AppendPressedKeys is concurrent safe.
    203 func AppendPressedKeys(keys []ebiten.Key) []ebiten.Key {
    204 	theInputState.m.RLock()
    205 	defer theInputState.m.RUnlock()
    206 
    207 	for i, d := range theInputState.keyDurations {
    208 		if d == 0 {
    209 			continue
    210 		}
    211 		keys = append(keys, ebiten.Key(i))
    212 	}
    213 	return keys
    214 }
    215 
    216 // PressedKeys returns a set of currently pressed keyboard keys.
    217 //
    218 // Deprecated: as of v2.2. Use AppendPressedKeys instead.
    219 func PressedKeys() []ebiten.Key {
    220 	return AppendPressedKeys(nil)
    221 }
    222 
    223 // IsKeyJustPressed returns a boolean value indicating
    224 // whether the given key is pressed just in the current frame.
    225 //
    226 // IsKeyJustPressed is concurrent safe.
    227 func IsKeyJustPressed(key ebiten.Key) bool {
    228 	return KeyPressDuration(key) == 1
    229 }
    230 
    231 // IsKeyJustReleased returns a boolean value indicating
    232 // whether the given key is released just in the current frame.
    233 //
    234 // IsKeyJustReleased is concurrent safe.
    235 func IsKeyJustReleased(key ebiten.Key) bool {
    236 	theInputState.m.RLock()
    237 	r := theInputState.keyDurations[key] == 0 && theInputState.prevKeyDurations[key] > 0
    238 	theInputState.m.RUnlock()
    239 	return r
    240 }
    241 
    242 // KeyPressDuration returns how long the key is pressed in frames.
    243 //
    244 // KeyPressDuration is concurrent safe.
    245 func KeyPressDuration(key ebiten.Key) int {
    246 	theInputState.m.RLock()
    247 	s := theInputState.keyDurations[key]
    248 	theInputState.m.RUnlock()
    249 	return s
    250 }
    251 
    252 // IsMouseButtonJustPressed returns a boolean value indicating
    253 // whether the given mouse button is pressed just in the current frame.
    254 //
    255 // IsMouseButtonJustPressed is concurrent safe.
    256 func IsMouseButtonJustPressed(button ebiten.MouseButton) bool {
    257 	return MouseButtonPressDuration(button) == 1
    258 }
    259 
    260 // IsMouseButtonJustReleased returns a boolean value indicating
    261 // whether the given mouse button is released just in the current frame.
    262 //
    263 // IsMouseButtonJustReleased is concurrent safe.
    264 func IsMouseButtonJustReleased(button ebiten.MouseButton) bool {
    265 	theInputState.m.RLock()
    266 	r := theInputState.mouseButtonDurations[button] == 0 &&
    267 		theInputState.prevMouseButtonDurations[button] > 0
    268 	theInputState.m.RUnlock()
    269 	return r
    270 }
    271 
    272 // MouseButtonPressDuration returns how long the mouse button is pressed in frames.
    273 //
    274 // MouseButtonPressDuration is concurrent safe.
    275 func MouseButtonPressDuration(button ebiten.MouseButton) int {
    276 	theInputState.m.RLock()
    277 	s := theInputState.mouseButtonDurations[button]
    278 	theInputState.m.RUnlock()
    279 	return s
    280 }
    281 
    282 // AppendJustConnectedGamepadIDs appends gamepad IDs that are connected just in the current frame to gamepadIDs,
    283 // and returns the extended buffer.
    284 // Giving a slice that already has enough capacity works efficiently.
    285 //
    286 // AppendJustConnectedGamepadIDs is concurrent safe.
    287 func AppendJustConnectedGamepadIDs(gamepadIDs []ebiten.GamepadID) []ebiten.GamepadID {
    288 	origLen := len(gamepadIDs)
    289 	theInputState.m.RLock()
    290 	for id := range theInputState.gamepadIDs {
    291 		if _, ok := theInputState.prevGamepadIDs[id]; !ok {
    292 			gamepadIDs = append(gamepadIDs, id)
    293 		}
    294 	}
    295 	theInputState.m.RUnlock()
    296 	s := gamepadIDs[origLen:]
    297 	sort.Slice(s, func(a, b int) bool {
    298 		return s[a] < s[b]
    299 	})
    300 	return gamepadIDs
    301 }
    302 
    303 // JustConnectedGamepadIDs returns gamepad IDs that are connected just in the current frame.
    304 //
    305 // Deprecated: as of v2.2. Use AppendJustConnectedGamepadIDs instead.
    306 func JustConnectedGamepadIDs() []ebiten.GamepadID {
    307 	return AppendJustConnectedGamepadIDs(nil)
    308 }
    309 
    310 // IsGamepadJustDisconnected returns a boolean value indicating
    311 // whether the gamepad of the given id is released just in the current frame.
    312 //
    313 // IsGamepadJustDisconnected is concurrent safe.
    314 func IsGamepadJustDisconnected(id ebiten.GamepadID) bool {
    315 	theInputState.m.RLock()
    316 	_, prev := theInputState.prevGamepadIDs[id]
    317 	_, current := theInputState.gamepadIDs[id]
    318 	theInputState.m.RUnlock()
    319 	return prev && !current
    320 }
    321 
    322 // IsGamepadButtonJustPressed returns a boolean value indicating
    323 // whether the given gamepad button of the gamepad id is pressed just in the current frame.
    324 //
    325 // IsGamepadButtonJustPressed is concurrent safe.
    326 func IsGamepadButtonJustPressed(id ebiten.GamepadID, button ebiten.GamepadButton) bool {
    327 	return GamepadButtonPressDuration(id, button) == 1
    328 }
    329 
    330 // IsGamepadButtonJustReleased returns a boolean value indicating
    331 // whether the given gamepad button of the gamepad id is released just in the current frame.
    332 //
    333 // IsGamepadButtonJustReleased is concurrent safe.
    334 func IsGamepadButtonJustReleased(id ebiten.GamepadID, button ebiten.GamepadButton) bool {
    335 	theInputState.m.RLock()
    336 	prev := 0
    337 	if _, ok := theInputState.prevGamepadButtonDurations[id]; ok {
    338 		prev = theInputState.prevGamepadButtonDurations[id][button]
    339 	}
    340 	current := 0
    341 	if _, ok := theInputState.gamepadButtonDurations[id]; ok {
    342 		current = theInputState.gamepadButtonDurations[id][button]
    343 	}
    344 	theInputState.m.RUnlock()
    345 	return current == 0 && prev > 0
    346 }
    347 
    348 // GamepadButtonPressDuration returns how long the gamepad button of the gamepad id is pressed in frames.
    349 //
    350 // GamepadButtonPressDuration is concurrent safe.
    351 func GamepadButtonPressDuration(id ebiten.GamepadID, button ebiten.GamepadButton) int {
    352 	theInputState.m.RLock()
    353 	s := 0
    354 	if _, ok := theInputState.gamepadButtonDurations[id]; ok {
    355 		s = theInputState.gamepadButtonDurations[id][button]
    356 	}
    357 	theInputState.m.RUnlock()
    358 	return s
    359 }
    360 
    361 // IsStandardGamepadButtonJustPressed returns a boolean value indicating
    362 // whether the given standard gamepad button of the gamepad id is pressed just in the current frame.
    363 //
    364 // IsStandardGamepadButtonJustPressed is concurrent safe.
    365 func IsStandardGamepadButtonJustPressed(id ebiten.GamepadID, button ebiten.StandardGamepadButton) bool {
    366 	return StandardGamepadButtonPressDuration(id, button) == 1
    367 }
    368 
    369 // IsStandardGamepadButtonJustReleased returns a boolean value indicating
    370 // whether the given standard gamepad button of the gamepad id is released just in the current frame.
    371 //
    372 // IsStandardGamepadButtonJustReleased is concurrent safe.
    373 func IsStandardGamepadButtonJustReleased(id ebiten.GamepadID, button ebiten.StandardGamepadButton) bool {
    374 	theInputState.m.RLock()
    375 	defer theInputState.m.RUnlock()
    376 
    377 	var prev int
    378 	if _, ok := theInputState.prevStandardGamepadButtonDurations[id]; ok {
    379 		prev = theInputState.prevStandardGamepadButtonDurations[id][button]
    380 	}
    381 	var current int
    382 	if _, ok := theInputState.standardGamepadButtonDurations[id]; ok {
    383 		current = theInputState.standardGamepadButtonDurations[id][button]
    384 	}
    385 	return current == 0 && prev > 0
    386 }
    387 
    388 // StandardGamepadButtonPressDuration returns how long the standard gamepad button of the gamepad id is pressed in frames.
    389 //
    390 // StandardGamepadButtonPressDuration is concurrent safe.
    391 func StandardGamepadButtonPressDuration(id ebiten.GamepadID, button ebiten.StandardGamepadButton) int {
    392 	theInputState.m.RLock()
    393 	defer theInputState.m.RUnlock()
    394 
    395 	if _, ok := theInputState.standardGamepadButtonDurations[id]; ok {
    396 		return theInputState.standardGamepadButtonDurations[id][button]
    397 	}
    398 	return 0
    399 }
    400 
    401 // AppendJustPressedTouchIDs append touch IDs that are created just in the current frame to touchIDs,
    402 // and returns the extended buffer.
    403 // Giving a slice that already has enough capacity works efficiently.
    404 //
    405 // AppendJustPressedTouchIDs is concurrent safe.
    406 func AppendJustPressedTouchIDs(touchIDs []ebiten.TouchID) []ebiten.TouchID {
    407 	origLen := len(touchIDs)
    408 	theInputState.m.RLock()
    409 	for id, s := range theInputState.touchDurations {
    410 		if s == 1 {
    411 			touchIDs = append(touchIDs, id)
    412 		}
    413 	}
    414 	theInputState.m.RUnlock()
    415 	s := touchIDs[origLen:]
    416 	sort.Slice(s, func(a, b int) bool {
    417 		return s[a] < s[b]
    418 	})
    419 	return touchIDs
    420 }
    421 
    422 // JustPressedTouchIDs returns touch IDs that are created just in the current frame.
    423 //
    424 // Deprecated: as of v2.2. Use AppendJustPressedTouchIDs instead.
    425 func JustPressedTouchIDs() []ebiten.TouchID {
    426 	return AppendJustPressedTouchIDs(nil)
    427 }
    428 
    429 // IsTouchJustReleased returns a boolean value indicating
    430 // whether the given touch is released just in the current frame.
    431 //
    432 // IsTouchJustReleased is concurrent safe.
    433 func IsTouchJustReleased(id ebiten.TouchID) bool {
    434 	theInputState.m.RLock()
    435 	r := theInputState.touchDurations[id] == 0 && theInputState.prevTouchDurations[id] > 0
    436 	theInputState.m.RUnlock()
    437 	return r
    438 }
    439 
    440 // TouchPressDuration returns how long the touch remains in frames.
    441 //
    442 // TouchPressDuration is concurrent safe.
    443 func TouchPressDuration(id ebiten.TouchID) int {
    444 	theInputState.m.RLock()
    445 	s := theInputState.touchDurations[id]
    446 	theInputState.m.RUnlock()
    447 	return s
    448 }