input.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. // Copyright 2015 Hajime Hoshi
  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. package ebiten
  15. import (
  16. "io/fs"
  17. "sync"
  18. "github.com/hajimehoshi/ebiten/v2/internal/gamepad"
  19. "github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
  20. "github.com/hajimehoshi/ebiten/v2/internal/ui"
  21. )
  22. // AppendInputChars appends "printable" runes, read from the keyboard at the time Update is called, to runes,
  23. // and returns the extended buffer.
  24. // Giving a slice that already has enough capacity works efficiently.
  25. //
  26. // AppendInputChars represents the environment's locale-dependent translation of keyboard
  27. // input to Unicode characters. On the other hand, Key represents a physical key of US keyboard layout
  28. //
  29. // "Control" and modifier keys should be handled with IsKeyPressed.
  30. //
  31. // AppendInputChars is concurrent-safe.
  32. //
  33. // On Android (ebitenmobile), EbitenView must be focusable to enable to handle keyboard keys.
  34. func AppendInputChars(runes []rune) []rune {
  35. return theInputState.appendInputChars(runes)
  36. }
  37. // InputChars return "printable" runes read from the keyboard at the time Update is called.
  38. //
  39. // Deprecated: as of v2.2. Use AppendInputChars instead.
  40. func InputChars() []rune {
  41. return AppendInputChars(nil)
  42. }
  43. // IsKeyPressed returns a boolean indicating whether key is pressed.
  44. //
  45. // If you want to know whether the key started being pressed in the current tick,
  46. // use inpututil.IsKeyJustPressed
  47. //
  48. // Note that a Key represents a physical key of US keyboard layout.
  49. // For example, KeyQ represents Q key on US keyboards and ' (quote) key on Dvorak keyboards.
  50. //
  51. // IsKeyPressed is concurrent-safe.
  52. //
  53. // On Android (ebitenmobile), EbitenView must be focusable to enable to handle keyboard keys.
  54. func IsKeyPressed(key Key) bool {
  55. return theInputState.isKeyPressed(key)
  56. }
  57. // KeyName returns a key name for the current keyboard layout.
  58. // For example, KeyName(KeyQ) returns 'q' for a QWERTY keyboard, and returns 'a' for an AZERTY keyboard.
  59. //
  60. // KeyName returns an empty string if 1) the key doesn't have a physical key name, 2) the platform doesn't support KeyName,
  61. // or 3) the main loop doesn't start yet.
  62. //
  63. // KeyName is supported by desktops and browsers.
  64. //
  65. // KeyName is concurrent-safe.
  66. func KeyName(key Key) string {
  67. return ui.Get().KeyName(ui.Key(key))
  68. }
  69. // CursorPosition returns a position of a mouse cursor relative to the game screen (window). The cursor position is
  70. // 'logical' position and this considers the scale of the screen.
  71. //
  72. // CursorPosition returns (0, 0) before the main loop on desktops and browsers.
  73. //
  74. // CursorPosition always returns (0, 0) on mobile native applications.
  75. //
  76. // CursorPosition is concurrent-safe.
  77. func CursorPosition() (x, y int) {
  78. cx, cy := theInputState.cursorPosition()
  79. return int(cx), int(cy)
  80. }
  81. // Wheel returns x and y offsets of the mouse wheel or touchpad scroll.
  82. // It returns 0 if the wheel isn't being rolled.
  83. //
  84. // Wheel is concurrent-safe.
  85. func Wheel() (xoff, yoff float64) {
  86. return theInputState.wheel()
  87. }
  88. // IsMouseButtonPressed returns a boolean indicating whether mouseButton is pressed.
  89. //
  90. // If you want to know whether the mouseButton started being pressed in the current tick,
  91. // use inpututil.IsMouseButtonJustPressed
  92. //
  93. // IsMouseButtonPressed is concurrent-safe.
  94. func IsMouseButtonPressed(mouseButton MouseButton) bool {
  95. return theInputState.isMouseButtonPressed(mouseButton)
  96. }
  97. // GamepadID represents a gamepad identifier.
  98. type GamepadID = gamepad.ID
  99. // GamepadSDLID returns a string with the GUID generated in the same way as SDL.
  100. // To detect devices, see also the community project of gamepad devices database: https://github.com/gabomdq/SDL_GameControllerDB
  101. //
  102. // GamepadSDLID always returns an empty string on browsers and mobiles.
  103. //
  104. // GamepadSDLID is concurrent-safe.
  105. func GamepadSDLID(id GamepadID) string {
  106. g := gamepad.Get(id)
  107. if g == nil {
  108. return ""
  109. }
  110. return g.SDLID()
  111. }
  112. // GamepadName returns a string with the name.
  113. // This function may vary in how it returns descriptions for the same device across platforms.
  114. // for example the following drivers/platforms see an Xbox One controller as the following:
  115. //
  116. // - Windows: "Xbox Controller"
  117. // - Chrome: "Xbox 360 Controller (XInput STANDARD GAMEPAD)"
  118. // - Firefox: "xinput"
  119. //
  120. // GamepadName is concurrent-safe.
  121. func GamepadName(id GamepadID) string {
  122. g := gamepad.Get(id)
  123. if g == nil {
  124. return ""
  125. }
  126. return g.Name()
  127. }
  128. // AppendGamepadIDs appends available gamepad IDs to gamepadIDs, and returns the extended buffer.
  129. // Giving a slice that already has enough capacity works efficiently.
  130. //
  131. // AppendGamepadIDs is concurrent-safe.
  132. func AppendGamepadIDs(gamepadIDs []GamepadID) []GamepadID {
  133. return gamepad.AppendGamepadIDs(gamepadIDs)
  134. }
  135. // GamepadIDs returns a slice indicating available gamepad IDs.
  136. //
  137. // Deprecated: as of v2.2. Use AppendGamepadIDs instead.
  138. func GamepadIDs() []GamepadID {
  139. return AppendGamepadIDs(nil)
  140. }
  141. // GamepadAxisCount returns the number of axes of the gamepad (id).
  142. //
  143. // GamepadAxisCount is concurrent-safe.
  144. func GamepadAxisCount(id GamepadID) int {
  145. g := gamepad.Get(id)
  146. if g == nil {
  147. return 0
  148. }
  149. return g.AxisCount()
  150. }
  151. // GamepadAxisNum returns the number of axes of the gamepad (id).
  152. //
  153. // Deprecated: as of v2.4. Use GamepadAxisCount instead.
  154. func GamepadAxisNum(id GamepadID) int {
  155. return GamepadAxisCount(id)
  156. }
  157. // GamepadAxisValue returns a float value [-1.0 - 1.0] of the given gamepad (id)'s axis (axis).
  158. //
  159. // GamepadAxisValue is concurrent-safe.
  160. func GamepadAxisValue(id GamepadID, axis GamepadAxisType) float64 {
  161. g := gamepad.Get(id)
  162. if g == nil {
  163. return 0
  164. }
  165. return g.Axis(int(axis))
  166. }
  167. // GamepadAxis returns a float value [-1.0 - 1.0] of the given gamepad (id)'s axis (axis).
  168. //
  169. // Deprecated: as of v2.2. Use GamepadAxisValue instead.
  170. func GamepadAxis(id GamepadID, axis GamepadAxisType) float64 {
  171. return GamepadAxisValue(id, axis)
  172. }
  173. // GamepadButtonCount returns the number of the buttons of the given gamepad (id).
  174. //
  175. // GamepadButtonCount is concurrent-safe.
  176. func GamepadButtonCount(id GamepadID) int {
  177. g := gamepad.Get(id)
  178. if g == nil {
  179. return 0
  180. }
  181. // For backward compatibility, hats are treated as buttons in GLFW.
  182. return g.ButtonCount() + g.HatCount()*4
  183. }
  184. // GamepadButtonNum returns the number of the buttons of the given gamepad (id).
  185. //
  186. // Deprecated: as of v2.4. Use GamepadButtonCount instead.
  187. func GamepadButtonNum(id GamepadID) int {
  188. return GamepadButtonCount(id)
  189. }
  190. // IsGamepadButtonPressed reports whether the given button of the gamepad (id) is pressed or not.
  191. //
  192. // If you want to know whether the given button of gamepad (id) started being pressed in the current tick,
  193. // use inpututil.IsGamepadButtonJustPressed
  194. //
  195. // IsGamepadButtonPressed is concurrent-safe.
  196. //
  197. // The relationships between physical buttons and button IDs depend on environments.
  198. // There can be differences even between Chrome and Firefox.
  199. func IsGamepadButtonPressed(id GamepadID, button GamepadButton) bool {
  200. g := gamepad.Get(id)
  201. if g == nil {
  202. return false
  203. }
  204. nbuttons := g.ButtonCount()
  205. if int(button) < nbuttons {
  206. return g.Button(int(button))
  207. }
  208. // For backward compatibility, hats are treated as buttons in GLFW.
  209. if hat := (int(button) - nbuttons) / 4; hat < g.HatCount() {
  210. dir := (int(button) - nbuttons) % 4
  211. return g.Hat(hat)&(1<<dir) != 0
  212. }
  213. return false
  214. }
  215. // StandardGamepadAxisValue returns a float value [-1.0 - 1.0] of the given gamepad (id)'s standard axis (axis).
  216. //
  217. // StandardGamepadAxisValue returns 0 when the gamepad doesn't have a standard gamepad layout mapping.
  218. //
  219. // StandardGamepadAxisValue is concurrent safe.
  220. func StandardGamepadAxisValue(id GamepadID, axis StandardGamepadAxis) float64 {
  221. g := gamepad.Get(id)
  222. if g == nil {
  223. return 0
  224. }
  225. return g.StandardAxisValue(axis)
  226. }
  227. // StandardGamepadButtonValue returns a float value [0.0 - 1.0] of the given gamepad (id)'s standard button (button).
  228. //
  229. // StandardGamepadButtonValue returns 0 when the gamepad doesn't have a standard gamepad layout mapping.
  230. //
  231. // StandardGamepadButtonValue is concurrent safe.
  232. func StandardGamepadButtonValue(id GamepadID, button StandardGamepadButton) float64 {
  233. g := gamepad.Get(id)
  234. if g == nil {
  235. return 0
  236. }
  237. return g.StandardButtonValue(button)
  238. }
  239. // IsStandardGamepadButtonPressed reports whether the given gamepad (id)'s standard gamepad button (button) is pressed.
  240. //
  241. // IsStandardGamepadButtonPressed returns false when the gamepad doesn't have a standard gamepad layout mapping.
  242. //
  243. // IsStandardGamepadButtonPressed is concurrent safe.
  244. func IsStandardGamepadButtonPressed(id GamepadID, button StandardGamepadButton) bool {
  245. g := gamepad.Get(id)
  246. if g == nil {
  247. return false
  248. }
  249. return g.IsStandardButtonPressed(button)
  250. }
  251. // IsStandardGamepadLayoutAvailable reports whether the gamepad (id) has a standard gamepad layout mapping.
  252. //
  253. // IsStandardGamepadLayoutAvailable is concurrent-safe.
  254. func IsStandardGamepadLayoutAvailable(id GamepadID) bool {
  255. g := gamepad.Get(id)
  256. if g == nil {
  257. return false
  258. }
  259. return g.IsStandardLayoutAvailable()
  260. }
  261. // IsStandardGamepadAxisAvailable reports whether the standard gamepad axis is available on the gamepad (id).
  262. //
  263. // IsStandardGamepadAxisAvailable is concurrent-safe.
  264. func IsStandardGamepadAxisAvailable(id GamepadID, axis StandardGamepadAxis) bool {
  265. g := gamepad.Get(id)
  266. if g == nil {
  267. return false
  268. }
  269. return g.IsStandardAxisAvailable(axis)
  270. }
  271. // IsStandardGamepadButtonAvailable reports whether the standard gamepad button is available on the gamepad (id).
  272. //
  273. // IsStandardGamepadButtonAvailable is concurrent-safe.
  274. func IsStandardGamepadButtonAvailable(id GamepadID, button StandardGamepadButton) bool {
  275. g := gamepad.Get(id)
  276. if g == nil {
  277. return false
  278. }
  279. return g.IsStandardButtonAvailable(button)
  280. }
  281. // UpdateStandardGamepadLayoutMappings parses the specified string mappings in SDL_GameControllerDB format and
  282. // updates the gamepad layout definitions.
  283. //
  284. // UpdateStandardGamepadLayoutMappings reports whether the mappings were applied,
  285. // and returns an error in case any occurred while parsing the mappings.
  286. //
  287. // One or more input definitions can be provided separated by newlines.
  288. // In particular, it is valid to pass an entire gamecontrollerdb.txt file.
  289. // Note though that Ebitengine already includes its own copy of this file,
  290. // so this call should only be necessary to add mappings for hardware not supported yet;
  291. // ideally games using the StandardGamepad* functions should allow the user to provide mappings and
  292. // then call this function if provided.
  293. // When using this facility to support new hardware, please also send a pull request to
  294. // https://github.com/gabomdq/SDL_GameControllerDB to make your mapping available to everyone else.
  295. //
  296. // A platform field in a line corresponds with a GOOS like the following:
  297. //
  298. // "Windows": GOOS=windows
  299. // "Mac OS X": GOOS=darwin (not ios)
  300. // "Linux": GOOS=linux (not android)
  301. // "Android": GOOS=android
  302. // "iOS": GOOS=ios
  303. // "": Any GOOS
  304. //
  305. // On platforms where gamepad mappings are not managed by Ebitengine, this always returns false and nil.
  306. //
  307. // UpdateStandardGamepadLayoutMappings is concurrent-safe.
  308. //
  309. // UpdateStandardGamepadLayoutMappings mappings take effect immediately even for already connected gamepads.
  310. //
  311. // UpdateStandardGamepadLayoutMappings works atomically. If an error happens, nothing is updated.
  312. func UpdateStandardGamepadLayoutMappings(mappings string) (bool, error) {
  313. if err := gamepaddb.Update([]byte(mappings)); err != nil {
  314. return false, err
  315. }
  316. return true, nil
  317. }
  318. // TouchID represents a touch's identifier.
  319. type TouchID int
  320. // AppendTouchIDs appends the current touch states to touches, and returns the extended buffer.
  321. // Giving a slice that already has enough capacity works efficiently.
  322. //
  323. // If you want to know whether a touch started being pressed in the current tick,
  324. // use inpututil.JustPressedTouchIDs
  325. //
  326. // AppendTouchIDs doesn't append anything when there are no touches.
  327. // AppendTouchIDs always does nothing on desktops.
  328. //
  329. // AppendTouchIDs is concurrent-safe.
  330. func AppendTouchIDs(touches []TouchID) []TouchID {
  331. return theInputState.appendTouchIDs(touches)
  332. }
  333. // TouchIDs returns the current touch states.
  334. //
  335. // Deprecated: as of v2.2. Use AppendTouchIDs instead.
  336. func TouchIDs() []TouchID {
  337. return AppendTouchIDs(nil)
  338. }
  339. // TouchPosition returns the position for the touch of the specified ID.
  340. //
  341. // If the touch of the specified ID is not present, TouchPosition returns (0, 0).
  342. //
  343. // TouchPosition is concurrent-safe.
  344. func TouchPosition(id TouchID) (int, int) {
  345. return theInputState.touchPosition(id)
  346. }
  347. var theInputState inputState
  348. type inputState struct {
  349. state ui.InputState
  350. m sync.Mutex
  351. }
  352. func (i *inputState) update(fn func(*ui.InputState)) {
  353. i.m.Lock()
  354. defer i.m.Unlock()
  355. fn(&i.state)
  356. }
  357. func (i *inputState) appendInputChars(runes []rune) []rune {
  358. i.m.Lock()
  359. defer i.m.Unlock()
  360. return append(runes, i.state.Runes...)
  361. }
  362. func (i *inputState) isKeyPressed(key Key) bool {
  363. if !key.isValid() {
  364. return false
  365. }
  366. i.m.Lock()
  367. defer i.m.Unlock()
  368. switch key {
  369. case KeyAlt:
  370. return i.state.KeyPressed[ui.KeyAltLeft] || i.state.KeyPressed[ui.KeyAltRight]
  371. case KeyControl:
  372. return i.state.KeyPressed[ui.KeyControlLeft] || i.state.KeyPressed[ui.KeyControlRight]
  373. case KeyShift:
  374. return i.state.KeyPressed[ui.KeyShiftLeft] || i.state.KeyPressed[ui.KeyShiftRight]
  375. case KeyMeta:
  376. return i.state.KeyPressed[ui.KeyMetaLeft] || i.state.KeyPressed[ui.KeyMetaRight]
  377. default:
  378. return i.state.KeyPressed[key]
  379. }
  380. }
  381. func (i *inputState) cursorPosition() (float64, float64) {
  382. i.m.Lock()
  383. defer i.m.Unlock()
  384. return i.state.CursorX, i.state.CursorY
  385. }
  386. func (i *inputState) wheel() (float64, float64) {
  387. i.m.Lock()
  388. defer i.m.Unlock()
  389. return i.state.WheelX, i.state.WheelY
  390. }
  391. func (i *inputState) isMouseButtonPressed(mouseButton MouseButton) bool {
  392. i.m.Lock()
  393. defer i.m.Unlock()
  394. return i.state.MouseButtonPressed[mouseButton]
  395. }
  396. func (i *inputState) appendTouchIDs(touches []TouchID) []TouchID {
  397. i.m.Lock()
  398. defer i.m.Unlock()
  399. for _, t := range i.state.Touches {
  400. touches = append(touches, TouchID(t.ID))
  401. }
  402. return touches
  403. }
  404. func (i *inputState) touchPosition(id TouchID) (int, int) {
  405. i.m.Lock()
  406. defer i.m.Unlock()
  407. for _, t := range i.state.Touches {
  408. if id != TouchID(t.ID) {
  409. continue
  410. }
  411. return t.X, t.Y
  412. }
  413. return 0, 0
  414. }
  415. func (i *inputState) windowBeingClosed() bool {
  416. i.m.Lock()
  417. defer i.m.Unlock()
  418. return i.state.WindowBeingClosed
  419. }
  420. func (i *inputState) droppedFiles() fs.FS {
  421. i.m.Lock()
  422. defer i.m.Unlock()
  423. return i.state.DroppedFiles
  424. }