sdl2_backend.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. # -*- coding: utf-8 -*-
  2. # ruff: noqa: F403, F405
  3. from __future__ import absolute_import
  4. from typing import Any
  5. from imgui_bundle import imgui
  6. from sdl2 import * # type: ignore
  7. from .opengl_backend_programmable import ProgrammablePipelineRenderer
  8. import ctypes
  9. from typing import Dict
  10. SdlKey = int
  11. class SDL2Renderer(ProgrammablePipelineRenderer):
  12. """Basic SDL2 integration implementation."""
  13. key_map: Dict[SdlKey, imgui.Key]
  14. MOUSE_WHEEL_OFFSET_SCALE = 0.5
  15. def __init__(self, window):
  16. super(SDL2Renderer, self).__init__()
  17. self.window = window
  18. self._mouse_wheel = 0.0
  19. self._gui_time = None
  20. width_ptr = ctypes.pointer(ctypes.c_int(0))
  21. height_ptr = ctypes.pointer(ctypes.c_int(0))
  22. SDL_GetWindowSize(self.window, width_ptr, height_ptr)
  23. self.io.display_size = width_ptr[0], height_ptr[0]
  24. def get_clipboard_text(_imgui_context: Any) -> str:
  25. r: ctypes.c_char_p = SDL_GetClipboardText() # type: ignore
  26. return r.decode() if r else "" # type: ignore
  27. def set_clipboard_text(_imgui_context: Any, text: str) -> None:
  28. SDL_SetClipboardText(ctypes.c_char_p(text.encode())) # type: ignore
  29. imgui.get_platform_io().platform_get_clipboard_text_fn = get_clipboard_text
  30. imgui.get_platform_io().platform_set_clipboard_text_fn = set_clipboard_text
  31. self._map_keys()
  32. def _map_keys(self):
  33. self.key_map = {}
  34. key_map = self.key_map
  35. # Control Keys
  36. key_map[SDL_SCANCODE_TAB] = imgui.Key.tab
  37. key_map[SDL_SCANCODE_BACKSPACE] = imgui.Key.backspace
  38. key_map[SDL_SCANCODE_RETURN] = imgui.Key.enter
  39. key_map[SDL_SCANCODE_ESCAPE] = imgui.Key.escape
  40. key_map[SDL_SCANCODE_INSERT] = imgui.Key.insert
  41. key_map[SDL_SCANCODE_DELETE] = imgui.Key.delete
  42. key_map[SDL_SCANCODE_SPACE] = imgui.Key.space
  43. # Function Keys
  44. key_map[SDL_SCANCODE_F1] = imgui.Key.f1
  45. key_map[SDL_SCANCODE_F2] = imgui.Key.f2
  46. key_map[SDL_SCANCODE_F3] = imgui.Key.f3
  47. key_map[SDL_SCANCODE_F4] = imgui.Key.f4
  48. key_map[SDL_SCANCODE_F5] = imgui.Key.f5
  49. key_map[SDL_SCANCODE_F6] = imgui.Key.f6
  50. key_map[SDL_SCANCODE_F7] = imgui.Key.f7
  51. key_map[SDL_SCANCODE_F8] = imgui.Key.f8
  52. key_map[SDL_SCANCODE_F9] = imgui.Key.f9
  53. key_map[SDL_SCANCODE_F10] = imgui.Key.f10
  54. key_map[SDL_SCANCODE_F11] = imgui.Key.f11
  55. key_map[SDL_SCANCODE_F12] = imgui.Key.f12
  56. # Navigation Keys
  57. key_map[SDL_SCANCODE_LEFT] = imgui.Key.left_arrow
  58. key_map[SDL_SCANCODE_RIGHT] = imgui.Key.right_arrow
  59. key_map[SDL_SCANCODE_UP] = imgui.Key.up_arrow
  60. key_map[SDL_SCANCODE_DOWN] = imgui.Key.down_arrow
  61. key_map[SDL_SCANCODE_PAGEUP] = imgui.Key.page_up
  62. key_map[SDL_SCANCODE_PAGEDOWN] = imgui.Key.page_down
  63. key_map[SDL_SCANCODE_HOME] = imgui.Key.home
  64. key_map[SDL_SCANCODE_END] = imgui.Key.end
  65. # Numeric Keys
  66. key_map[SDL_SCANCODE_0] = imgui.Key._0
  67. key_map[SDL_SCANCODE_1] = imgui.Key._1
  68. key_map[SDL_SCANCODE_2] = imgui.Key._2
  69. key_map[SDL_SCANCODE_3] = imgui.Key._3
  70. key_map[SDL_SCANCODE_4] = imgui.Key._4
  71. key_map[SDL_SCANCODE_5] = imgui.Key._5
  72. key_map[SDL_SCANCODE_6] = imgui.Key._6
  73. key_map[SDL_SCANCODE_7] = imgui.Key._7
  74. key_map[SDL_SCANCODE_8] = imgui.Key._8
  75. key_map[SDL_SCANCODE_9] = imgui.Key._9
  76. # Keypad Keys
  77. key_map[SDL_SCANCODE_KP_ENTER] = imgui.Key.keypad_enter
  78. key_map[SDL_SCANCODE_KP_0] = imgui.Key.keypad0
  79. key_map[SDL_SCANCODE_KP_1] = imgui.Key.keypad1
  80. key_map[SDL_SCANCODE_KP_2] = imgui.Key.keypad2
  81. key_map[SDL_SCANCODE_KP_3] = imgui.Key.keypad3
  82. key_map[SDL_SCANCODE_KP_4] = imgui.Key.keypad4
  83. key_map[SDL_SCANCODE_KP_5] = imgui.Key.keypad5
  84. key_map[SDL_SCANCODE_KP_6] = imgui.Key.keypad6
  85. key_map[SDL_SCANCODE_KP_7] = imgui.Key.keypad7
  86. key_map[SDL_SCANCODE_KP_8] = imgui.Key.keypad8
  87. key_map[SDL_SCANCODE_KP_9] = imgui.Key.keypad9
  88. key_map[SDL_SCANCODE_KP_DECIMAL] = imgui.Key.keypad_decimal
  89. key_map[SDL_SCANCODE_KP_DIVIDE] = imgui.Key.keypad_divide
  90. key_map[SDL_SCANCODE_KP_MULTIPLY] = imgui.Key.keypad_multiply
  91. key_map[SDL_SCANCODE_KP_MINUS] = imgui.Key.keypad_subtract
  92. key_map[SDL_SCANCODE_KP_PLUS] = imgui.Key.keypad_add
  93. key_map[SDL_SCANCODE_KP_EQUALS] = imgui.Key.keypad_equal
  94. # Alphabetic Keys
  95. key_map[SDL_SCANCODE_A] = imgui.Key.a
  96. key_map[SDL_SCANCODE_B] = imgui.Key.b
  97. key_map[SDL_SCANCODE_C] = imgui.Key.c
  98. key_map[SDL_SCANCODE_D] = imgui.Key.d
  99. key_map[SDL_SCANCODE_E] = imgui.Key.e
  100. key_map[SDL_SCANCODE_F] = imgui.Key.f
  101. key_map[SDL_SCANCODE_G] = imgui.Key.g
  102. key_map[SDL_SCANCODE_H] = imgui.Key.h
  103. key_map[SDL_SCANCODE_I] = imgui.Key.i
  104. key_map[SDL_SCANCODE_J] = imgui.Key.j
  105. key_map[SDL_SCANCODE_K] = imgui.Key.k
  106. key_map[SDL_SCANCODE_L] = imgui.Key.l
  107. key_map[SDL_SCANCODE_M] = imgui.Key.m
  108. key_map[SDL_SCANCODE_N] = imgui.Key.n
  109. key_map[SDL_SCANCODE_O] = imgui.Key.o
  110. key_map[SDL_SCANCODE_P] = imgui.Key.p
  111. key_map[SDL_SCANCODE_Q] = imgui.Key.q
  112. key_map[SDL_SCANCODE_R] = imgui.Key.r
  113. key_map[SDL_SCANCODE_S] = imgui.Key.s
  114. key_map[SDL_SCANCODE_T] = imgui.Key.t
  115. key_map[SDL_SCANCODE_U] = imgui.Key.u
  116. key_map[SDL_SCANCODE_V] = imgui.Key.v
  117. key_map[SDL_SCANCODE_W] = imgui.Key.w
  118. key_map[SDL_SCANCODE_X] = imgui.Key.x
  119. key_map[SDL_SCANCODE_Y] = imgui.Key.y
  120. key_map[SDL_SCANCODE_Z] = imgui.Key.z
  121. # Modifier Keys
  122. key_map[SDL_SCANCODE_LCTRL] = imgui.Key.left_ctrl
  123. key_map[SDL_SCANCODE_RCTRL] = imgui.Key.right_ctrl
  124. key_map[SDL_SCANCODE_LSHIFT] = imgui.Key.left_shift
  125. key_map[SDL_SCANCODE_RSHIFT] = imgui.Key.right_shift
  126. key_map[SDL_SCANCODE_LALT] = imgui.Key.left_alt
  127. key_map[SDL_SCANCODE_RALT] = imgui.Key.right_alt
  128. key_map[SDL_SCANCODE_LGUI] = imgui.Key.left_super
  129. key_map[SDL_SCANCODE_RGUI] = imgui.Key.right_super
  130. # Media Keys
  131. key_map[SDL_SCANCODE_AUDIONEXT] = imgui.Key.app_forward
  132. key_map[SDL_SCANCODE_AUDIOPREV] = imgui.Key.app_back
  133. # key_map[SDL_SCANCODE_AUDIOSTOP] = imgui.Key.none
  134. # key_map[SDL_SCANCODE_AUDIOPLAY] = imgui.Key.none
  135. # key_map[SDL_SCANCODE_AUDIOMUTE] = imgui.Key.none
  136. # Application Keys
  137. key_map[SDL_SCANCODE_APPLICATION] = imgui.Key.menu
  138. # Lock Keys
  139. key_map[SDL_SCANCODE_CAPSLOCK] = imgui.Key.caps_lock
  140. key_map[SDL_SCANCODE_SCROLLLOCK] = imgui.Key.scroll_lock
  141. key_map[SDL_SCANCODE_NUMLOCKCLEAR] = imgui.Key.num_lock
  142. # Symbol Keys
  143. key_map[SDL_SCANCODE_GRAVE] = imgui.Key.grave_accent # `
  144. key_map[SDL_SCANCODE_MINUS] = imgui.Key.minus # -
  145. key_map[SDL_SCANCODE_EQUALS] = imgui.Key.equal # =
  146. key_map[SDL_SCANCODE_LEFTBRACKET] = imgui.Key.left_bracket # [
  147. key_map[SDL_SCANCODE_RIGHTBRACKET] = imgui.Key.right_bracket # ]
  148. key_map[SDL_SCANCODE_BACKSLASH] = imgui.Key.backslash # \
  149. key_map[SDL_SCANCODE_SEMICOLON] = imgui.Key.semicolon # ;
  150. key_map[SDL_SCANCODE_APOSTROPHE] = imgui.Key.apostrophe # '
  151. key_map[SDL_SCANCODE_COMMA] = imgui.Key.comma # ,
  152. key_map[SDL_SCANCODE_PERIOD] = imgui.Key.period # .
  153. key_map[SDL_SCANCODE_SLASH] = imgui.Key.slash # /
  154. # Miscellaneous Keys
  155. key_map[SDL_SCANCODE_PRINTSCREEN] = imgui.Key.print_screen
  156. key_map[SDL_SCANCODE_PAUSE] = imgui.Key.pause
  157. key_map[SDL_SCANCODE_KP_ENTER] = imgui.Key.keypad_enter
  158. def process_event(self, event):
  159. io = self.io
  160. if event.type == SDL_MOUSEMOTION:
  161. if SDL_GetWindowFlags(self.window) & SDL_WINDOW_MOUSE_FOCUS:
  162. io.mouse_pos = -1, -1
  163. io.mouse_pos = event.motion.x, event.motion.y
  164. return True
  165. if event.type == SDL_MOUSEWHEEL:
  166. io.add_mouse_wheel_event(
  167. event.wheel.x * self.MOUSE_WHEEL_OFFSET_SCALE, event.wheel.y * self.MOUSE_WHEEL_OFFSET_SCALE)
  168. return True
  169. if event.type == SDL_MOUSEBUTTONDOWN or event.type == SDL_MOUSEBUTTONUP:
  170. imgui_button = 0
  171. if event.button.button == SDL_BUTTON_LEFT:
  172. imgui_button = 0
  173. elif event.button.button == SDL_BUTTON_RIGHT:
  174. imgui_button = 1
  175. elif event.button.button == SDL_BUTTON_MIDDLE:
  176. imgui_button = 2
  177. down = event.type == SDL_MOUSEBUTTONDOWN
  178. io.add_mouse_button_event(imgui_button, down)
  179. return True
  180. if event.type == SDL_KEYUP or event.type == SDL_KEYDOWN:
  181. sdl_key = event.key.keysym.scancode
  182. if sdl_key in self.key_map:
  183. imgui_key = self.key_map[sdl_key]
  184. down = event.type == SDL_KEYDOWN
  185. io.add_key_event(imgui_key, down)
  186. # Handle modifiers, since ImGui has an additional mod_ctrl / shift / etc
  187. if imgui_key == imgui.Key.left_ctrl or imgui_key == imgui.Key.right_ctrl:
  188. io.add_key_event(imgui.Key.mod_ctrl, down)
  189. if imgui_key == imgui.Key.left_shift or imgui_key == imgui.Key.right_shift:
  190. io.add_key_event(imgui.Key.mod_shift, down)
  191. if imgui_key == imgui.Key.left_alt or imgui_key == imgui.Key.right_alt:
  192. io.add_key_event(imgui.Key.mod_alt, down)
  193. if imgui_key == imgui.Key.left_super or imgui_key == imgui.Key.right_super:
  194. io.add_key_event(imgui.Key.mod_super, down)
  195. return True
  196. if event.type == SDL_TEXTINPUT:
  197. for char in event.text.text.decode("utf-8"):
  198. io.add_input_character(ord(char))
  199. return True
  200. if event.type == SDL_CONTROLLERBUTTONDOWN or event.type == SDL_CONTROLLERBUTTONUP:
  201. button = event.cbutton.button
  202. is_pressed = event.type == SDL_CONTROLLERBUTTONDOWN
  203. # Map SDL_CONTROLLER_BUTTON_* to imgui keys
  204. if button == SDL_CONTROLLER_BUTTON_A:
  205. imgui.get_io().add_key_event(imgui.Key.gamepad_face_down, is_pressed)
  206. elif button == SDL_CONTROLLER_BUTTON_B:
  207. imgui.get_io().add_key_event(imgui.Key.gamepad_face_right, is_pressed)
  208. elif button == SDL_CONTROLLER_BUTTON_X:
  209. imgui.get_io().add_key_event(imgui.Key.gamepad_face_left, is_pressed)
  210. elif button == SDL_CONTROLLER_BUTTON_Y:
  211. imgui.get_io().add_key_event(imgui.Key.gamepad_face_up, is_pressed)
  212. elif button == SDL_CONTROLLER_BUTTON_DPAD_UP:
  213. imgui.get_io().add_key_event(imgui.Key.gamepad_dpad_up, is_pressed)
  214. elif button == SDL_CONTROLLER_BUTTON_DPAD_DOWN:
  215. imgui.get_io().add_key_event(imgui.Key.gamepad_dpad_down, is_pressed)
  216. elif button == SDL_CONTROLLER_BUTTON_DPAD_LEFT:
  217. imgui.get_io().add_key_event(imgui.Key.gamepad_dpad_left, is_pressed)
  218. elif button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT:
  219. imgui.get_io().add_key_event(imgui.Key.gamepad_dpad_right, is_pressed)
  220. return True
  221. def process_inputs(self):
  222. io = imgui.get_io()
  223. s_w = ctypes.pointer(ctypes.c_int(0))
  224. s_h = ctypes.pointer(ctypes.c_int(0))
  225. SDL_GetWindowSize(self.window, s_w, s_h)
  226. w = s_w.contents.value
  227. h = s_h.contents.value
  228. io.display_size = w, h
  229. io.display_framebuffer_scale = (1, 1)
  230. current_time = SDL_GetTicks() / 1000.0
  231. if self._gui_time:
  232. io.delta_time = current_time - self._gui_time
  233. else:
  234. io.delta_time = 1.0 / 60.0
  235. if io.delta_time <= 0.0:
  236. io.delta_time = 1.0 / 1000.0
  237. self._gui_time = current_time