pyglet_backend.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. # -*- coding: utf-8 -*-
  2. #type: ignore
  3. from __future__ import absolute_import
  4. from imgui_bundle import imgui
  5. from pyglet.window import key, mouse, Window
  6. import pyglet
  7. import pyglet.clock
  8. from imgui_bundle.python_backends import compute_fb_scale
  9. from imgui_bundle.python_backends.opengl_backend_programmable import ProgrammablePipelineRenderer
  10. class PygletMixin(object):
  11. _gui_time = None
  12. MOUSE_CURSORS = {
  13. imgui.MouseCursor_.arrow.value: Window.CURSOR_DEFAULT,
  14. imgui.MouseCursor_.text_input.value: Window.CURSOR_TEXT,
  15. imgui.MouseCursor_.resize_all.value: Window.CURSOR_SIZE,
  16. imgui.MouseCursor_.resize_ns.value: Window.CURSOR_SIZE_UP_DOWN,
  17. imgui.MouseCursor_.resize_ew.value: Window.CURSOR_SIZE_LEFT_RIGHT,
  18. imgui.MouseCursor_.resize_nesw.value: Window.CURSOR_SIZE_DOWN_LEFT,
  19. imgui.MouseCursor_.resize_nwse.value: Window.CURSOR_SIZE_DOWN_RIGHT,
  20. imgui.MouseCursor_.hand.value: Window.CURSOR_HAND,
  21. }
  22. def __init__(self):
  23. super(PygletMixin, self).__init__()
  24. self._cursor = -2
  25. self._window = None
  26. # Let Dear imgui know we have mouse cursor support
  27. self.io = imgui.get_io()
  28. self.io.backend_flags |= imgui.BackendFlags_.has_mouse_cursors
  29. self._map_keys()
  30. def _map_keys(self):
  31. self.key_map = {}
  32. key_map = self.key_map
  33. # Control Keys
  34. key_map[key.TAB] = imgui.Key.tab
  35. key_map[key.BACKSPACE] = imgui.Key.backspace
  36. key_map[key.RETURN] = imgui.Key.enter
  37. key_map[key.ESCAPE] = imgui.Key.escape
  38. key_map[key.INSERT] = imgui.Key.insert
  39. key_map[key.DELETE] = imgui.Key.delete
  40. key_map[key.SPACE] = imgui.Key.space
  41. # Function Keys
  42. key_map[key.F1] = imgui.Key.f1
  43. key_map[key.F2] = imgui.Key.f2
  44. key_map[key.F3] = imgui.Key.f3
  45. key_map[key.F4] = imgui.Key.f4
  46. key_map[key.F5] = imgui.Key.f5
  47. key_map[key.F6] = imgui.Key.f6
  48. key_map[key.F7] = imgui.Key.f7
  49. key_map[key.F8] = imgui.Key.f8
  50. key_map[key.F9] = imgui.Key.f9
  51. key_map[key.F10] = imgui.Key.f10
  52. key_map[key.F11] = imgui.Key.f11
  53. key_map[key.F12] = imgui.Key.f12
  54. # Navigation Keys
  55. key_map[key.LEFT] = imgui.Key.left_arrow
  56. key_map[key.RIGHT] = imgui.Key.right_arrow
  57. key_map[key.UP] = imgui.Key.up_arrow
  58. key_map[key.DOWN] = imgui.Key.down_arrow
  59. key_map[key.PAGEUP] = imgui.Key.page_up
  60. key_map[key.PAGEDOWN] = imgui.Key.page_down
  61. key_map[key.HOME] = imgui.Key.home
  62. key_map[key.END] = imgui.Key.end
  63. # Numeric Keys
  64. key_map[key._0] = imgui.Key._0
  65. key_map[key._1] = imgui.Key._1
  66. key_map[key._2] = imgui.Key._2
  67. key_map[key._3] = imgui.Key._3
  68. key_map[key._4] = imgui.Key._4
  69. key_map[key._5] = imgui.Key._5
  70. key_map[key._6] = imgui.Key._6
  71. key_map[key._7] = imgui.Key._7
  72. key_map[key._8] = imgui.Key._8
  73. key_map[key._9] = imgui.Key._9
  74. # Keypad Keys
  75. key_map[key.NUM_ENTER] = imgui.Key.keypad_enter
  76. key_map[key.NUM_0] = imgui.Key.keypad0
  77. key_map[key.NUM_1] = imgui.Key.keypad1
  78. key_map[key.NUM_2] = imgui.Key.keypad2
  79. key_map[key.NUM_3] = imgui.Key.keypad3
  80. key_map[key.NUM_4] = imgui.Key.keypad4
  81. key_map[key.NUM_5] = imgui.Key.keypad5
  82. key_map[key.NUM_6] = imgui.Key.keypad6
  83. key_map[key.NUM_7] = imgui.Key.keypad7
  84. key_map[key.NUM_8] = imgui.Key.keypad8
  85. key_map[key.NUM_9] = imgui.Key.keypad9
  86. key_map[key.NUM_DECIMAL] = imgui.Key.keypad_decimal
  87. key_map[key.NUM_DIVIDE] = imgui.Key.keypad_divide
  88. key_map[key.NUM_MULTIPLY] = imgui.Key.keypad_multiply
  89. key_map[key.NUM_SUBTRACT] = imgui.Key.keypad_subtract
  90. key_map[key.NUM_ADD] = imgui.Key.keypad_add
  91. # Alphabetic Keys
  92. key_map[key.A] = imgui.Key.a
  93. key_map[key.B] = imgui.Key.b
  94. key_map[key.C] = imgui.Key.c
  95. key_map[key.D] = imgui.Key.d
  96. key_map[key.E] = imgui.Key.e
  97. key_map[key.F] = imgui.Key.f
  98. key_map[key.G] = imgui.Key.g
  99. key_map[key.H] = imgui.Key.h
  100. key_map[key.I] = imgui.Key.i
  101. key_map[key.J] = imgui.Key.j
  102. key_map[key.K] = imgui.Key.k
  103. key_map[key.L] = imgui.Key.l
  104. key_map[key.M] = imgui.Key.m
  105. key_map[key.N] = imgui.Key.n
  106. key_map[key.O] = imgui.Key.o
  107. key_map[key.P] = imgui.Key.p
  108. key_map[key.Q] = imgui.Key.q
  109. key_map[key.R] = imgui.Key.r
  110. key_map[key.S] = imgui.Key.s
  111. key_map[key.T] = imgui.Key.t
  112. key_map[key.U] = imgui.Key.u
  113. key_map[key.V] = imgui.Key.v
  114. key_map[key.W] = imgui.Key.w
  115. key_map[key.X] = imgui.Key.x
  116. key_map[key.Y] = imgui.Key.y
  117. key_map[key.Z] = imgui.Key.z
  118. # Modifier Keys
  119. key_map[key.LCTRL] = imgui.Key.left_ctrl
  120. key_map[key.RCTRL] = imgui.Key.right_ctrl
  121. key_map[key.LSHIFT] = imgui.Key.left_shift
  122. key_map[key.RSHIFT] = imgui.Key.right_shift
  123. key_map[key.LALT] = imgui.Key.left_alt
  124. key_map[key.RALT] = imgui.Key.right_alt
  125. key_map[key.LCOMMAND] = imgui.Key.left_super
  126. key_map[key.RCOMMAND] = imgui.Key.right_super
  127. # Symbol Keys
  128. key_map[key.GRAVE] = imgui.Key.grave_accent # `
  129. key_map[key.MINUS] = imgui.Key.minus # -
  130. key_map[key.EQUAL] = imgui.Key.equal # =
  131. key_map[key.BRACKETLEFT] = imgui.Key.left_bracket # [
  132. key_map[key.BRACKETRIGHT] = imgui.Key.right_bracket # ]
  133. key_map[key.BACKSLASH] = imgui.Key.backslash # \
  134. key_map[key.SEMICOLON] = imgui.Key.semicolon # ;
  135. key_map[key.APOSTROPHE] = imgui.Key.apostrophe # '
  136. key_map[key.COMMA] = imgui.Key.comma # ,
  137. key_map[key.PERIOD] = imgui.Key.period # .
  138. key_map[key.SLASH] = imgui.Key.slash # /
  139. # Miscellaneous Keys
  140. key_map[key.PRINT] = imgui.Key.print_screen
  141. key_map[key.PAUSE] = imgui.Key.pause
  142. # Lock Keys
  143. key_map[key.CAPSLOCK] = imgui.Key.caps_lock
  144. key_map[key.NUMLOCK] = imgui.Key.num_lock
  145. key_map[key.SCROLLLOCK] = imgui.Key.scroll_lock
  146. def _set_pixel_ratio(self, window):
  147. window_size = window.get_size()
  148. self.io.display_size = window_size
  149. # It is conceivable that the pyglet version will not be solely
  150. # determinant of whether we use the fixed or programmable, so do some
  151. # minor introspection here to check.
  152. if hasattr(window, "get_viewport_size"):
  153. viewport_size = window.get_viewport_size()
  154. self.io.display_framebuffer_scale = compute_fb_scale(
  155. window_size, viewport_size
  156. )
  157. elif hasattr(window, "get_pixel_ratio"):
  158. self.io.display_framebuffer_scale = (
  159. window.get_pixel_ratio(),
  160. window.get_pixel_ratio(),
  161. )
  162. else:
  163. # Default to 1.0 in this unlikely circumstance
  164. self.io.display_fb_scale = (1.0, 1.0)
  165. def _attach_callbacks(self, window):
  166. self._window = window
  167. window.push_handlers(
  168. self.on_mouse_motion,
  169. self.on_key_press,
  170. self.on_key_release,
  171. self.on_text,
  172. self.on_mouse_drag,
  173. self.on_mouse_press,
  174. self.on_mouse_release,
  175. self.on_mouse_scroll,
  176. self.on_resize,
  177. )
  178. def _handle_mouse_cursor(self):
  179. if self.io.config_flags & imgui.ConfigFlags_.no_mouse_cursor_change.value:
  180. return
  181. mouse_cursor = imgui.get_mouse_cursor()
  182. window = self._window
  183. if self._cursor != mouse_cursor:
  184. self._cursor = mouse_cursor
  185. if mouse_cursor == imgui.MouseCursor_.none:
  186. window.set_mouse_visible(False)
  187. else:
  188. cursor = self.MOUSE_CURSORS.get(mouse_cursor)
  189. window.set_mouse_cursor(window.get_system_mouse_cursor(cursor))
  190. def _on_key(self, key_pressed, down):
  191. if key_pressed in self.key_map:
  192. imgui_key = self.key_map[key_pressed]
  193. self.io.add_key_event(imgui_key, down=down)
  194. # Handle modifiers, since ImGui has an additional mod_ctrl / shift / etc
  195. if imgui_key == imgui.Key.left_ctrl or imgui_key == imgui.Key.right_ctrl:
  196. self.io.add_key_event(imgui.Key.mod_ctrl, down)
  197. if imgui_key == imgui.Key.left_shift or imgui_key == imgui.Key.right_shift:
  198. self.io.add_key_event(imgui.Key.mod_shift, down)
  199. if imgui_key == imgui.Key.left_alt or imgui_key == imgui.Key.right_alt:
  200. self.io.add_key_event(imgui.Key.mod_alt, down)
  201. if imgui_key == imgui.Key.left_super or imgui_key == imgui.Key.right_super:
  202. self.io.add_key_event(imgui.Key.mod_super, down)
  203. def on_key_press(self, key_pressed, mods):
  204. self._on_key(key_pressed, True)
  205. def on_key_release(self, key_released, mods):
  206. self._on_key(key_released, False)
  207. def on_text(self, text):
  208. io = imgui.get_io()
  209. for char in text:
  210. io.add_input_character(ord(char))
  211. def on_mouse_motion(self, x, y, dx, dy):
  212. self.io.add_mouse_pos_event(x, self.io.display_size.y - y)
  213. def _on_mouse_button(self, pyglet_button, down):
  214. imgui_button = 0
  215. if pyglet_button == mouse.LEFT:
  216. imgui_button = 0
  217. elif pyglet_button == mouse.RIGHT:
  218. imgui_button = 1
  219. elif pyglet_button == mouse.MIDDLE:
  220. imgui_button = 2
  221. self.io.add_mouse_button_event(imgui_button, down)
  222. def on_mouse_press(self, x, y, button, modifiers):
  223. self._on_mouse_button(button, True)
  224. def on_mouse_release(self, x, y, button, modifiers):
  225. self._on_mouse_button(button, False)
  226. def on_mouse_drag(self, x, y, dx, dy, button, modifiers):
  227. self._on_mouse_button(button, True)
  228. self.on_mouse_motion(x, y, dx, dy)
  229. def on_mouse_scroll(self, x, y, mods, scroll):
  230. self.io.add_mouse_wheel_event(0, -scroll)
  231. def on_resize(self, width, height):
  232. self.io.display_size = width, height
  233. def process_inputs(self):
  234. io = imgui.get_io()
  235. current_time = pyglet.clock.tick()
  236. if self._gui_time:
  237. io.delta_time = current_time - self._gui_time
  238. else:
  239. io.delta_time = 1.0 / 60.0
  240. if io.delta_time <= 0.0:
  241. io.delta_time = 1.0 / 1000.0
  242. self._gui_time = current_time
  243. class PygletProgrammablePipelineRenderer(PygletMixin, ProgrammablePipelineRenderer):
  244. def __init__(self, window, attach_callbacks=True):
  245. super(PygletProgrammablePipelineRenderer, self).__init__()
  246. self._set_pixel_ratio(window)
  247. if attach_callbacks:
  248. self._attach_callbacks(window)
  249. def render(self, draw_data):
  250. super(PygletProgrammablePipelineRenderer, self).render(draw_data)
  251. self._handle_mouse_cursor()
  252. def create_renderer(window, attach_callbacks=True):
  253. """
  254. This is a helper function that wraps the appropriate version of the Pyglet
  255. renderer class, based on the version of pyglet being used.
  256. """
  257. # Determine the context version
  258. # Pyglet < 2.0 has issues with ProgrammablePipeline even when the context
  259. # is OpenGL 3, so we need to check the pyglet version rather than looking
  260. # at window.config.major_version to see if we want to use programmable.
  261. assert int(pyglet.version.split('.')[0]) >= 2
  262. return PygletProgrammablePipelineRenderer(window, attach_callbacks)