Module concur.integrations.glfw

Main integration back-end.

Expand source code
"""Main integration back-end."""


import glfw
import OpenGL.GL as gl
import time

import imgui
from imgui.integrations.glfw import GlfwRenderer

from concur.integrations.opengl import create_offscreen_fb, get_fb_data


__pdoc__ = dict(create_window=False, begin_maximized_window=False, create_window_dock=False)


class PatchedGlfwRenderer(GlfwRenderer):
    """ Custom variant of Glfwrenderer in PyImGui:

    https://github.com/swistakm/pyimgui/blob/master/imgui/integrations/glfw.py

    This works around the issue that GLFW uses EN_US keyboard to specify the key codes
    in `keyboard_callback`. This meant that keyboard shortcuts were broken on non-querty
    keyboard layouts.

    See https://github.com/ocornut/imgui/issues/2959 for details.

    # Temporary try except fix until we find a better solution, if we don't apply this,
    # the app will crash if certain special keys are pressed.
    """
    def keyboard_callback(self, window, key, scancode, action, mods):
        try:
            _key = key
            if _key < 0x100:
                # Translate characters to the correct keyboard layout.
                key_name = glfw.get_key_name(key, 0)
                if key_name is not None:
                    _key = ord(key_name.upper())
            super(PatchedGlfwRenderer, self).keyboard_callback(window, _key, scancode, action, mods)
        except:
            super(PatchedGlfwRenderer, self).keyboard_callback(window, key, scancode, action, mods)


def create_window(window_name, width, height, visible=True, maximized=False):
    """ Create a GLFW window. """
    if not glfw.init():
        print("Could not initialize OpenGL context")
        exit(1)

    # OS X supports only forward-compatible core profiles from 3.2
    glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
    glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3)
    glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
    glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, gl.GL_TRUE)
    if not visible:
        glfw.window_hint(glfw.VISIBLE, glfw.FALSE)
    if maximized:
        glfw.window_hint(glfw.MAXIMIZED, glfw.TRUE)

    # Create a windowed mode window and its OpenGL context
    window = glfw.create_window(
        int(width), int(height), window_name, None, None
    )
    glfw.make_context_current(window)

    if not window:
        glfw.terminate()
        print("Could not initialize Window")
        exit(1)

    return window


def begin_maximized_window(name, glfw_window, menu_bar=False):
    imgui.set_next_window_position(0, 0)
    imgui.set_next_window_size(*glfw.get_window_size(glfw_window))
    imgui.push_style_var(imgui.STYLE_WINDOW_ROUNDING, 0)
    imgui.push_style_var(imgui.STYLE_WINDOW_BORDERSIZE, 0)
    window_flags = imgui.WINDOW_NO_TITLE_BAR | imgui.WINDOW_NO_COLLAPSE | \
        imgui.WINDOW_NO_RESIZE | imgui.WINDOW_NO_MOVE | \
        imgui.WINDOW_NO_BRING_TO_FRONT_ON_FOCUS | imgui.WINDOW_NO_NAV_FOCUS | \
        imgui.WINDOW_NO_DOCKING
    if menu_bar:
        window_flags |= imgui.WINDOW_MENU_BAR
    imgui.begin(name, True, window_flags)
    imgui.pop_style_var(2)


def create_window_dock(glfw_window, menu_bar=False):
    imgui.set_next_window_bg_alpha(0)
    imgui.push_style_var(imgui.STYLE_WINDOW_PADDING, (0, 0))
    begin_maximized_window("Background Window", glfw_window, menu_bar)
    imgui.pop_style_var(1)
    imgui.dock_space("Window Dock Space", 0., 0., 1 << 3)
    imgui.end()


def main(
        widget, name="Concur", width=640, height=480,
        fps=60, save_screencast=None, screencast_fps=60,
        menu_bar=False, maximized=False):
    """ Create a GLFW window, spin up the main loop, and display a given widget inside.

    To create a maximized window, pass width and height larger than the screen.

    Args:
        widget: The widget to display inside the window. When the widget returns, the application exits.
        name: Window name, displayed in the title bar and other OS outputs.
        width: Desired window width.
        height: Desired window height.
        fps: Maximum number of frames per second
        save_screencast: Capture and save the UI into a specified video file (experimental). Main window shouldn't
            be resized while the application is running when using this option.
        screencast_fps: Save the screencast video with a given FPS.
        menu_bar: Reserve space for `concur.widgets.main_menu_bar` at the top of the window.
        maximized: Create a maximized window.
    """
    if imgui.get_current_context() is None:
        imgui.create_context()

    # Set config flags
    imgui.get_io().config_flags |= imgui.CONFIG_DOCKING_ENABLE # | imgui.CONFIG_VIEWPORTS_ENABLE

    window = create_window(name, width, height, maximized=maximized)
    impl = PatchedGlfwRenderer(window)

    win_w, win_h = glfw.get_window_size(window)
    fb_w, fb_h = glfw.get_framebuffer_size(window)
    font_scaling_factor = max(float(fb_w) / win_w, float(fb_h) / win_h)
    imgui.get_io().font_global_scale /= font_scaling_factor
    impl.refresh_font_texture()  # Refresh the font texture in case user changed it

    # Using this feels significantly choppier than sleeping manually. TODO: investigate & fix
    # glfw.swap_interval(-1)
    if save_screencast:
        import imageio
        width, height = glfw.get_framebuffer_size(window)
        offscreen_fb = create_offscreen_fb(width, height)
        writer = imageio.get_writer(save_screencast, mode='I', fps=screencast_fps)

    try:
        while not glfw.window_should_close(window):
            t0 = time.perf_counter()
            glfw.poll_events()
            impl.process_inputs()

            imgui.new_frame()

            create_window_dock(window, menu_bar=menu_bar)
            begin_maximized_window("Default##Concur", window, menu_bar=menu_bar)

            try:
                next(widget)
            except StopIteration:
                break
            finally:
                imgui.end()
                imgui.render()

                gl.glClearColor(0.5, 0.5, 0.5, 1)
                gl.glClear(gl.GL_COLOR_BUFFER_BIT)

                if save_screencast:
                    gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, offscreen_fb)
                    impl.render(imgui.get_draw_data())
                    image = get_fb_data(offscreen_fb, width, height)
                    writer.append_data(image)
                gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)

                impl.render(imgui.get_draw_data())
                glfw.swap_buffers(window)

            t1 = time.perf_counter()
            if t1 - t0 < 1/fps:
                time.sleep(1/fps - (t1 - t0))
    finally:
        impl.shutdown()
        imgui.destroy_context(imgui.get_current_context())
        glfw.terminate()
        if save_screencast:
            writer.close()

Functions

def main(widget, name='Concur', width=640, height=480, fps=60, save_screencast=None, screencast_fps=60, menu_bar=False, maximized=False)

Create a GLFW window, spin up the main loop, and display a given widget inside.

To create a maximized window, pass width and height larger than the screen.

Args

widget
The widget to display inside the window. When the widget returns, the application exits.
name
Window name, displayed in the title bar and other OS outputs.
width
Desired window width.
height
Desired window height.
fps
Maximum number of frames per second
save_screencast
Capture and save the UI into a specified video file (experimental). Main window shouldn't be resized while the application is running when using this option.
screencast_fps
Save the screencast video with a given FPS.
menu_bar
Reserve space for main_menu_bar() at the top of the window.
maximized
Create a maximized window.
Expand source code
def main(
        widget, name="Concur", width=640, height=480,
        fps=60, save_screencast=None, screencast_fps=60,
        menu_bar=False, maximized=False):
    """ Create a GLFW window, spin up the main loop, and display a given widget inside.

    To create a maximized window, pass width and height larger than the screen.

    Args:
        widget: The widget to display inside the window. When the widget returns, the application exits.
        name: Window name, displayed in the title bar and other OS outputs.
        width: Desired window width.
        height: Desired window height.
        fps: Maximum number of frames per second
        save_screencast: Capture and save the UI into a specified video file (experimental). Main window shouldn't
            be resized while the application is running when using this option.
        screencast_fps: Save the screencast video with a given FPS.
        menu_bar: Reserve space for `concur.widgets.main_menu_bar` at the top of the window.
        maximized: Create a maximized window.
    """
    if imgui.get_current_context() is None:
        imgui.create_context()

    # Set config flags
    imgui.get_io().config_flags |= imgui.CONFIG_DOCKING_ENABLE # | imgui.CONFIG_VIEWPORTS_ENABLE

    window = create_window(name, width, height, maximized=maximized)
    impl = PatchedGlfwRenderer(window)

    win_w, win_h = glfw.get_window_size(window)
    fb_w, fb_h = glfw.get_framebuffer_size(window)
    font_scaling_factor = max(float(fb_w) / win_w, float(fb_h) / win_h)
    imgui.get_io().font_global_scale /= font_scaling_factor
    impl.refresh_font_texture()  # Refresh the font texture in case user changed it

    # Using this feels significantly choppier than sleeping manually. TODO: investigate & fix
    # glfw.swap_interval(-1)
    if save_screencast:
        import imageio
        width, height = glfw.get_framebuffer_size(window)
        offscreen_fb = create_offscreen_fb(width, height)
        writer = imageio.get_writer(save_screencast, mode='I', fps=screencast_fps)

    try:
        while not glfw.window_should_close(window):
            t0 = time.perf_counter()
            glfw.poll_events()
            impl.process_inputs()

            imgui.new_frame()

            create_window_dock(window, menu_bar=menu_bar)
            begin_maximized_window("Default##Concur", window, menu_bar=menu_bar)

            try:
                next(widget)
            except StopIteration:
                break
            finally:
                imgui.end()
                imgui.render()

                gl.glClearColor(0.5, 0.5, 0.5, 1)
                gl.glClear(gl.GL_COLOR_BUFFER_BIT)

                if save_screencast:
                    gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, offscreen_fb)
                    impl.render(imgui.get_draw_data())
                    image = get_fb_data(offscreen_fb, width, height)
                    writer.append_data(image)
                gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)

                impl.render(imgui.get_draw_data())
                glfw.swap_buffers(window)

            t1 = time.perf_counter()
            if t1 - t0 < 1/fps:
                time.sleep(1/fps - (t1 - t0))
    finally:
        impl.shutdown()
        imgui.destroy_context(imgui.get_current_context())
        glfw.terminate()
        if save_screencast:
            writer.close()

Classes

class PatchedGlfwRenderer (window, attach_callbacks=True)

Custom variant of Glfwrenderer in PyImGui:

https://github.com/swistakm/pyimgui/blob/master/imgui/integrations/glfw.py

This works around the issue that GLFW uses EN_US keyboard to specify the key codes in keyboard_callback. This meant that keyboard shortcuts were broken on non-querty keyboard layouts.

See https://github.com/ocornut/imgui/issues/2959 for details.

Temporary try except fix until we find a better solution, if we don't apply this,

the app will crash if certain special keys are pressed.

Expand source code
class PatchedGlfwRenderer(GlfwRenderer):
    """ Custom variant of Glfwrenderer in PyImGui:

    https://github.com/swistakm/pyimgui/blob/master/imgui/integrations/glfw.py

    This works around the issue that GLFW uses EN_US keyboard to specify the key codes
    in `keyboard_callback`. This meant that keyboard shortcuts were broken on non-querty
    keyboard layouts.

    See https://github.com/ocornut/imgui/issues/2959 for details.

    # Temporary try except fix until we find a better solution, if we don't apply this,
    # the app will crash if certain special keys are pressed.
    """
    def keyboard_callback(self, window, key, scancode, action, mods):
        try:
            _key = key
            if _key < 0x100:
                # Translate characters to the correct keyboard layout.
                key_name = glfw.get_key_name(key, 0)
                if key_name is not None:
                    _key = ord(key_name.upper())
            super(PatchedGlfwRenderer, self).keyboard_callback(window, _key, scancode, action, mods)
        except:
            super(PatchedGlfwRenderer, self).keyboard_callback(window, key, scancode, action, mods)

Ancestors

  • imgui.integrations.glfw.GlfwRenderer
  • imgui.integrations.opengl.ProgrammablePipelineRenderer
  • imgui.integrations.opengl.BaseOpenGLRenderer

Methods

def keyboard_callback(self, window, key, scancode, action, mods)
Expand source code
def keyboard_callback(self, window, key, scancode, action, mods):
    try:
        _key = key
        if _key < 0x100:
            # Translate characters to the correct keyboard layout.
            key_name = glfw.get_key_name(key, 0)
            if key_name is not None:
                _key = ord(key_name.upper())
        super(PatchedGlfwRenderer, self).keyboard_callback(window, _key, scancode, action, mods)
    except:
        super(PatchedGlfwRenderer, self).keyboard_callback(window, key, scancode, action, mods)