Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for multiple GLFW contexts #3934

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 130 additions & 78 deletions backends/imgui_impl_glfw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
// 2016-10-15: Misc: Added a void* user_data parameter to Clipboard function handlers.

#include "imgui.h"
#include "imgui_internal.h"
#include "imgui_impl_glfw.h"

// GLFW
Expand All @@ -58,25 +59,48 @@
#define GLFW_HAS_NEW_CURSORS (0)
#endif

// Data
enum GlfwClientApi
{
GlfwClientApi_Unknown,
GlfwClientApi_OpenGL,
GlfwClientApi_Vulkan
};
static GLFWwindow* g_Window = NULL; // Main window
static GlfwClientApi g_ClientApi = GlfwClientApi_Unknown;
static double g_Time = 0.0;
static bool g_MouseJustPressed[ImGuiMouseButton_COUNT] = {};
static GLFWcursor* g_MouseCursors[ImGuiMouseCursor_COUNT] = {};
static bool g_InstalledCallbacks = false;

// Chain GLFW callbacks: our callbacks will call the user's previously installed callbacks, if any.
static GLFWmousebuttonfun g_PrevUserCallbackMousebutton = NULL;
static GLFWscrollfun g_PrevUserCallbackScroll = NULL;
static GLFWkeyfun g_PrevUserCallbackKey = NULL;
static GLFWcharfun g_PrevUserCallbackChar = NULL;

// State for each GLFWWindow
struct State {
GlfwClientApi ClientApi = GlfwClientApi_Unknown;
double Time = 0.0;
bool MouseJustPressed[ImGuiMouseButton_COUNT] = {};
GLFWcursor* MouseCursors[ImGuiMouseCursor_COUNT] = {};
bool InstalledCallbacks = false;

// Chain GLFW callbacks: our callbacks will call the user's previously installed callbacks, if any.
GLFWmousebuttonfun PrevUserCallbackMousebutton = NULL;
GLFWscrollfun PrevUserCallbackScroll = NULL;
GLFWkeyfun PrevUserCallbackKey = NULL;
GLFWcharfun PrevUserCallbackChar = NULL;
};

// Map GLFWwindow pointer to State
static ImGuiStorage g_StateStorage;

// The initial window given to the first call to ImGui_ImplGlfw_Init. Fall
// back to this window if another window isn't provided in ImGui_ImplGlfw_Shutdown or ImGui_ImplGlfw_NewFrame
static GLFWwindow* g_DefaultWindow = NULL;

static State* ImGui_ImplGlfw_InitWindowState(GLFWwindow* window)
{
State* state = IM_NEW(State)();
const ImGuiID window_hash = ImHashData(&window, sizeof(void*), 0);
g_StateStorage.SetVoidPtr(window_hash, state);
return state;
}

static State* ImGui_ImplGlfw_GetWindowState(GLFWwindow* window)
{
const ImGuiID window_hash = ImHashData(&window, sizeof(void*), 0);
return (State*)g_StateStorage.GetVoidPtr(window_hash);
}

static const char* ImGui_ImplGlfw_GetClipboardText(void* user_data)
{
Expand All @@ -90,17 +114,21 @@ static void ImGui_ImplGlfw_SetClipboardText(void* user_data, const char* text)

void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow* window, int button, int action, int mods)
{
if (g_PrevUserCallbackMousebutton != NULL)
g_PrevUserCallbackMousebutton(window, button, action, mods);
State* state = ImGui_ImplGlfw_GetWindowState(window);

if (state->PrevUserCallbackMousebutton != NULL)
state->PrevUserCallbackMousebutton(window, button, action, mods);

if (action == GLFW_PRESS && button >= 0 && button < IM_ARRAYSIZE(g_MouseJustPressed))
g_MouseJustPressed[button] = true;
if (action == GLFW_PRESS && button >= 0 && button < IM_ARRAYSIZE(state->MouseJustPressed))
state->MouseJustPressed[button] = true;
}

void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yoffset)
{
if (g_PrevUserCallbackScroll != NULL)
g_PrevUserCallbackScroll(window, xoffset, yoffset);
State* state = ImGui_ImplGlfw_GetWindowState(window);

if (state->PrevUserCallbackScroll != NULL)
state->PrevUserCallbackScroll(window, xoffset, yoffset);

ImGuiIO& io = ImGui::GetIO();
io.MouseWheelH += (float)xoffset;
Expand All @@ -109,8 +137,10 @@ void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yo

void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
if (g_PrevUserCallbackKey != NULL)
g_PrevUserCallbackKey(window, key, scancode, action, mods);
State* state = ImGui_ImplGlfw_GetWindowState(window);

if (state->PrevUserCallbackKey != NULL)
state->PrevUserCallbackKey(window, key, scancode, action, mods);

ImGuiIO& io = ImGui::GetIO();
if (action == GLFW_PRESS)
Expand All @@ -131,17 +161,23 @@ void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int key, int scancode, int a

void ImGui_ImplGlfw_CharCallback(GLFWwindow* window, unsigned int c)
{
if (g_PrevUserCallbackChar != NULL)
g_PrevUserCallbackChar(window, c);
State* state = ImGui_ImplGlfw_GetWindowState(window);

if (state->PrevUserCallbackChar != NULL)
state->PrevUserCallbackChar(window, c);

ImGuiIO& io = ImGui::GetIO();
io.AddInputCharacter(c);
}

static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, GlfwClientApi client_api)
{
g_Window = window;
g_Time = 0.0;
if (g_DefaultWindow == NULL)
g_DefaultWindow = window;

State* state = ImGui_ImplGlfw_InitWindowState(window);

state->Time = 0.0;

// Setup backend capabilities flags
ImGuiIO& io = ImGui::GetIO();
Expand Down Expand Up @@ -175,49 +211,49 @@ static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, Glfw

io.SetClipboardTextFn = ImGui_ImplGlfw_SetClipboardText;
io.GetClipboardTextFn = ImGui_ImplGlfw_GetClipboardText;
io.ClipboardUserData = g_Window;
io.ClipboardUserData = window;
#if defined(_WIN32)
io.ImeWindowHandle = (void*)glfwGetWin32Window(g_Window);
io.ImeWindowHandle = (void*)glfwGetWin32Window(window);
#endif

// Create mouse cursors
// (By design, on X11 cursors are user configurable and some cursors may be missing. When a cursor doesn't exist,
// GLFW will emit an error which will often be printed by the app, so we temporarily disable error reporting.
// Missing cursors will return NULL and our _UpdateMouseCursor() function will use the Arrow cursor instead.)
GLFWerrorfun prev_error_callback = glfwSetErrorCallback(NULL);
g_MouseCursors[ImGuiMouseCursor_Arrow] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
g_MouseCursors[ImGuiMouseCursor_TextInput] = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR);
g_MouseCursors[ImGuiMouseCursor_ResizeNS] = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR);
g_MouseCursors[ImGuiMouseCursor_ResizeEW] = glfwCreateStandardCursor(GLFW_HRESIZE_CURSOR);
g_MouseCursors[ImGuiMouseCursor_Hand] = glfwCreateStandardCursor(GLFW_HAND_CURSOR);
state->MouseCursors[ImGuiMouseCursor_Arrow] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
state->MouseCursors[ImGuiMouseCursor_TextInput] = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR);
state->MouseCursors[ImGuiMouseCursor_ResizeNS] = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR);
state->MouseCursors[ImGuiMouseCursor_ResizeEW] = glfwCreateStandardCursor(GLFW_HRESIZE_CURSOR);
state->MouseCursors[ImGuiMouseCursor_Hand] = glfwCreateStandardCursor(GLFW_HAND_CURSOR);
#if GLFW_HAS_NEW_CURSORS
g_MouseCursors[ImGuiMouseCursor_ResizeAll] = glfwCreateStandardCursor(GLFW_RESIZE_ALL_CURSOR);
g_MouseCursors[ImGuiMouseCursor_ResizeNESW] = glfwCreateStandardCursor(GLFW_RESIZE_NESW_CURSOR);
g_MouseCursors[ImGuiMouseCursor_ResizeNWSE] = glfwCreateStandardCursor(GLFW_RESIZE_NWSE_CURSOR);
g_MouseCursors[ImGuiMouseCursor_NotAllowed] = glfwCreateStandardCursor(GLFW_NOT_ALLOWED_CURSOR);
state->MouseCursors[ImGuiMouseCursor_ResizeAll] = glfwCreateStandardCursor(GLFW_RESIZE_ALL_CURSOR);
state->MouseCursors[ImGuiMouseCursor_ResizeNESW] = glfwCreateStandardCursor(GLFW_RESIZE_NESW_CURSOR);
state->MouseCursors[ImGuiMouseCursor_ResizeNWSE] = glfwCreateStandardCursor(GLFW_RESIZE_NWSE_CURSOR);
state->MouseCursors[ImGuiMouseCursor_NotAllowed] = glfwCreateStandardCursor(GLFW_NOT_ALLOWED_CURSOR);
#else
g_MouseCursors[ImGuiMouseCursor_ResizeAll] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
g_MouseCursors[ImGuiMouseCursor_ResizeNESW] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
g_MouseCursors[ImGuiMouseCursor_ResizeNWSE] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
g_MouseCursors[ImGuiMouseCursor_NotAllowed] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
state->MouseCursors[ImGuiMouseCursor_ResizeAll] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
state->MouseCursors[ImGuiMouseCursor_ResizeNESW] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
state->MouseCursors[ImGuiMouseCursor_ResizeNWSE] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
state->MouseCursors[ImGuiMouseCursor_NotAllowed] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
#endif
glfwSetErrorCallback(prev_error_callback);

// Chain GLFW callbacks: our callbacks will call the user's previously installed callbacks, if any.
g_PrevUserCallbackMousebutton = NULL;
g_PrevUserCallbackScroll = NULL;
g_PrevUserCallbackKey = NULL;
g_PrevUserCallbackChar = NULL;
state->PrevUserCallbackMousebutton = NULL;
state->PrevUserCallbackScroll = NULL;
state->PrevUserCallbackKey = NULL;
state->PrevUserCallbackChar = NULL;
if (install_callbacks)
{
g_InstalledCallbacks = true;
g_PrevUserCallbackMousebutton = glfwSetMouseButtonCallback(window, ImGui_ImplGlfw_MouseButtonCallback);
g_PrevUserCallbackScroll = glfwSetScrollCallback(window, ImGui_ImplGlfw_ScrollCallback);
g_PrevUserCallbackKey = glfwSetKeyCallback(window, ImGui_ImplGlfw_KeyCallback);
g_PrevUserCallbackChar = glfwSetCharCallback(window, ImGui_ImplGlfw_CharCallback);
state->InstalledCallbacks = true;
state->PrevUserCallbackMousebutton = glfwSetMouseButtonCallback(window, ImGui_ImplGlfw_MouseButtonCallback);
state->PrevUserCallbackScroll = glfwSetScrollCallback(window, ImGui_ImplGlfw_ScrollCallback);
state->PrevUserCallbackKey = glfwSetKeyCallback(window, ImGui_ImplGlfw_KeyCallback);
state->PrevUserCallbackChar = glfwSetCharCallback(window, ImGui_ImplGlfw_CharCallback);
}

g_ClientApi = client_api;
state->ClientApi = client_api;
return true;
}

Expand All @@ -236,34 +272,43 @@ bool ImGui_ImplGlfw_InitForOther(GLFWwindow* window, bool install_callbacks)
return ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_Unknown);
}

void ImGui_ImplGlfw_Shutdown()
void ImGui_ImplGlfw_Shutdown(GLFWwindow* window)
{
if (g_InstalledCallbacks)
if (window == NULL)
window = g_DefaultWindow;

State* state = ImGui_ImplGlfw_GetWindowState(window);

if (state->InstalledCallbacks)
{
glfwSetMouseButtonCallback(g_Window, g_PrevUserCallbackMousebutton);
glfwSetScrollCallback(g_Window, g_PrevUserCallbackScroll);
glfwSetKeyCallback(g_Window, g_PrevUserCallbackKey);
glfwSetCharCallback(g_Window, g_PrevUserCallbackChar);
g_InstalledCallbacks = false;
glfwSetMouseButtonCallback(window, state->PrevUserCallbackMousebutton);
glfwSetScrollCallback(window, state->PrevUserCallbackScroll);
glfwSetKeyCallback(window, state->PrevUserCallbackKey);
glfwSetCharCallback(window, state->PrevUserCallbackChar);
state->InstalledCallbacks = false;
}

for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++)
{
glfwDestroyCursor(g_MouseCursors[cursor_n]);
g_MouseCursors[cursor_n] = NULL;
glfwDestroyCursor(state->MouseCursors[cursor_n]);
state->MouseCursors[cursor_n] = NULL;
}
g_ClientApi = GlfwClientApi_Unknown;
state->ClientApi = GlfwClientApi_Unknown;

IM_DELETE(state);
}

static void ImGui_ImplGlfw_UpdateMousePosAndButtons()
static void ImGui_ImplGlfw_UpdateMousePosAndButtons(GLFWwindow* window)
{
State* state = ImGui_ImplGlfw_GetWindowState(window);

// Update buttons
ImGuiIO& io = ImGui::GetIO();
for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++)
{
// If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame.
io.MouseDown[i] = g_MouseJustPressed[i] || glfwGetMouseButton(g_Window, i) != 0;
g_MouseJustPressed[i] = false;
io.MouseDown[i] = state->MouseJustPressed[i] || glfwGetMouseButton(window, i) != 0;
state->MouseJustPressed[i] = false;
}

// Update mouse position
Expand All @@ -272,41 +317,43 @@ static void ImGui_ImplGlfw_UpdateMousePosAndButtons()
#ifdef __EMSCRIPTEN__
const bool focused = true; // Emscripten
#else
const bool focused = glfwGetWindowAttrib(g_Window, GLFW_FOCUSED) != 0;
const bool focused = glfwGetWindowAttrib(window, GLFW_FOCUSED) != 0;
#endif
if (focused)
{
if (io.WantSetMousePos)
{
glfwSetCursorPos(g_Window, (double)mouse_pos_backup.x, (double)mouse_pos_backup.y);
glfwSetCursorPos(window, (double)mouse_pos_backup.x, (double)mouse_pos_backup.y);
}
else
{
double mouse_x, mouse_y;
glfwGetCursorPos(g_Window, &mouse_x, &mouse_y);
glfwGetCursorPos(window, &mouse_x, &mouse_y);
io.MousePos = ImVec2((float)mouse_x, (float)mouse_y);
}
}
}

static void ImGui_ImplGlfw_UpdateMouseCursor()
static void ImGui_ImplGlfw_UpdateMouseCursor(GLFWwindow* window)
{
State* state = ImGui_ImplGlfw_GetWindowState(window);

ImGuiIO& io = ImGui::GetIO();
if ((io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) || glfwGetInputMode(g_Window, GLFW_CURSOR) == GLFW_CURSOR_DISABLED)
if ((io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) || glfwGetInputMode(window, GLFW_CURSOR) == GLFW_CURSOR_DISABLED)
return;

ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
if (imgui_cursor == ImGuiMouseCursor_None || io.MouseDrawCursor)
{
// Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
glfwSetInputMode(g_Window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
}
else
{
// Show OS mouse cursor
// FIXME-PLATFORM: Unfocused windows seems to fail changing the mouse cursor with GLFW 3.2, but 3.3 works here.
glfwSetCursor(g_Window, g_MouseCursors[imgui_cursor] ? g_MouseCursors[imgui_cursor] : g_MouseCursors[ImGuiMouseCursor_Arrow]);
glfwSetInputMode(g_Window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
glfwSetCursor(window, state->MouseCursors[imgui_cursor] ? state->MouseCursors[imgui_cursor] : state->MouseCursors[ImGuiMouseCursor_Arrow]);
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
}
}

Expand Down Expand Up @@ -347,27 +394,32 @@ static void ImGui_ImplGlfw_UpdateGamepads()
io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
}

void ImGui_ImplGlfw_NewFrame()
void ImGui_ImplGlfw_NewFrame(GLFWwindow* window)
{
ImGuiIO& io = ImGui::GetIO();
IM_ASSERT(io.Fonts->IsBuilt() && "Font atlas not built! It is generally built by the renderer backend. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame().");

if (window == NULL)
window = g_DefaultWindow;

State* state = ImGui_ImplGlfw_GetWindowState(window);

// Setup display size (every frame to accommodate for window resizing)
int w, h;
int display_w, display_h;
glfwGetWindowSize(g_Window, &w, &h);
glfwGetFramebufferSize(g_Window, &display_w, &display_h);
glfwGetWindowSize(window, &w, &h);
glfwGetFramebufferSize(window, &display_w, &display_h);
io.DisplaySize = ImVec2((float)w, (float)h);
if (w > 0 && h > 0)
io.DisplayFramebufferScale = ImVec2((float)display_w / w, (float)display_h / h);

// Setup time step
double current_time = glfwGetTime();
io.DeltaTime = g_Time > 0.0 ? (float)(current_time - g_Time) : (float)(1.0f / 60.0f);
g_Time = current_time;
io.DeltaTime = state->Time > 0.0 ? (float)(current_time - state->Time) : (float)(1.0f / 60.0f);
state->Time = current_time;

ImGui_ImplGlfw_UpdateMousePosAndButtons();
ImGui_ImplGlfw_UpdateMouseCursor();
ImGui_ImplGlfw_UpdateMousePosAndButtons(window);
ImGui_ImplGlfw_UpdateMouseCursor(window);

// Update game controllers (if enabled and available)
ImGui_ImplGlfw_UpdateGamepads();
Expand Down
4 changes: 2 additions & 2 deletions backends/imgui_impl_glfw.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ struct GLFWwindow;
IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForOpenGL(GLFWwindow* window, bool install_callbacks);
IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForVulkan(GLFWwindow* window, bool install_callbacks);
IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForOther(GLFWwindow* window, bool install_callbacks);
IMGUI_IMPL_API void ImGui_ImplGlfw_Shutdown();
IMGUI_IMPL_API void ImGui_ImplGlfw_NewFrame();
IMGUI_IMPL_API void ImGui_ImplGlfw_Shutdown(GLFWwindow* window = NULL);
IMGUI_IMPL_API void ImGui_ImplGlfw_NewFrame(GLFWwindow* window = NULL);

// GLFW callbacks
// - When calling Init with 'install_callbacks=true': GLFW callbacks will be installed for you. They will call user's previously installed callbacks, if any.
Expand Down