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 }