sdl3_backend.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  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 sdl3 import *
  7. from .opengl_backend_programmable import ProgrammablePipelineRenderer
  8. import ctypes
  9. from typing import Dict
  10. SdlKey = int
  11. class SDL3Renderer(ProgrammablePipelineRenderer):
  12. """Basic SDL3 integration implementation."""
  13. key_map: Dict[SdlKey, imgui.Key]
  14. MOUSE_WHEEL_OFFSET_SCALE = 0.5
  15. def __init__(self, window):
  16. super(SDL3Renderer, 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()
  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()))
  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. SDL_StartTextInput(self.window)
  32. self._map_keys()
  33. def _map_keys(self):
  34. self.key_map = {}
  35. key_map = self.key_map
  36. # Control Keys
  37. key_map[SDL_SCANCODE_TAB] = imgui.Key.tab
  38. key_map[SDL_SCANCODE_BACKSPACE] = imgui.Key.backspace
  39. key_map[SDL_SCANCODE_RETURN] = imgui.Key.enter
  40. key_map[SDL_SCANCODE_ESCAPE] = imgui.Key.escape
  41. key_map[SDL_SCANCODE_INSERT] = imgui.Key.insert
  42. key_map[SDL_SCANCODE_DELETE] = imgui.Key.delete
  43. key_map[SDL_SCANCODE_SPACE] = imgui.Key.space
  44. # Function Keys
  45. key_map[SDL_SCANCODE_F1] = imgui.Key.f1
  46. key_map[SDL_SCANCODE_F2] = imgui.Key.f2
  47. key_map[SDL_SCANCODE_F3] = imgui.Key.f3
  48. key_map[SDL_SCANCODE_F4] = imgui.Key.f4
  49. key_map[SDL_SCANCODE_F5] = imgui.Key.f5
  50. key_map[SDL_SCANCODE_F6] = imgui.Key.f6
  51. key_map[SDL_SCANCODE_F7] = imgui.Key.f7
  52. key_map[SDL_SCANCODE_F8] = imgui.Key.f8
  53. key_map[SDL_SCANCODE_F9] = imgui.Key.f9
  54. key_map[SDL_SCANCODE_F10] = imgui.Key.f10
  55. key_map[SDL_SCANCODE_F11] = imgui.Key.f11
  56. key_map[SDL_SCANCODE_F12] = imgui.Key.f12
  57. # Navigation Keys
  58. key_map[SDL_SCANCODE_LEFT] = imgui.Key.left_arrow
  59. key_map[SDL_SCANCODE_RIGHT] = imgui.Key.right_arrow
  60. key_map[SDL_SCANCODE_UP] = imgui.Key.up_arrow
  61. key_map[SDL_SCANCODE_DOWN] = imgui.Key.down_arrow
  62. key_map[SDL_SCANCODE_PAGEUP] = imgui.Key.page_up
  63. key_map[SDL_SCANCODE_PAGEDOWN] = imgui.Key.page_down
  64. key_map[SDL_SCANCODE_HOME] = imgui.Key.home
  65. key_map[SDL_SCANCODE_END] = imgui.Key.end
  66. # Numeric Keys
  67. key_map[SDL_SCANCODE_0] = imgui.Key._0
  68. key_map[SDL_SCANCODE_1] = imgui.Key._1
  69. key_map[SDL_SCANCODE_2] = imgui.Key._2
  70. key_map[SDL_SCANCODE_3] = imgui.Key._3
  71. key_map[SDL_SCANCODE_4] = imgui.Key._4
  72. key_map[SDL_SCANCODE_5] = imgui.Key._5
  73. key_map[SDL_SCANCODE_6] = imgui.Key._6
  74. key_map[SDL_SCANCODE_7] = imgui.Key._7
  75. key_map[SDL_SCANCODE_8] = imgui.Key._8
  76. key_map[SDL_SCANCODE_9] = imgui.Key._9
  77. # Keypad Keys
  78. key_map[SDL_SCANCODE_KP_ENTER] = imgui.Key.keypad_enter
  79. key_map[SDL_SCANCODE_KP_0] = imgui.Key.keypad0
  80. key_map[SDL_SCANCODE_KP_1] = imgui.Key.keypad1
  81. key_map[SDL_SCANCODE_KP_2] = imgui.Key.keypad2
  82. key_map[SDL_SCANCODE_KP_3] = imgui.Key.keypad3
  83. key_map[SDL_SCANCODE_KP_4] = imgui.Key.keypad4
  84. key_map[SDL_SCANCODE_KP_5] = imgui.Key.keypad5
  85. key_map[SDL_SCANCODE_KP_6] = imgui.Key.keypad6
  86. key_map[SDL_SCANCODE_KP_7] = imgui.Key.keypad7
  87. key_map[SDL_SCANCODE_KP_8] = imgui.Key.keypad8
  88. key_map[SDL_SCANCODE_KP_9] = imgui.Key.keypad9
  89. key_map[SDL_SCANCODE_KP_DECIMAL] = imgui.Key.keypad_decimal
  90. key_map[SDL_SCANCODE_KP_DIVIDE] = imgui.Key.keypad_divide
  91. key_map[SDL_SCANCODE_KP_MULTIPLY] = imgui.Key.keypad_multiply
  92. key_map[SDL_SCANCODE_KP_MINUS] = imgui.Key.keypad_subtract
  93. key_map[SDL_SCANCODE_KP_PLUS] = imgui.Key.keypad_add
  94. key_map[SDL_SCANCODE_KP_EQUALS] = imgui.Key.keypad_equal
  95. # Alphabetic Keys
  96. key_map[SDL_SCANCODE_A] = imgui.Key.a
  97. key_map[SDL_SCANCODE_B] = imgui.Key.b
  98. key_map[SDL_SCANCODE_C] = imgui.Key.c
  99. key_map[SDL_SCANCODE_D] = imgui.Key.d
  100. key_map[SDL_SCANCODE_E] = imgui.Key.e
  101. key_map[SDL_SCANCODE_F] = imgui.Key.f
  102. key_map[SDL_SCANCODE_G] = imgui.Key.g
  103. key_map[SDL_SCANCODE_H] = imgui.Key.h
  104. key_map[SDL_SCANCODE_I] = imgui.Key.i
  105. key_map[SDL_SCANCODE_J] = imgui.Key.j
  106. key_map[SDL_SCANCODE_K] = imgui.Key.k
  107. key_map[SDL_SCANCODE_L] = imgui.Key.l
  108. key_map[SDL_SCANCODE_M] = imgui.Key.m
  109. key_map[SDL_SCANCODE_N] = imgui.Key.n
  110. key_map[SDL_SCANCODE_O] = imgui.Key.o
  111. key_map[SDL_SCANCODE_P] = imgui.Key.p
  112. key_map[SDL_SCANCODE_Q] = imgui.Key.q
  113. key_map[SDL_SCANCODE_R] = imgui.Key.r
  114. key_map[SDL_SCANCODE_S] = imgui.Key.s
  115. key_map[SDL_SCANCODE_T] = imgui.Key.t
  116. key_map[SDL_SCANCODE_U] = imgui.Key.u
  117. key_map[SDL_SCANCODE_V] = imgui.Key.v
  118. key_map[SDL_SCANCODE_W] = imgui.Key.w
  119. key_map[SDL_SCANCODE_X] = imgui.Key.x
  120. key_map[SDL_SCANCODE_Y] = imgui.Key.y
  121. key_map[SDL_SCANCODE_Z] = imgui.Key.z
  122. # Modifier Keys
  123. key_map[SDL_SCANCODE_LCTRL] = imgui.Key.left_ctrl
  124. key_map[SDL_SCANCODE_RCTRL] = imgui.Key.right_ctrl
  125. key_map[SDL_SCANCODE_LSHIFT] = imgui.Key.left_shift
  126. key_map[SDL_SCANCODE_RSHIFT] = imgui.Key.right_shift
  127. key_map[SDL_SCANCODE_LALT] = imgui.Key.left_alt
  128. key_map[SDL_SCANCODE_RALT] = imgui.Key.right_alt
  129. key_map[SDL_SCANCODE_LGUI] = imgui.Key.left_super
  130. key_map[SDL_SCANCODE_RGUI] = imgui.Key.right_super
  131. # Media Keys
  132. key_map[SDL_SCANCODE_MEDIA_NEXT_TRACK] = imgui.Key.app_forward
  133. key_map[SDL_SCANCODE_MEDIA_PREVIOUS_TRACK] = imgui.Key.app_back
  134. # key_map[SDL_SCANCODE_MEDIA_STOP] = imgui.Key.none
  135. # key_map[SDL_SCANCODE_MEDIA_PLAY] = imgui.Key.none
  136. # key_map[SDL_SCANCODE_MUTE] = imgui.Key.none
  137. # Application Keys
  138. key_map[SDL_SCANCODE_APPLICATION] = imgui.Key.menu
  139. # Lock Keys
  140. key_map[SDL_SCANCODE_CAPSLOCK] = imgui.Key.caps_lock
  141. key_map[SDL_SCANCODE_SCROLLLOCK] = imgui.Key.scroll_lock
  142. key_map[SDL_SCANCODE_NUMLOCKCLEAR] = imgui.Key.num_lock
  143. # Symbol Keys
  144. key_map[SDL_SCANCODE_GRAVE] = imgui.Key.grave_accent # `
  145. key_map[SDL_SCANCODE_MINUS] = imgui.Key.minus # -
  146. key_map[SDL_SCANCODE_EQUALS] = imgui.Key.equal # =
  147. key_map[SDL_SCANCODE_LEFTBRACKET] = imgui.Key.left_bracket # [
  148. key_map[SDL_SCANCODE_RIGHTBRACKET] = imgui.Key.right_bracket # ]
  149. key_map[SDL_SCANCODE_BACKSLASH] = imgui.Key.backslash # \
  150. key_map[SDL_SCANCODE_SEMICOLON] = imgui.Key.semicolon # ;
  151. key_map[SDL_SCANCODE_APOSTROPHE] = imgui.Key.apostrophe # '
  152. key_map[SDL_SCANCODE_COMMA] = imgui.Key.comma # ,
  153. key_map[SDL_SCANCODE_PERIOD] = imgui.Key.period # .
  154. key_map[SDL_SCANCODE_SLASH] = imgui.Key.slash # /
  155. # Miscellaneous Keys
  156. key_map[SDL_SCANCODE_PRINTSCREEN] = imgui.Key.print_screen
  157. key_map[SDL_SCANCODE_PAUSE] = imgui.Key.pause
  158. def process_event(self, event):
  159. io = self.io
  160. if event.type == SDL_EVENT_MOUSE_MOTION:
  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_EVENT_MOUSE_WHEEL:
  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_EVENT_MOUSE_BUTTON_DOWN or event.type == SDL_EVENT_MOUSE_BUTTON_UP:
  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_EVENT_MOUSE_BUTTON_DOWN
  178. io.add_mouse_button_event(imgui_button, down)
  179. return True
  180. if event.type == SDL_EVENT_KEY_UP or event.type == SDL_EVENT_KEY_DOWN:
  181. sdl_key = event.key.scancode
  182. if sdl_key in self.key_map:
  183. imgui_key = self.key_map[sdl_key]
  184. down = event.type == SDL_EVENT_KEY_DOWN
  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_EVENT_TEXT_INPUT:
  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_EVENT_GAMEPAD_BUTTON_DOWN or event.type == SDL_EVENT_GAMEPAD_BUTTON_UP:
  201. button = event.gbutton.button
  202. is_pressed = event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN
  203. # Map SDL_CONTROLLER_BUTTON_* to imgui keys
  204. if button == SDL_GAMEPAD_BUTTON_LABEL_A:
  205. imgui.get_io().add_key_event(imgui.Key.gamepad_face_down, is_pressed)
  206. elif button == SDL_GAMEPAD_BUTTON_LABEL_B:
  207. imgui.get_io().add_key_event(imgui.Key.gamepad_face_right, is_pressed)
  208. elif button == SDL_GAMEPAD_BUTTON_LABEL_X:
  209. imgui.get_io().add_key_event(imgui.Key.gamepad_face_left, is_pressed)
  210. elif button == SDL_GAMEPAD_BUTTON_LABEL_Y:
  211. imgui.get_io().add_key_event(imgui.Key.gamepad_face_up, is_pressed)
  212. elif button == SDL_GAMEPAD_BUTTON_DPAD_UP:
  213. imgui.get_io().add_key_event(imgui.Key.gamepad_dpad_up, is_pressed)
  214. elif button == SDL_GAMEPAD_BUTTON_DPAD_DOWN:
  215. imgui.get_io().add_key_event(imgui.Key.gamepad_dpad_down, is_pressed)
  216. elif button == SDL_GAMEPAD_BUTTON_DPAD_LEFT:
  217. imgui.get_io().add_key_event(imgui.Key.gamepad_dpad_left, is_pressed)
  218. elif button == SDL_GAMEPAD_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