glfw_backend.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. # -*- coding: utf-8 -*-
  2. from __future__ import absolute_import
  3. # Workaround for PyOpenGL 3.1.6+ on Wayland: GLFW uses X11/XWayland but PyOpenGL
  4. # defaults to Wayland EGL, causing a context mismatch. Force the X11 backend.
  5. # Must be set before any `import OpenGL` happens (the import below pulls it in).
  6. # See https://github.com/pthom/imgui_bundle/issues/321
  7. import os
  8. if os.getenv("XDG_SESSION_TYPE") == "wayland" and not os.getenv("PYOPENGL_PLATFORM"):
  9. os.environ["PYOPENGL_PLATFORM"] = "x11"
  10. from imgui_bundle import imgui
  11. import glfw # pip install glfw
  12. from imgui_bundle.python_backends import compute_fb_scale
  13. from .opengl_backend_programmable import ProgrammablePipelineRenderer
  14. from typing import Dict, Any
  15. GlfwKey = int
  16. class GlfwRenderer(ProgrammablePipelineRenderer):
  17. key_map: Dict[GlfwKey, imgui.Key]
  18. def __init__(self, window: Any, attach_callbacks: bool = True) -> None:
  19. super(GlfwRenderer, self).__init__()
  20. self.window = window
  21. if attach_callbacks:
  22. glfw.set_key_callback(self.window, self.keyboard_callback)
  23. glfw.set_cursor_pos_callback(self.window, self.mouse_callback)
  24. glfw.set_mouse_button_callback(self.window, self.mouse_button_callback)
  25. glfw.set_window_size_callback(self.window, self.resize_callback)
  26. glfw.set_char_callback(self.window, self.char_callback)
  27. glfw.set_scroll_callback(self.window, self.scroll_callback)
  28. self.io.display_size = glfw.get_framebuffer_size(self.window)
  29. def get_clipboard_text(_ctx: imgui.internal.Context) -> str:
  30. s = glfw.get_clipboard_string(self.window)
  31. return s.decode() # type: ignore
  32. def set_clipboard_text(_ctx: imgui.internal.Context, text: str) -> None:
  33. glfw.set_clipboard_string(self.window, text)
  34. imgui.get_platform_io().platform_get_clipboard_text_fn = get_clipboard_text
  35. imgui.get_platform_io().platform_set_clipboard_text_fn = set_clipboard_text
  36. self._map_keys()
  37. self._gui_time = None
  38. def _map_keys(self):
  39. self.key_map = {}
  40. key_map = self.key_map
  41. # Control Keys
  42. key_map[glfw.KEY_TAB] = imgui.Key.tab
  43. key_map[glfw.KEY_BACKSPACE] = imgui.Key.backspace
  44. key_map[glfw.KEY_ENTER] = imgui.Key.enter
  45. key_map[glfw.KEY_ESCAPE] = imgui.Key.escape
  46. key_map[glfw.KEY_INSERT] = imgui.Key.insert
  47. key_map[glfw.KEY_DELETE] = imgui.Key.delete
  48. key_map[glfw.KEY_SPACE] = imgui.Key.space
  49. # Function Keys
  50. key_map[glfw.KEY_F1] = imgui.Key.f1
  51. key_map[glfw.KEY_F2] = imgui.Key.f2
  52. key_map[glfw.KEY_F3] = imgui.Key.f3
  53. key_map[glfw.KEY_F4] = imgui.Key.f4
  54. key_map[glfw.KEY_F5] = imgui.Key.f5
  55. key_map[glfw.KEY_F6] = imgui.Key.f6
  56. key_map[glfw.KEY_F7] = imgui.Key.f7
  57. key_map[glfw.KEY_F8] = imgui.Key.f8
  58. key_map[glfw.KEY_F9] = imgui.Key.f9
  59. key_map[glfw.KEY_F10] = imgui.Key.f10
  60. key_map[glfw.KEY_F11] = imgui.Key.f11
  61. key_map[glfw.KEY_F12] = imgui.Key.f12
  62. # Navigation Keys
  63. key_map[glfw.KEY_LEFT] = imgui.Key.left_arrow
  64. key_map[glfw.KEY_RIGHT] = imgui.Key.right_arrow
  65. key_map[glfw.KEY_UP] = imgui.Key.up_arrow
  66. key_map[glfw.KEY_DOWN] = imgui.Key.down_arrow
  67. key_map[glfw.KEY_PAGE_UP] = imgui.Key.page_up
  68. key_map[glfw.KEY_PAGE_DOWN] = imgui.Key.page_down
  69. key_map[glfw.KEY_HOME] = imgui.Key.home
  70. key_map[glfw.KEY_END] = imgui.Key.end
  71. # Numeric Keys
  72. key_map[glfw.KEY_0] = imgui.Key._0
  73. key_map[glfw.KEY_1] = imgui.Key._1
  74. key_map[glfw.KEY_2] = imgui.Key._2
  75. key_map[glfw.KEY_3] = imgui.Key._3
  76. key_map[glfw.KEY_4] = imgui.Key._4
  77. key_map[glfw.KEY_5] = imgui.Key._5
  78. key_map[glfw.KEY_6] = imgui.Key._6
  79. key_map[glfw.KEY_7] = imgui.Key._7
  80. key_map[glfw.KEY_8] = imgui.Key._8
  81. key_map[glfw.KEY_9] = imgui.Key._9
  82. # Keypad Keys
  83. key_map[glfw.KEY_KP_ENTER] = imgui.Key.keypad_enter
  84. key_map[glfw.KEY_KP_0] = imgui.Key.keypad0
  85. key_map[glfw.KEY_KP_1] = imgui.Key.keypad1
  86. key_map[glfw.KEY_KP_2] = imgui.Key.keypad2
  87. key_map[glfw.KEY_KP_3] = imgui.Key.keypad3
  88. key_map[glfw.KEY_KP_4] = imgui.Key.keypad4
  89. key_map[glfw.KEY_KP_5] = imgui.Key.keypad5
  90. key_map[glfw.KEY_KP_6] = imgui.Key.keypad6
  91. key_map[glfw.KEY_KP_7] = imgui.Key.keypad7
  92. key_map[glfw.KEY_KP_8] = imgui.Key.keypad8
  93. key_map[glfw.KEY_KP_9] = imgui.Key.keypad9
  94. key_map[glfw.KEY_KP_DECIMAL] = imgui.Key.keypad_decimal
  95. key_map[glfw.KEY_KP_DIVIDE] = imgui.Key.keypad_divide
  96. key_map[glfw.KEY_KP_MULTIPLY] = imgui.Key.keypad_multiply
  97. key_map[glfw.KEY_KP_SUBTRACT] = imgui.Key.keypad_subtract
  98. key_map[glfw.KEY_KP_ADD] = imgui.Key.keypad_add
  99. key_map[glfw.KEY_KP_EQUAL] = imgui.Key.keypad_equal
  100. # Alphabetic Keys
  101. key_map[glfw.KEY_A] = imgui.Key.a
  102. key_map[glfw.KEY_B] = imgui.Key.b
  103. key_map[glfw.KEY_C] = imgui.Key.c
  104. key_map[glfw.KEY_D] = imgui.Key.d
  105. key_map[glfw.KEY_E] = imgui.Key.e
  106. key_map[glfw.KEY_F] = imgui.Key.f
  107. key_map[glfw.KEY_G] = imgui.Key.g
  108. key_map[glfw.KEY_H] = imgui.Key.h
  109. key_map[glfw.KEY_I] = imgui.Key.i
  110. key_map[glfw.KEY_J] = imgui.Key.j
  111. key_map[glfw.KEY_K] = imgui.Key.k
  112. key_map[glfw.KEY_L] = imgui.Key.l
  113. key_map[glfw.KEY_M] = imgui.Key.m
  114. key_map[glfw.KEY_N] = imgui.Key.n
  115. key_map[glfw.KEY_O] = imgui.Key.o
  116. key_map[glfw.KEY_P] = imgui.Key.p
  117. key_map[glfw.KEY_Q] = imgui.Key.q
  118. key_map[glfw.KEY_R] = imgui.Key.r
  119. key_map[glfw.KEY_S] = imgui.Key.s
  120. key_map[glfw.KEY_T] = imgui.Key.t
  121. key_map[glfw.KEY_U] = imgui.Key.u
  122. key_map[glfw.KEY_V] = imgui.Key.v
  123. key_map[glfw.KEY_W] = imgui.Key.w
  124. key_map[glfw.KEY_X] = imgui.Key.x
  125. key_map[glfw.KEY_Y] = imgui.Key.y
  126. key_map[glfw.KEY_Z] = imgui.Key.z
  127. # Modifier Keys
  128. key_map[glfw.KEY_LEFT_CONTROL] = imgui.Key.left_ctrl
  129. key_map[glfw.KEY_RIGHT_CONTROL] = imgui.Key.right_ctrl
  130. key_map[glfw.KEY_LEFT_SHIFT] = imgui.Key.left_shift
  131. key_map[glfw.KEY_RIGHT_SHIFT] = imgui.Key.right_shift
  132. key_map[glfw.KEY_LEFT_ALT] = imgui.Key.left_alt
  133. key_map[glfw.KEY_RIGHT_ALT] = imgui.Key.right_alt
  134. key_map[glfw.KEY_LEFT_SUPER] = imgui.Key.left_super
  135. key_map[glfw.KEY_RIGHT_SUPER] = imgui.Key.right_super
  136. # Symbol Keys
  137. key_map[glfw.KEY_APOSTROPHE] = imgui.Key.apostrophe # '
  138. key_map[glfw.KEY_COMMA] = imgui.Key.comma # ,
  139. key_map[glfw.KEY_MINUS] = imgui.Key.minus # -
  140. key_map[glfw.KEY_PERIOD] = imgui.Key.period # .
  141. key_map[glfw.KEY_SLASH] = imgui.Key.slash # /
  142. key_map[glfw.KEY_SEMICOLON] = imgui.Key.semicolon # ;
  143. key_map[glfw.KEY_EQUAL] = imgui.Key.equal # =
  144. key_map[glfw.KEY_LEFT_BRACKET] = imgui.Key.left_bracket # [
  145. key_map[glfw.KEY_BACKSLASH] = imgui.Key.backslash # \
  146. key_map[glfw.KEY_RIGHT_BRACKET] = imgui.Key.right_bracket # ]
  147. key_map[glfw.KEY_GRAVE_ACCENT] = imgui.Key.grave_accent # `
  148. # Lock Keys
  149. key_map[glfw.KEY_CAPS_LOCK] = imgui.Key.caps_lock
  150. key_map[glfw.KEY_SCROLL_LOCK] = imgui.Key.scroll_lock
  151. key_map[glfw.KEY_NUM_LOCK] = imgui.Key.num_lock
  152. # Miscellaneous Keys
  153. key_map[glfw.KEY_PRINT_SCREEN] = imgui.Key.print_screen
  154. key_map[glfw.KEY_PAUSE] = imgui.Key.pause
  155. # Application Keys
  156. key_map[glfw.KEY_MENU] = imgui.Key.menu
  157. def keyboard_callback(self, window, glfw_key: int, scancode, action, mods) -> None: # type: ignore
  158. # perf: local for faster access
  159. io = self.io
  160. if glfw_key not in self.key_map:
  161. return
  162. imgui_key = self.key_map[glfw_key]
  163. down = action != glfw.RELEASE
  164. io.add_key_event(imgui_key, down)
  165. # Handle modifiers, since ImGui has an additional mod_ctrl / shift / etc
  166. if imgui_key == imgui.Key.left_ctrl or imgui_key == imgui.Key.right_ctrl:
  167. io.add_key_event(imgui.Key.mod_ctrl, down)
  168. if imgui_key == imgui.Key.left_shift or imgui_key == imgui.Key.right_shift:
  169. io.add_key_event(imgui.Key.mod_shift, down)
  170. if imgui_key == imgui.Key.left_alt or imgui_key == imgui.Key.right_alt:
  171. io.add_key_event(imgui.Key.mod_alt, down)
  172. if imgui_key == imgui.Key.left_super or imgui_key == imgui.Key.right_super:
  173. io.add_key_event(imgui.Key.mod_super, down)
  174. def char_callback(self, window, char):
  175. io = imgui.get_io()
  176. if 0 < char < 0x10000:
  177. io.add_input_character(char)
  178. def resize_callback(self, window, width, height):
  179. self.io.display_size = width, height
  180. def mouse_callback(self, *args, **kwargs):
  181. if glfw.get_window_attrib(self.window, glfw.FOCUSED):
  182. mouse_pos = glfw.get_cursor_pos(self.window)
  183. self.io.add_mouse_pos_event(mouse_pos[0], mouse_pos[1])
  184. else:
  185. self.io.add_mouse_pos_event(-1, -1)
  186. def mouse_button_callback(self, window, button, action, mods):
  187. self.io.add_mouse_button_event(button, action == glfw.PRESS)
  188. def scroll_callback(self, window, x_offset, y_offset):
  189. self.io.add_mouse_wheel_event(x_offset, y_offset)
  190. def process_inputs(self):
  191. io = imgui.get_io()
  192. window_size = glfw.get_window_size(self.window)
  193. fb_size = glfw.get_framebuffer_size(self.window)
  194. io.display_size = window_size
  195. io.display_framebuffer_scale = compute_fb_scale(window_size, fb_size)
  196. io.delta_time = 1.0 / 60
  197. current_time = glfw.get_time()
  198. if self._gui_time:
  199. self.io.delta_time = current_time - self._gui_time
  200. else:
  201. self.io.delta_time = 1.0 / 60.0
  202. if io.delta_time <= 0.0:
  203. io.delta_time = 1.0 / 1000.0
  204. self._gui_time = current_time