Magnum::ImGuiIntegration::Context class

Dear ImGui context.

Handles initialization and destruction of a Dear ImGui context and implements a Magnum-based rendering backend.

Usage

Creating the Context instance will create the Dear ImGui context and make it current. From that point on you can use ImGui calls.

ImGuiIntegration::Context imgui{{640, 480}};

// ...

Rendering

Use newFrame() to initialize a ImGui frame and finally draw it with drawFrame() to the currently bound framebuffer. Dear ImGui requires scissor test to be enabled and depth test to be disabled. Blending should be enabled and set up as below. The following snippet sets up all required renderer state and then resets it back to default values. Adapt the state changes based on what else you are rendering. Right before drawFrame() you can call updateApplicationCursor() if your application implementation supports setting cursors.

GL::Renderer::setBlendEquation(GL::Renderer::BlendEquation::Add,
    GL::Renderer::BlendEquation::Add);
GL::Renderer::setBlendFunction(GL::Renderer::BlendFunction::SourceAlpha,
    GL::Renderer::BlendFunction::OneMinusSourceAlpha);

_imgui.newFrame();

// ImGui widget calls here ...

_imgui.updateApplicationCursor(*this);

GL::Renderer::enable(GL::Renderer::Feature::Blending);
GL::Renderer::enable(GL::Renderer::Feature::ScissorTest);
GL::Renderer::disable(GL::Renderer::Feature::DepthTest);
GL::Renderer::disable(GL::Renderer::Feature::FaceCulling);

_imgui.drawFrame();

GL::Renderer::enable(GL::Renderer::Feature::DepthTest);
GL::Renderer::enable(GL::Renderer::Feature::FaceCulling);
GL::Renderer::disable(GL::Renderer::Feature::ScissorTest);
GL::Renderer::disable(GL::Renderer::Feature::Blending);

// ...

redraw();

Event handling

The templated handleMousePressEvent(), handleMouseReleaseEvent() etc. functions are meant to be used inside event handlers of application classes such as Platform::Sdl2Application, directly passing the event parameter to them. The returned value is then true if ImGui used the event (and thus it shouldn't be propagated further) and false otherwise.

#include <Magnum/ImGuiIntegration/Context.hpp>

// ...

void MyApp::mousePressEvent(MouseEvent& event) {
    if(_imgui.handleMousePressEvent(event)) return;

    // event not handled by ImGui, handle it for the app itself
}

void MyApp::mouseReleaseEvent(MouseEvent& event) {
    if(_imgui.handleMouseReleaseEvent(event)) return;

    // ...
}

// ...

Text input

UTF-8 text input is handled via handleTextInputEvent() but the application implementations only call textInputEvent() when text input is enabled. This is done because some platforms require explicit action in order to start a text input (for example, to open an on-screen keyboard on touch devices, or IME for complex alphabets). ImGui exposes its desire to capture text input during a call to newFrame(). Based on that, you can toggle the text input in the application, for example using startTextInput() / stopTextInput() in Platform::Sdl2Application or Platform::GlfwApplication:

_imgui.newFrame();
if(ImGui::GetIO().WantTextInput && !isTextInputActive())
    startTextInput();
else if(!ImGui::GetIO().WantTextInput && isTextInputActive())
    stopTextInput();

The above snippet also means that ImGui's InputQueueCharacters will be empty unless an text input box is focused — so if you want to handle text input through ImGui manually, you need to explicitly call startTextInput() / stopTextInput() when desired.

Loading custom fonts

The Context class does additional adjustments to ImGui font setup in order to make their scaling DPI-aware. If you load custom fonts, it's recommended to do that before the Context class is created, in which case it picks up the custom font as default. Create the ImGui context first, add the font and then construct the integration using the Context(ImGuiContext&, const Vector2i&) constructor, passing the already created ImGui context to it:

ImGui::CreateContext();

ImGui::GetIO().Fonts->AddFontFromFileTTF("SourceSansPro-Regular.ttf", 16.0f);

ImGuiIntegration::Context imgui(*ImGui::GetCurrentContext(), {640, 480});

// ...

It's possible to load custom fonts after the Context instance been constructed as well, but you first need to clear the default font added during Context construction and finally call relayout() to make it pick up the updated glyph cache. Alternatively, if you don't call Clear(), you need to explicitly call PushFont() to switch to a non-default one. Compared to loading fonts before the Context is created, this is the less efficient option, as the glyph cache is unnecessarily built and discarded one more time.

ImGuiIntegration::Context imgui({640, 480});

// ...

ImGui::GetIO().Fonts->Clear();
ImGui::GetIO().Fonts->AddFontFromFileTTF("SourceSansPro-Regular.ttf", 16.0f);

imgui.relayout(windowSize());

See the DPI awareness section below for more information about configuring the fonts for HiDPI screens.

DPI awareness

There are three separate concepts for DPI-aware UI rendering:

  • UI size — size of the user interface to which all widgets are positioned
  • Window size — size of the window to which all input events are related
  • Framebuffer size — size of the framebuffer the UI is being rendered to

Depending on the platform and use case, each of these three values can be different. For example, a game menu screen can have the UI size the same regardless of window size. Or on Retina macOS you can have different window and framebuffer size and the UI size might be related to window size but independent on the framebuffer size.

When using for example Platform::Sdl2Application or other *Application implementations, you usually have three values at your disposal — windowSize(), framebufferSize() and dpiScaling(). ImGui interfaces are usually positioned with pixel units, getting more room on bigger windows. A non-DPI-aware setup would be simply this:

ImGuiIntegration::Context imgui{windowSize()};

If you want the UI to keep a reasonable physical size and stay crisp with different pixel densities, pass a ratio of window size and DPI scaling to the UI size:

ImGuiIntegration::Context imgui{
    Vector2{windowSize()}/dpiScaling(), windowSize(), framebufferSize()};

Finally, by clamping the first size parameter you can achieve various other results like limiting it to a minimal / maximal area or have it fully scaled with window size. When window size, framebuffer size or DPI scaling changes (usually as a response to viewportEvent()), call relayout() with the new values. If the pixel density is changed, this will result in the font caches being rebuilt.

HiDPI fonts

There are further important steps for DPI awareness if you are supplying custom fonts. Use the Context(ImGuiContext&, const Vector2&, const Vector2i&, const Vector2i&) constructor and pre-scale their size by the ratio of size and framebufferSize. If you don't do that, the fonts will appear tiny on HiDPI screens. Example:

ImGui::CreateContext();

const Vector2 size = Vector2{windowSize()}/dpiScaling();

ImGui::GetIO().Fonts->AddFontFromFileTTF("SourceSansPro-Regular.ttf",
    16.0f*framebufferSize().x()/size.x());

ImGuiIntegration::Context imgui(*ImGui::GetCurrentContext(),
    size, windowSize(), framebufferSize());

// ...

If you supplied custom fonts and pixel density changed, in order to regenerate them you have to clear the font atlas and re-add all fonts again with a different scaling before calling relayout(), for example:

void MyApp::viewportEvent(ViewportEvent& event) {
    // ...

    const Vector2 size = Vector2{event.windowSize()}/event.dpiScaling();

    /* Reload fonts if pixel density changed */
    const Float supersamplingRatio = Float(event.framebufferSize().x())/size.x();
    if(supersamplingRatio != _supersamplingRatio) {
        _supersamplingRatio = supersamplingRatio;

        ImGui::GetIO().Fonts->Clear(); // important
        ImGui::GetIO().Fonts->AddFontFromFileTTF("SourceSansPro-Regular.ttf",
            16.0f*supersamplingRatio);
    }

    _imgui.relayout(size, event.windowSize(), event.framebufferSize());
}

If you don't do that, the fonts stay at the original scale, not matching the new UI scaling anymore. If you didn't supply any custom font, the function will reconfigure the builtin font automatically.

Large meshes

Complex user interfaces or widgets like ImPlot may end up creating large meshes with more than 65k vertices. Because ImGui defaults to 16-bit index buffers this can lead to asserts or visual errors.

If the underlying GL context supports setting the base vertex for indexed meshes the rendering backend sets the ImGuiBackendFlags_RendererHasVtxOffset flag. This lets ImGui know the backend can handle per-draw vertex offsets, removing the 65k limitation altogether. Support for that requires one of the following:

If you can't guarantee that the required GL versions or extensions will be available at runtime (especially relevant on WebGL), the next best option is to change ImGui's index type to 32-bit by adding the following line to the ImGui user config:

#define ImDrawIdx unsigned int

This doubles the size of the index buffer, resulting in potentially reduced draw performance, but is guaranteed to work on all GL versions.

Multiple contexts

Each instance of Context creates a new ImGui context. You can also pass an existing context to the Context(ImGuiContext&, const Vector2i&) constructor, which will then take ownership (and thus delete it on destruction). Switching between various ImGui contexts wrapped in Context instances is done automatically when calling any of the relayout(), newFrame(), drawFrame() APIs or the event handling functions. You can also query the instance-specific context with context() and call ImGui::SetCurrentContext() manually on that.

It's also possible to create a context-less instance using the Context(NoCreateT) constructor and release context ownership using release(). Such instances, together with moved-out instances are empty and calling any API that interacts with ImGui is not allowed on these.

Constructors, destructors, conversion operators

Context(const Vector2& size, const Vector2i& windowSize, const Vector2i& framebufferSize) explicit
Constructor.
Context(const Vector2i& size) explicit
Construct without DPI awareness.
Context(ImGuiContext& context, const Vector2& size, const Vector2i& windowSize, const Vector2i& framebufferSize) explicit
Construct from an existing context.
Context(ImGuiContext& context, const Vector2i& size) explicit
Construct from an existing context without DPI awareness.
Context(NoCreateT) explicit noexcept
Construct without creating the underlying ImGui context.
Context(const Context&) deleted
Copying is not allowed.
Context(Context&& other) noexcept
Move constructor.
~Context()
Destructor.

Public functions

auto operator=(const Context&) -> Context& deleted
Copying is not allowed.
auto operator=(Context&& other) -> Context& noexcept
Move assignment.
auto context() -> ImGuiContext*
Underlying ImGui context.
auto release() -> ImGuiContext*
Release the underlying ImGui context.
auto atlasTexture() -> GL::Texture2D& new in 2020.06
Font texture used in ImFontAtlas
void relayout(const Vector2& size, const Vector2i& windowSize, const Vector2i& framebufferSize)
Relayout the context.
void relayout(const Vector2i& size)
Relayout the context.
void newFrame()
Start a new frame.
void drawFrame()
Draw a frame.
template<class MouseEvent>
auto handleMousePressEvent(MouseEvent& event) -> bool
Handle mouse press event.
template<class MouseEvent>
auto handleMouseReleaseEvent(MouseEvent& event) -> bool
Handle mouse release event.
template<class MouseScrollEvent>
auto handleMouseScrollEvent(MouseScrollEvent& event) -> bool
Handle mouse scroll event.
template<class MouseMoveEvent>
auto handleMouseMoveEvent(MouseMoveEvent& event) -> bool
Handle mouse move event.
template<class KeyEvent>
auto handleKeyPressEvent(KeyEvent& event) -> bool
Handle key press event.
template<class KeyEvent>
auto handleKeyReleaseEvent(KeyEvent& event) -> bool
Handle key release event.
template<class TextInputEvent>
auto handleTextInputEvent(TextInputEvent& event) -> bool
Handle text input event.
template<class Application>
void updateApplicationCursor(Application& application) new in 2020.06
Update application mouse cursor.

Function documentation

Magnum::ImGuiIntegration::Context::Context(const Vector2& size, const Vector2i& windowSize, const Vector2i& framebufferSize) explicit

Constructor.

Parameters
size Size of the user interface to which all widgets are positioned
windowSize Size of the window to which all input events are related
framebufferSize Size of the window framebuffer. On some platforms with HiDPI screens may be different from window size.

This function creates the ImGui context using ImGui::CreateContext() and then queries the font glyph cache from ImGui, uploading it to the GPU. If you need to do some extra work on the context and before the font texture gets uploaded, use Context(ImGuiContext&, const Vector2&, const Vector2i&, const Vector2i&) instead.

The sizes are allowed to be zero in any dimension, but note that specifying a concrete value later in relayout() may trigger an unnecessary rebuild of the font glyph cache due to different calculated pixel density. See DPI awareness for more information about the different size arguments. If you don't need DPI awareness, you can use the simpler Context(const Vector2i&) constructor instead.

Magnum::ImGuiIntegration::Context::Context(const Vector2i& size) explicit

Construct without DPI awareness.

Equivalent to calling Context(const Vector2&, const Vector2i&, const Vector2i&) with size passed to all three parameters.

Magnum::ImGuiIntegration::Context::Context(ImGuiContext& context, const Vector2& size, const Vector2i& windowSize, const Vector2i& framebufferSize) explicit

Construct from an existing context.

Parameters
context Existing ImGui context
size Size of the user interface to which all widgets are positioned
windowSize Size of the window to which all inputs events are related
framebufferSize Size of the window framebuffer. On some platforms with HiDPI screens may be different from window size.

Expects that no instance is created yet; takes ownership of the passed context, deleting it on destruction. In comparison to Context(const Vector2&, const Vector2i&, const Vector2i&) this constructor is useful if you need to do some work before the font glyph cache gets uploaded to the GPU, for example adding custom fonts.

See DPI awareness for more information about the different size arguments. If you don't need DPI awareness, you can use the simpler Context(ImGuiContext&, const Vector2i&) constructor instead. Note that, in order to have the custom fonts crisp also on HiDPI screens, you have to pre-scale their size by the ratio of size and framebufferSize. See HiDPI fonts for more information.

Magnum::ImGuiIntegration::Context::Context(ImGuiContext& context, const Vector2i& size) explicit

Construct from an existing context without DPI awareness.

Equivalent to calling Context(ImGuiContext&, const Vector2&, const Vector2i&, const Vector2i&) with size passed to the last three parameters. In comparison to Context(const Vector2i&) this constructor is useful if you need to do some work before the font glyph cache gets uploaded to the GPU, for example adding custom fonts.

Magnum::ImGuiIntegration::Context::Context(NoCreateT) explicit noexcept

Construct without creating the underlying ImGui context.

This constructor also doesn't create any internal OpenGL objects, meaning it can be used without an active OpenGL context. Calling any APIs that interact with ImGui on such instance is not allowed. Move a non-empty instance over to make it useful.

Magnum::ImGuiIntegration::Context::~Context()

Destructor.

If context() is not nullptr, makes it current using ImGui::SetCurrentContext() and then calls ImGui::DestroyContext().

ImGuiContext* Magnum::ImGuiIntegration::Context::context()

Underlying ImGui context.

Returns nullptr if this is a NoCreated, moved-out or release()d instance.

ImGuiContext* Magnum::ImGuiIntegration::Context::release()

Release the underlying ImGui context.

Returns the underlying ImGui context and sets the internal context pointer to nullptr, making the instance equivalent to a moved-out state. Calling APIs that interact with ImGui is not allowed on the instance anymore.

void Magnum::ImGuiIntegration::Context::relayout(const Vector2& size, const Vector2i& windowSize, const Vector2i& framebufferSize)

Relayout the context.

Parameters
size Size of the user interface to which all widgets are positioned
windowSize Size of the window to which all input events are related
framebufferSize Size of the window framebuffer. On some platforms with HiDPI screens may be different from window size.

Calls ImGui::SetCurrentContext() on context() and adapts the internal state for a new window size or pixel density. In case the pixel density gets changed, font glyph caches are rebuilt to match the new pixel density.

The sizes are allowed to be zero in any dimension, but note that it may trigger an unwanted rebuild of the font glyph cache due to different calculated pixel density. See DPI awareness for more information about the different size arguments. If you don't need DPI awareness, you can use the simpler relayout(const Vector2i&) function instead.

void Magnum::ImGuiIntegration::Context::relayout(const Vector2i& size)

Relayout the context.

Equivalent to calling relayout(const Vector2&, const Vector2i&, const Vector2i&) with size passed to all three parameters.

void Magnum::ImGuiIntegration::Context::newFrame()

Start a new frame.

Calls ImGui::SetCurrentContext() on context() and initializes a new ImGui frame using ImGui::NewFrame(). This function also decides if a text input needs to be enabled, see Text input for more information.

void Magnum::ImGuiIntegration::Context::drawFrame()

Draw a frame.

Calls ImGui::SetCurrentContext() on context(), ImGui::Render() and then draws the frame created by ImGui calls since last call to newFrame() to currently bound framebuffer.

See Rendering for more information on which rendering states to set before and after calling this method.

template<class MouseEvent>
bool Magnum::ImGuiIntegration::Context::handleMousePressEvent(MouseEvent& event)

Handle mouse press event.

Calls ImGui::SetCurrentContext() on context() first and then propagates the event to ImGui. Returns true if ImGui wants to capture the mouse (so the event shouldn't be further propagated to the rest of the application), false otherwise.

template<class MouseEvent>
bool Magnum::ImGuiIntegration::Context::handleMouseReleaseEvent(MouseEvent& event)

Handle mouse release event.

Calls ImGui::SetCurrentContext() on context() first and then propagates the event to ImGui. Returns true if ImGui wants to capture the mouse (so the event shouldn't be further propagated to the rest of the application), false otherwise.

template<class MouseScrollEvent>
bool Magnum::ImGuiIntegration::Context::handleMouseScrollEvent(MouseScrollEvent& event)

Handle mouse scroll event.

Calls ImGui::SetCurrentContext() on context() first and then propagates the event to ImGui. Returns true if ImGui wants to capture the mouse (so the event shouldn't be further propagated to the rest of the application), false otherwise.

template<class MouseMoveEvent>
bool Magnum::ImGuiIntegration::Context::handleMouseMoveEvent(MouseMoveEvent& event)

Handle mouse move event.

Calls ImGui::SetCurrentContext() on context() first and then propagates the event to ImGui. Returns true if ImGui wants to capture the mouse (so the event shouldn't be further propagated to the rest of the application), false otherwise.

template<class KeyEvent>
bool Magnum::ImGuiIntegration::Context::handleKeyPressEvent(KeyEvent& event)

Handle key press event.

Calls ImGui::SetCurrentContext() on context() first and then propagates the event to ImGui. Returns true if ImGui wants to capture the keyboard (so the event shouldn't be further propagated to the rest of the application), false otherwise.

template<class KeyEvent>
bool Magnum::ImGuiIntegration::Context::handleKeyReleaseEvent(KeyEvent& event)

Handle key release event.

Calls ImGui::SetCurrentContext() on context() first and then propagates the event to ImGui. Returns true if ImGui wants to capture the keyboard (so the event shouldn't be further propagated to the rest of the application), false otherwise.

template<class TextInputEvent>
bool Magnum::ImGuiIntegration::Context::handleTextInputEvent(TextInputEvent& event)

Handle text input event.

Calls ImGui::SetCurrentContext() on context() first and then propagates the event to ImGui. Returns true if ImGui wants to capture the keyboard (so the event shouldn't be further propagated to the rest of the application), false otherwise.

template<class Application>
void Magnum::ImGuiIntegration::Context::updateApplicationCursor(Application& application) new in 2020.06

Update application mouse cursor.

Calls ImGui::SetCurrentContext() on context() first and then queries ImGui::GetMouseCursor(), propagating that to the application via setCursor(). If the application doesn't implement a corresponding cursor, falls back to Cursor::Arrow.