# -*- coding: utf-8 -*- from __future__ import absolute_import # Workaround for PyOpenGL 3.1.6+ on Wayland: GLFW uses X11/XWayland but PyOpenGL # defaults to Wayland EGL, causing a context mismatch. Force the X11 backend. # Must be set before any `import OpenGL` happens (the import below pulls it in). # See https://github.com/pthom/imgui_bundle/issues/321 import os if os.getenv("XDG_SESSION_TYPE") == "wayland" and not os.getenv("PYOPENGL_PLATFORM"): os.environ["PYOPENGL_PLATFORM"] = "x11" from imgui_bundle import imgui import glfw # pip install glfw from imgui_bundle.python_backends import compute_fb_scale from .opengl_backend_programmable import ProgrammablePipelineRenderer from typing import Dict, Any GlfwKey = int class GlfwRenderer(ProgrammablePipelineRenderer): key_map: Dict[GlfwKey, imgui.Key] def __init__(self, window: Any, attach_callbacks: bool = True) -> None: super(GlfwRenderer, self).__init__() self.window = window if attach_callbacks: glfw.set_key_callback(self.window, self.keyboard_callback) glfw.set_cursor_pos_callback(self.window, self.mouse_callback) glfw.set_mouse_button_callback(self.window, self.mouse_button_callback) glfw.set_window_size_callback(self.window, self.resize_callback) glfw.set_char_callback(self.window, self.char_callback) glfw.set_scroll_callback(self.window, self.scroll_callback) self.io.display_size = glfw.get_framebuffer_size(self.window) def get_clipboard_text(_ctx: imgui.internal.Context) -> str: s = glfw.get_clipboard_string(self.window) return s.decode() # type: ignore def set_clipboard_text(_ctx: imgui.internal.Context, text: str) -> None: glfw.set_clipboard_string(self.window, text) imgui.get_platform_io().platform_get_clipboard_text_fn = get_clipboard_text imgui.get_platform_io().platform_set_clipboard_text_fn = set_clipboard_text self._map_keys() self._gui_time = None def _map_keys(self): self.key_map = {} key_map = self.key_map # Control Keys key_map[glfw.KEY_TAB] = imgui.Key.tab key_map[glfw.KEY_BACKSPACE] = imgui.Key.backspace key_map[glfw.KEY_ENTER] = imgui.Key.enter key_map[glfw.KEY_ESCAPE] = imgui.Key.escape key_map[glfw.KEY_INSERT] = imgui.Key.insert key_map[glfw.KEY_DELETE] = imgui.Key.delete key_map[glfw.KEY_SPACE] = imgui.Key.space # Function Keys key_map[glfw.KEY_F1] = imgui.Key.f1 key_map[glfw.KEY_F2] = imgui.Key.f2 key_map[glfw.KEY_F3] = imgui.Key.f3 key_map[glfw.KEY_F4] = imgui.Key.f4 key_map[glfw.KEY_F5] = imgui.Key.f5 key_map[glfw.KEY_F6] = imgui.Key.f6 key_map[glfw.KEY_F7] = imgui.Key.f7 key_map[glfw.KEY_F8] = imgui.Key.f8 key_map[glfw.KEY_F9] = imgui.Key.f9 key_map[glfw.KEY_F10] = imgui.Key.f10 key_map[glfw.KEY_F11] = imgui.Key.f11 key_map[glfw.KEY_F12] = imgui.Key.f12 # Navigation Keys key_map[glfw.KEY_LEFT] = imgui.Key.left_arrow key_map[glfw.KEY_RIGHT] = imgui.Key.right_arrow key_map[glfw.KEY_UP] = imgui.Key.up_arrow key_map[glfw.KEY_DOWN] = imgui.Key.down_arrow key_map[glfw.KEY_PAGE_UP] = imgui.Key.page_up key_map[glfw.KEY_PAGE_DOWN] = imgui.Key.page_down key_map[glfw.KEY_HOME] = imgui.Key.home key_map[glfw.KEY_END] = imgui.Key.end # Numeric Keys key_map[glfw.KEY_0] = imgui.Key._0 key_map[glfw.KEY_1] = imgui.Key._1 key_map[glfw.KEY_2] = imgui.Key._2 key_map[glfw.KEY_3] = imgui.Key._3 key_map[glfw.KEY_4] = imgui.Key._4 key_map[glfw.KEY_5] = imgui.Key._5 key_map[glfw.KEY_6] = imgui.Key._6 key_map[glfw.KEY_7] = imgui.Key._7 key_map[glfw.KEY_8] = imgui.Key._8 key_map[glfw.KEY_9] = imgui.Key._9 # Keypad Keys key_map[glfw.KEY_KP_ENTER] = imgui.Key.keypad_enter key_map[glfw.KEY_KP_0] = imgui.Key.keypad0 key_map[glfw.KEY_KP_1] = imgui.Key.keypad1 key_map[glfw.KEY_KP_2] = imgui.Key.keypad2 key_map[glfw.KEY_KP_3] = imgui.Key.keypad3 key_map[glfw.KEY_KP_4] = imgui.Key.keypad4 key_map[glfw.KEY_KP_5] = imgui.Key.keypad5 key_map[glfw.KEY_KP_6] = imgui.Key.keypad6 key_map[glfw.KEY_KP_7] = imgui.Key.keypad7 key_map[glfw.KEY_KP_8] = imgui.Key.keypad8 key_map[glfw.KEY_KP_9] = imgui.Key.keypad9 key_map[glfw.KEY_KP_DECIMAL] = imgui.Key.keypad_decimal key_map[glfw.KEY_KP_DIVIDE] = imgui.Key.keypad_divide key_map[glfw.KEY_KP_MULTIPLY] = imgui.Key.keypad_multiply key_map[glfw.KEY_KP_SUBTRACT] = imgui.Key.keypad_subtract key_map[glfw.KEY_KP_ADD] = imgui.Key.keypad_add key_map[glfw.KEY_KP_EQUAL] = imgui.Key.keypad_equal # Alphabetic Keys key_map[glfw.KEY_A] = imgui.Key.a key_map[glfw.KEY_B] = imgui.Key.b key_map[glfw.KEY_C] = imgui.Key.c key_map[glfw.KEY_D] = imgui.Key.d key_map[glfw.KEY_E] = imgui.Key.e key_map[glfw.KEY_F] = imgui.Key.f key_map[glfw.KEY_G] = imgui.Key.g key_map[glfw.KEY_H] = imgui.Key.h key_map[glfw.KEY_I] = imgui.Key.i key_map[glfw.KEY_J] = imgui.Key.j key_map[glfw.KEY_K] = imgui.Key.k key_map[glfw.KEY_L] = imgui.Key.l key_map[glfw.KEY_M] = imgui.Key.m key_map[glfw.KEY_N] = imgui.Key.n key_map[glfw.KEY_O] = imgui.Key.o key_map[glfw.KEY_P] = imgui.Key.p key_map[glfw.KEY_Q] = imgui.Key.q key_map[glfw.KEY_R] = imgui.Key.r key_map[glfw.KEY_S] = imgui.Key.s key_map[glfw.KEY_T] = imgui.Key.t key_map[glfw.KEY_U] = imgui.Key.u key_map[glfw.KEY_V] = imgui.Key.v key_map[glfw.KEY_W] = imgui.Key.w key_map[glfw.KEY_X] = imgui.Key.x key_map[glfw.KEY_Y] = imgui.Key.y key_map[glfw.KEY_Z] = imgui.Key.z # Modifier Keys key_map[glfw.KEY_LEFT_CONTROL] = imgui.Key.left_ctrl key_map[glfw.KEY_RIGHT_CONTROL] = imgui.Key.right_ctrl key_map[glfw.KEY_LEFT_SHIFT] = imgui.Key.left_shift key_map[glfw.KEY_RIGHT_SHIFT] = imgui.Key.right_shift key_map[glfw.KEY_LEFT_ALT] = imgui.Key.left_alt key_map[glfw.KEY_RIGHT_ALT] = imgui.Key.right_alt key_map[glfw.KEY_LEFT_SUPER] = imgui.Key.left_super key_map[glfw.KEY_RIGHT_SUPER] = imgui.Key.right_super # Symbol Keys key_map[glfw.KEY_APOSTROPHE] = imgui.Key.apostrophe # ' key_map[glfw.KEY_COMMA] = imgui.Key.comma # , key_map[glfw.KEY_MINUS] = imgui.Key.minus # - key_map[glfw.KEY_PERIOD] = imgui.Key.period # . key_map[glfw.KEY_SLASH] = imgui.Key.slash # / key_map[glfw.KEY_SEMICOLON] = imgui.Key.semicolon # ; key_map[glfw.KEY_EQUAL] = imgui.Key.equal # = key_map[glfw.KEY_LEFT_BRACKET] = imgui.Key.left_bracket # [ key_map[glfw.KEY_BACKSLASH] = imgui.Key.backslash # \ key_map[glfw.KEY_RIGHT_BRACKET] = imgui.Key.right_bracket # ] key_map[glfw.KEY_GRAVE_ACCENT] = imgui.Key.grave_accent # ` # Lock Keys key_map[glfw.KEY_CAPS_LOCK] = imgui.Key.caps_lock key_map[glfw.KEY_SCROLL_LOCK] = imgui.Key.scroll_lock key_map[glfw.KEY_NUM_LOCK] = imgui.Key.num_lock # Miscellaneous Keys key_map[glfw.KEY_PRINT_SCREEN] = imgui.Key.print_screen key_map[glfw.KEY_PAUSE] = imgui.Key.pause # Application Keys key_map[glfw.KEY_MENU] = imgui.Key.menu def keyboard_callback(self, window, glfw_key: int, scancode, action, mods) -> None: # type: ignore # perf: local for faster access io = self.io if glfw_key not in self.key_map: return imgui_key = self.key_map[glfw_key] down = action != glfw.RELEASE io.add_key_event(imgui_key, down) # Handle modifiers, since ImGui has an additional mod_ctrl / shift / etc if imgui_key == imgui.Key.left_ctrl or imgui_key == imgui.Key.right_ctrl: io.add_key_event(imgui.Key.mod_ctrl, down) if imgui_key == imgui.Key.left_shift or imgui_key == imgui.Key.right_shift: io.add_key_event(imgui.Key.mod_shift, down) if imgui_key == imgui.Key.left_alt or imgui_key == imgui.Key.right_alt: io.add_key_event(imgui.Key.mod_alt, down) if imgui_key == imgui.Key.left_super or imgui_key == imgui.Key.right_super: io.add_key_event(imgui.Key.mod_super, down) def char_callback(self, window, char): io = imgui.get_io() if 0 < char < 0x10000: io.add_input_character(char) def resize_callback(self, window, width, height): self.io.display_size = width, height def mouse_callback(self, *args, **kwargs): if glfw.get_window_attrib(self.window, glfw.FOCUSED): mouse_pos = glfw.get_cursor_pos(self.window) self.io.add_mouse_pos_event(mouse_pos[0], mouse_pos[1]) else: self.io.add_mouse_pos_event(-1, -1) def mouse_button_callback(self, window, button, action, mods): self.io.add_mouse_button_event(button, action == glfw.PRESS) def scroll_callback(self, window, x_offset, y_offset): self.io.add_mouse_wheel_event(x_offset, y_offset) def process_inputs(self): io = imgui.get_io() window_size = glfw.get_window_size(self.window) fb_size = glfw.get_framebuffer_size(self.window) io.display_size = window_size io.display_framebuffer_scale = compute_fb_scale(window_size, fb_size) io.delta_time = 1.0 / 60 current_time = glfw.get_time() if self._gui_time: self.io.delta_time = current_time - self._gui_time else: self.io.delta_time = 1.0 / 60.0 if io.delta_time <= 0.0: io.delta_time = 1.0 / 1000.0 self._gui_time = current_time