Backends: SDL3: Added multi-viewports support. (#6146)

This commit is contained in:
ocornut 2023-02-07 16:30:58 +01:00
parent e4233c6fb6
commit a526ff8c30
3 changed files with 399 additions and 13 deletions

View File

@ -8,6 +8,9 @@
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set] // [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. // [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. // [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
// [x] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable' -> the OS animation effect when window gets created/destroyed is problematic. SDL2 backend doesn't have issue.
// Missing features:
// [ ] Platform: Multi-viewport + Minimized windows seems to break mouse wheel events (at least under Windows).
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
@ -16,6 +19,7 @@
// CHANGELOG // CHANGELOG
// (minor and older changes stripped away, please see git history for details) // (minor and older changes stripped away, please see git history for details)
// 2023-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2023-02-07: Forked "imgui_impl_sdl2" into "imgui_impl_sdl3". Removed version checks for old feature. Refer to imgui_impl_sdl2.cpp for older changelog. // 2023-02-07: Forked "imgui_impl_sdl2" into "imgui_impl_sdl3". Removed version checks for old feature. Refer to imgui_impl_sdl2.cpp for older changelog.
#include "imgui.h" #include "imgui.h"
@ -47,6 +51,8 @@ struct ImGui_ImplSDL3_Data
int PendingMouseLeaveFrame; int PendingMouseLeaveFrame;
char* ClipboardTextData; char* ClipboardTextData;
bool MouseCanUseGlobalState; bool MouseCanUseGlobalState;
bool MouseCanReportHoveredViewport; // This is hard to use/unreliable on SDL so we'll set ImGuiBackendFlags_HasMouseHoveredViewport dynamically based on state.
bool UseVulkan;
ImGui_ImplSDL3_Data() { memset((void*)this, 0, sizeof(*this)); } ImGui_ImplSDL3_Data() { memset((void*)this, 0, sizeof(*this)); }
}; };
@ -60,6 +66,11 @@ static ImGui_ImplSDL3_Data* ImGui_ImplSDL3_GetBackendData()
return ImGui::GetCurrentContext() ? (ImGui_ImplSDL3_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr; return ImGui::GetCurrentContext() ? (ImGui_ImplSDL3_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr;
} }
// Forward Declarations
static void ImGui_ImplSDL3_UpdateMonitors();
static void ImGui_ImplSDL3_InitPlatformInterface(SDL_Window* window, void* sdl_gl_context);
static void ImGui_ImplSDL3_ShutdownPlatformInterface();
// Functions // Functions
static const char* ImGui_ImplSDL3_GetClipboardText(void*) static const char* ImGui_ImplSDL3_GetClipboardText(void*)
{ {
@ -212,6 +223,13 @@ bool ImGui_ImplSDL3_ProcessEvent(const SDL_Event* event)
case SDL_EVENT_MOUSE_MOTION: case SDL_EVENT_MOUSE_MOTION:
{ {
ImVec2 mouse_pos((float)event->motion.x, (float)event->motion.y); ImVec2 mouse_pos((float)event->motion.x, (float)event->motion.y);
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
{
int window_x, window_y;
SDL_GetWindowPosition(SDL_GetWindowFromID(event->motion.windowID), &window_x, &window_y);
mouse_pos.x += window_x;
mouse_pos.y += window_y;
}
io.AddMousePosEvent(mouse_pos.x, mouse_pos.y); io.AddMousePosEvent(mouse_pos.x, mouse_pos.y);
return true; return true;
} }
@ -276,11 +294,25 @@ bool ImGui_ImplSDL3_ProcessEvent(const SDL_Event* event)
case SDL_EVENT_WINDOW_FOCUS_LOST: case SDL_EVENT_WINDOW_FOCUS_LOST:
io.AddFocusEvent(false); io.AddFocusEvent(false);
return true; return true;
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
case SDL_EVENT_WINDOW_MOVED:
case SDL_EVENT_WINDOW_RESIZED:
if (ImGuiViewport* viewport = ImGui::FindViewportByPlatformHandle((void*)SDL_GetWindowFromID(event->window.windowID)))
{
if (event->type == SDL_EVENT_WINDOW_CLOSE_REQUESTED)
viewport->PlatformRequestClose = true;
if (event->type == SDL_EVENT_WINDOW_MOVED)
viewport->PlatformRequestMove = true;
if (event->type == SDL_EVENT_WINDOW_RESIZED)
viewport->PlatformRequestResize = true;
return true;
}
return true;
} }
return false; return false;
} }
static bool ImGui_ImplSDL3_Init(SDL_Window* window, SDL_Renderer* renderer) static bool ImGui_ImplSDL3_Init(SDL_Window* window, SDL_Renderer* renderer, void* sdl_gl_context)
{ {
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!"); IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!");
@ -302,10 +334,20 @@ static bool ImGui_ImplSDL3_Init(SDL_Window* window, SDL_Renderer* renderer)
io.BackendPlatformName = "imgui_impl_sdl3"; io.BackendPlatformName = "imgui_impl_sdl3";
io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional) io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional)
io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used) io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used)
if (mouse_can_use_global_state)
io.BackendFlags |= ImGuiBackendFlags_PlatformHasViewports; // We can create multi-viewports on the Platform side (optional)
bd->Window = window; bd->Window = window;
bd->Renderer = renderer; bd->Renderer = renderer;
// SDL on Linux/OSX doesn't report events for unfocused windows (see https://github.com/ocornut/imgui/issues/4960)
// We will use 'MouseCanReportHoveredViewport' to set 'ImGuiBackendFlags_HasMouseHoveredViewport' dynamically each frame.
bd->MouseCanUseGlobalState = mouse_can_use_global_state; bd->MouseCanUseGlobalState = mouse_can_use_global_state;
#ifndef __APPLE__
bd->MouseCanReportHoveredViewport = bd->MouseCanUseGlobalState;
#else
bd->MouseCanReportHoveredViewport = false;
#endif
io.SetClipboardTextFn = ImGui_ImplSDL3_SetClipboardText; io.SetClipboardTextFn = ImGui_ImplSDL3_SetClipboardText;
io.GetClipboardTextFn = ImGui_ImplSDL3_GetClipboardText; io.GetClipboardTextFn = ImGui_ImplSDL3_GetClipboardText;
@ -325,6 +367,7 @@ static bool ImGui_ImplSDL3_Init(SDL_Window* window, SDL_Renderer* renderer)
// Set platform dependent data in viewport // Set platform dependent data in viewport
// Our mouse update function expect PlatformHandle to be filled for the main viewport // Our mouse update function expect PlatformHandle to be filled for the main viewport
ImGuiViewport* main_viewport = ImGui::GetMainViewport(); ImGuiViewport* main_viewport = ImGui::GetMainViewport();
main_viewport->PlatformHandle = (void*)window;
main_viewport->PlatformHandleRaw = nullptr; main_viewport->PlatformHandleRaw = nullptr;
SDL_SysWMinfo info; SDL_SysWMinfo info;
if (SDL_GetWindowWMInfo(window, &info, SDL_SYSWM_CURRENT_VERSION)) if (SDL_GetWindowWMInfo(window, &info, SDL_SYSWM_CURRENT_VERSION))
@ -350,18 +393,29 @@ static bool ImGui_ImplSDL3_Init(SDL_Window* window, SDL_Renderer* renderer)
SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0"); SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0");
#endif #endif
// Update monitors
ImGui_ImplSDL3_UpdateMonitors();
// We need SDL_CaptureMouse(), SDL_GetGlobalMouseState() from SDL 2.0.4+ to support multiple viewports.
// We left the call to ImGui_ImplSDL3_InitPlatformInterface() outside of #ifdef to avoid unused-function warnings.
if ((io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) && (io.BackendFlags & ImGuiBackendFlags_PlatformHasViewports))
ImGui_ImplSDL3_InitPlatformInterface(window, sdl_gl_context);
return true; return true;
} }
bool ImGui_ImplSDL3_InitForOpenGL(SDL_Window* window, void* sdl_gl_context) bool ImGui_ImplSDL3_InitForOpenGL(SDL_Window* window, void* sdl_gl_context)
{ {
IM_UNUSED(sdl_gl_context); // Viewport branch will need this. return ImGui_ImplSDL3_Init(window, nullptr, sdl_gl_context);
return ImGui_ImplSDL3_Init(window, nullptr);
} }
bool ImGui_ImplSDL3_InitForVulkan(SDL_Window* window) bool ImGui_ImplSDL3_InitForVulkan(SDL_Window* window)
{ {
return ImGui_ImplSDL3_Init(window, nullptr); if (!ImGui_ImplSDL3_Init(window, nullptr, nullptr))
return false;
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
bd->UseVulkan = true;
return true;
} }
bool ImGui_ImplSDL3_InitForD3D(SDL_Window* window) bool ImGui_ImplSDL3_InitForD3D(SDL_Window* window)
@ -369,17 +423,17 @@ bool ImGui_ImplSDL3_InitForD3D(SDL_Window* window)
#if !defined(_WIN32) #if !defined(_WIN32)
IM_ASSERT(0 && "Unsupported"); IM_ASSERT(0 && "Unsupported");
#endif #endif
return ImGui_ImplSDL3_Init(window, nullptr); return ImGui_ImplSDL3_Init(window, nullptr, nullptr);
} }
bool ImGui_ImplSDL3_InitForMetal(SDL_Window* window) bool ImGui_ImplSDL3_InitForMetal(SDL_Window* window)
{ {
return ImGui_ImplSDL3_Init(window, nullptr); return ImGui_ImplSDL3_Init(window, nullptr, nullptr);
} }
bool ImGui_ImplSDL3_InitForSDLRenderer(SDL_Window* window, SDL_Renderer* renderer) bool ImGui_ImplSDL3_InitForSDLRenderer(SDL_Window* window, SDL_Renderer* renderer)
{ {
return ImGui_ImplSDL3_Init(window, renderer); return ImGui_ImplSDL3_Init(window, renderer, nullptr);
} }
void ImGui_ImplSDL3_Shutdown() void ImGui_ImplSDL3_Shutdown()
@ -388,6 +442,8 @@ void ImGui_ImplSDL3_Shutdown()
IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?"); IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?");
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
ImGui_ImplSDL3_ShutdownPlatformInterface();
if (bd->ClipboardTextData) if (bd->ClipboardTextData)
SDL_free(bd->ClipboardTextData); SDL_free(bd->ClipboardTextData);
for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++) for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++)
@ -399,6 +455,7 @@ void ImGui_ImplSDL3_Shutdown()
IM_DELETE(bd); IM_DELETE(bd);
} }
// This code is incredibly messy because some of the functions we need for full viewport support are not available in SDL < 2.0.4.
static void ImGui_ImplSDL3_UpdateMouseData() static void ImGui_ImplSDL3_UpdateMouseData()
{ {
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData(); ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
@ -409,27 +466,57 @@ static void ImGui_ImplSDL3_UpdateMouseData()
// SDL_CaptureMouse() let the OS know e.g. that our imgui drag outside the SDL window boundaries shouldn't e.g. trigger other operations outside // SDL_CaptureMouse() let the OS know e.g. that our imgui drag outside the SDL window boundaries shouldn't e.g. trigger other operations outside
SDL_CaptureMouse((bd->MouseButtonsDown != 0) ? SDL_TRUE : SDL_FALSE); SDL_CaptureMouse((bd->MouseButtonsDown != 0) ? SDL_TRUE : SDL_FALSE);
SDL_Window* focused_window = SDL_GetKeyboardFocus(); SDL_Window* focused_window = SDL_GetKeyboardFocus();
const bool is_app_focused = (bd->Window == focused_window); const bool is_app_focused = (focused_window && (bd->Window == focused_window || ImGui::FindViewportByPlatformHandle((void*)focused_window)));
#else #else
SDL_Window* focused_window = bd->Window;
const bool is_app_focused = (SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_INPUT_FOCUS) != 0; // SDL 2.0.3 and non-windowed systems: single-viewport only const bool is_app_focused = (SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_INPUT_FOCUS) != 0; // SDL 2.0.3 and non-windowed systems: single-viewport only
#endif #endif
if (is_app_focused) if (is_app_focused)
{ {
// (Optional) Set OS mouse position from Dear ImGui if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user) // (Optional) Set OS mouse position from Dear ImGui if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user)
if (io.WantSetMousePos) if (io.WantSetMousePos)
SDL_WarpMouseInWindow(bd->Window, io.MousePos.x, io.MousePos.y); {
#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
SDL_WarpMouseGlobal(io.MousePos.x, io.MousePos.y);
else
#endif
SDL_WarpMouseInWindow(bd->Window, io.MousePos.x, io.MousePos.y);
}
// (Optional) Fallback to provide mouse position when focused (SDL_MOUSEMOTION already provides this when hovered or captured) // (Optional) Fallback to provide mouse position when focused (SDL_MOUSEMOTION already provides this when hovered or captured)
if (bd->MouseCanUseGlobalState && bd->MouseButtonsDown == 0) if (bd->MouseCanUseGlobalState && bd->MouseButtonsDown == 0)
{ {
// Single-viewport mode: mouse position in client window coordinates (io.MousePos is (0,0) when the mouse is on the upper-left corner of the app window) // Single-viewport mode: mouse position in client window coordinates (io.MousePos is (0,0) when the mouse is on the upper-left corner of the app window)
float mouse_x_global, mouse_y_global; // Multi-viewport mode: mouse position in OS absolute coordinates (io.MousePos is (0,0) when the mouse is on the upper-left of the primary monitor)
float mouse_x, mouse_y;
int window_x, window_y; int window_x, window_y;
SDL_GetGlobalMouseState(&mouse_x_global, &mouse_y_global); SDL_GetGlobalMouseState(&mouse_x, &mouse_y);
SDL_GetWindowPosition(focused_window, &window_x, &window_y); if (!(io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable))
io.AddMousePosEvent(mouse_x_global - window_x, mouse_y_global - window_y); {
SDL_GetWindowPosition(focused_window, &window_x, &window_y);
mouse_x -= window_x;
mouse_y -= window_y;
}
io.AddMousePosEvent((float)mouse_x, (float)mouse_y);
} }
} }
// (Optional) When using multiple viewports: call io.AddMouseViewportEvent() with the viewport the OS mouse cursor is hovering.
// If ImGuiBackendFlags_HasMouseHoveredViewport is not set by the backend, Dear imGui will ignore this field and infer the information using its flawed heuristic.
// - [!] SDL backend does NOT correctly ignore viewports with the _NoInputs flag.
// Some backend are not able to handle that correctly. If a backend report an hovered viewport that has the _NoInputs flag (e.g. when dragging a window
// for docking, the viewport has the _NoInputs flag in order to allow us to find the viewport under), then Dear ImGui is forced to ignore the value reported
// by the backend, and use its flawed heuristic to guess the viewport behind.
// - [X] SDL backend correctly reports this regardless of another viewport behind focused and dragged from (we need this to find a useful drag and drop target).
if (io.BackendFlags & ImGuiBackendFlags_HasMouseHoveredViewport)
{
ImGuiID mouse_viewport_id = 0;
if (SDL_Window* sdl_mouse_window = SDL_GetWindowFromID(bd->MouseWindowID))
if (ImGuiViewport* mouse_viewport = ImGui::FindViewportByPlatformHandle((void*)sdl_mouse_window))
mouse_viewport_id = mouse_viewport->ID;
io.AddMouseViewportEvent(mouse_viewport_id);
}
} }
static void ImGui_ImplSDL3_UpdateMouseCursor() static void ImGui_ImplSDL3_UpdateMouseCursor()
@ -504,6 +591,35 @@ static void ImGui_ImplSDL3_UpdateGamepads()
#undef MAP_ANALOG #undef MAP_ANALOG
} }
// FIXME-PLATFORM: SDL doesn't have an event to notify the application of display/monitor changes
static void ImGui_ImplSDL3_UpdateMonitors()
{
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
platform_io.Monitors.resize(0);
int display_count;
SDL_DisplayID* displays = SDL_GetDisplays(&display_count);
for (int n = 0; n < display_count; n++)
{
// Warning: the validity of monitor DPI information on Windows depends on the application DPI awareness settings, which generally needs to be set in the manifest or at runtime.
SDL_DisplayID display_id = displays[n];
ImGuiPlatformMonitor monitor;
SDL_Rect r;
SDL_GetDisplayBounds(display_id, &r);
monitor.MainPos = monitor.WorkPos = ImVec2((float)r.x, (float)r.y);
monitor.MainSize = monitor.WorkSize = ImVec2((float)r.w, (float)r.h);
SDL_GetDisplayUsableBounds(display_id, &r);
monitor.WorkPos = ImVec2((float)r.x, (float)r.y);
monitor.WorkSize = ImVec2((float)r.w, (float)r.h);
// FIXME-VIEWPORT: On MacOS SDL reports actual monitor DPI scale, ignoring OS configuration. We may want to set
// DpiScale to cocoa_window.backingScaleFactor here.
float dpi = 0.0f;
if (!SDL_GetDisplayPhysicalDPI(display_id, &dpi, nullptr, nullptr))
monitor.DpiScale = dpi / 96.0f;
platform_io.Monitors.push_back(monitor);
}
}
void ImGui_ImplSDL3_NewFrame() void ImGui_ImplSDL3_NewFrame()
{ {
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData(); ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
@ -534,9 +650,251 @@ void ImGui_ImplSDL3_NewFrame()
io.AddMousePosEvent(-FLT_MAX, -FLT_MAX); io.AddMousePosEvent(-FLT_MAX, -FLT_MAX);
} }
// Our io.AddMouseViewportEvent() calls will only be valid when not capturing.
// Technically speaking testing for 'bd->MouseButtonsDown == 0' would be more rygorous, but testing for payload reduces noise and potential side-effects.
if (bd->MouseCanReportHoveredViewport && ImGui::GetDragDropPayload() == nullptr)
io.BackendFlags |= ImGuiBackendFlags_HasMouseHoveredViewport;
else
io.BackendFlags &= ~ImGuiBackendFlags_HasMouseHoveredViewport;
ImGui_ImplSDL3_UpdateMouseData(); ImGui_ImplSDL3_UpdateMouseData();
ImGui_ImplSDL3_UpdateMouseCursor(); ImGui_ImplSDL3_UpdateMouseCursor();
// Update game controllers (if enabled and available) // Update game controllers (if enabled and available)
ImGui_ImplSDL3_UpdateGamepads(); ImGui_ImplSDL3_UpdateGamepads();
} }
//--------------------------------------------------------------------------------------------------------
// MULTI-VIEWPORT / PLATFORM INTERFACE SUPPORT
// This is an _advanced_ and _optional_ feature, allowing the backend to create and handle multiple viewports simultaneously.
// If you are new to dear imgui or creating a new binding for dear imgui, it is recommended that you completely ignore this section first..
//--------------------------------------------------------------------------------------------------------
// Helper structure we store in the void* RenderUserData field of each ImGuiViewport to easily retrieve our backend data.
struct ImGui_ImplSDL3_ViewportData
{
SDL_Window* Window;
Uint32 WindowID;
bool WindowOwned;
SDL_GLContext GLContext;
ImGui_ImplSDL3_ViewportData() { Window = nullptr; WindowID = 0; WindowOwned = false; GLContext = nullptr; }
~ImGui_ImplSDL3_ViewportData() { IM_ASSERT(Window == nullptr && GLContext == nullptr); }
};
static void ImGui_ImplSDL3_CreateWindow(ImGuiViewport* viewport)
{
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
ImGui_ImplSDL3_ViewportData* vd = IM_NEW(ImGui_ImplSDL3_ViewportData)();
viewport->PlatformUserData = vd;
ImGuiViewport* main_viewport = ImGui::GetMainViewport();
ImGui_ImplSDL3_ViewportData* main_viewport_data = (ImGui_ImplSDL3_ViewportData*)main_viewport->PlatformUserData;
// Share GL resources with main context
bool use_opengl = (main_viewport_data->GLContext != nullptr);
SDL_GLContext backup_context = nullptr;
if (use_opengl)
{
backup_context = SDL_GL_GetCurrentContext();
SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
SDL_GL_MakeCurrent(main_viewport_data->Window, main_viewport_data->GLContext);
}
Uint32 sdl_flags = 0;
sdl_flags |= use_opengl ? SDL_WINDOW_OPENGL : (bd->UseVulkan ? SDL_WINDOW_VULKAN : 0);
sdl_flags |= SDL_GetWindowFlags(bd->Window);
sdl_flags |= (viewport->Flags & ImGuiViewportFlags_NoDecoration) ? SDL_WINDOW_BORDERLESS : 0;
sdl_flags |= (viewport->Flags & ImGuiViewportFlags_NoDecoration) ? 0 : SDL_WINDOW_RESIZABLE;
#if !defined(_WIN32)
// See SDL hack in ImGui_ImplSDL3_ShowWindow().
sdl_flags |= (viewport->Flags & ImGuiViewportFlags_NoTaskBarIcon) ? SDL_WINDOW_SKIP_TASKBAR : 0;
#endif
sdl_flags |= (viewport->Flags & ImGuiViewportFlags_TopMost) ? SDL_WINDOW_ALWAYS_ON_TOP : 0;
vd->Window = SDL_CreateWindow("No Title Yet", (int)viewport->Pos.x, (int)viewport->Pos.y, (int)viewport->Size.x, (int)viewport->Size.y, sdl_flags);
vd->WindowOwned = true;
if (use_opengl)
{
vd->GLContext = SDL_GL_CreateContext(vd->Window);
SDL_GL_SetSwapInterval(0);
}
if (use_opengl && backup_context)
SDL_GL_MakeCurrent(vd->Window, backup_context);
viewport->PlatformHandle = (void*)vd->Window;
viewport->PlatformHandleRaw = nullptr;
SDL_SysWMinfo info;
if (SDL_GetWindowWMInfo(vd->Window, &info, SDL_SYSWM_CURRENT_VERSION))
{
#if defined(SDL_VIDEO_DRIVER_WINDOWS)
viewport->PlatformHandleRaw = info.info.win.window;
#elif defined(__APPLE__) && defined(SDL_VIDEO_DRIVER_COCOA)
viewport->PlatformHandleRaw = (void*)info.info.cocoa.window;
#endif
}
}
static void ImGui_ImplSDL3_DestroyWindow(ImGuiViewport* viewport)
{
if (ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData)
{
if (vd->GLContext && vd->WindowOwned)
SDL_GL_DeleteContext(vd->GLContext);
if (vd->Window && vd->WindowOwned)
SDL_DestroyWindow(vd->Window);
vd->GLContext = nullptr;
vd->Window = nullptr;
IM_DELETE(vd);
}
viewport->PlatformUserData = viewport->PlatformHandle = nullptr;
}
static void ImGui_ImplSDL3_ShowWindow(ImGuiViewport* viewport)
{
ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
#if defined(_WIN32)
HWND hwnd = (HWND)viewport->PlatformHandleRaw;
// SDL hack: Hide icon from task bar
// Note: SDL 2.0.6+ has a SDL_WINDOW_SKIP_TASKBAR flag which is supported under Windows but the way it create the window breaks our seamless transition.
if (viewport->Flags & ImGuiViewportFlags_NoTaskBarIcon)
{
LONG ex_style = ::GetWindowLong(hwnd, GWL_EXSTYLE);
ex_style &= ~WS_EX_APPWINDOW;
ex_style |= WS_EX_TOOLWINDOW;
::SetWindowLong(hwnd, GWL_EXSTYLE, ex_style);
}
// SDL hack: SDL always activate/focus windows :/
if (viewport->Flags & ImGuiViewportFlags_NoFocusOnAppearing)
{
::ShowWindow(hwnd, SW_SHOWNA);
return;
}
#endif
SDL_ShowWindow(vd->Window);
}
static ImVec2 ImGui_ImplSDL3_GetWindowPos(ImGuiViewport* viewport)
{
ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
int x = 0, y = 0;
SDL_GetWindowPosition(vd->Window, &x, &y);
return ImVec2((float)x, (float)y);
}
static void ImGui_ImplSDL3_SetWindowPos(ImGuiViewport* viewport, ImVec2 pos)
{
ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
SDL_SetWindowPosition(vd->Window, (int)pos.x, (int)pos.y);
}
static ImVec2 ImGui_ImplSDL3_GetWindowSize(ImGuiViewport* viewport)
{
ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
int w = 0, h = 0;
SDL_GetWindowSize(vd->Window, &w, &h);
return ImVec2((float)w, (float)h);
}
static void ImGui_ImplSDL3_SetWindowSize(ImGuiViewport* viewport, ImVec2 size)
{
ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
SDL_SetWindowSize(vd->Window, (int)size.x, (int)size.y);
}
static void ImGui_ImplSDL3_SetWindowTitle(ImGuiViewport* viewport, const char* title)
{
ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
SDL_SetWindowTitle(vd->Window, title);
}
static void ImGui_ImplSDL3_SetWindowAlpha(ImGuiViewport* viewport, float alpha)
{
ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
SDL_SetWindowOpacity(vd->Window, alpha);
}
static void ImGui_ImplSDL3_SetWindowFocus(ImGuiViewport* viewport)
{
ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
SDL_RaiseWindow(vd->Window);
}
static bool ImGui_ImplSDL3_GetWindowFocus(ImGuiViewport* viewport)
{
ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
return (SDL_GetWindowFlags(vd->Window) & SDL_WINDOW_INPUT_FOCUS) != 0;
}
static bool ImGui_ImplSDL3_GetWindowMinimized(ImGuiViewport* viewport)
{
ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
return (SDL_GetWindowFlags(vd->Window) & SDL_WINDOW_MINIMIZED) != 0;
}
static void ImGui_ImplSDL3_RenderWindow(ImGuiViewport* viewport, void*)
{
ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
if (vd->GLContext)
SDL_GL_MakeCurrent(vd->Window, vd->GLContext);
}
static void ImGui_ImplSDL3_SwapBuffers(ImGuiViewport* viewport, void*)
{
ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
if (vd->GLContext)
{
SDL_GL_MakeCurrent(vd->Window, vd->GLContext);
SDL_GL_SwapWindow(vd->Window);
}
}
// Vulkan support (the Vulkan renderer needs to call a platform-side support function to create the surface)
// SDL is graceful enough to _not_ need <vulkan/vulkan.h> so we can safely include this.
#include <SDL3/SDL_vulkan.h>
static int ImGui_ImplSDL3_CreateVkSurface(ImGuiViewport* viewport, ImU64 vk_instance, const void* vk_allocator, ImU64* out_vk_surface)
{
ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
(void)vk_allocator;
SDL_bool ret = SDL_Vulkan_CreateSurface(vd->Window, (VkInstance)vk_instance, (VkSurfaceKHR*)out_vk_surface);
return ret ? 0 : 1; // ret ? VK_SUCCESS : VK_NOT_READY
}
static void ImGui_ImplSDL3_InitPlatformInterface(SDL_Window* window, void* sdl_gl_context)
{
// Register platform interface (will be coupled with a renderer interface)
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
platform_io.Platform_CreateWindow = ImGui_ImplSDL3_CreateWindow;
platform_io.Platform_DestroyWindow = ImGui_ImplSDL3_DestroyWindow;
platform_io.Platform_ShowWindow = ImGui_ImplSDL3_ShowWindow;
platform_io.Platform_SetWindowPos = ImGui_ImplSDL3_SetWindowPos;
platform_io.Platform_GetWindowPos = ImGui_ImplSDL3_GetWindowPos;
platform_io.Platform_SetWindowSize = ImGui_ImplSDL3_SetWindowSize;
platform_io.Platform_GetWindowSize = ImGui_ImplSDL3_GetWindowSize;
platform_io.Platform_SetWindowFocus = ImGui_ImplSDL3_SetWindowFocus;
platform_io.Platform_GetWindowFocus = ImGui_ImplSDL3_GetWindowFocus;
platform_io.Platform_GetWindowMinimized = ImGui_ImplSDL3_GetWindowMinimized;
platform_io.Platform_SetWindowTitle = ImGui_ImplSDL3_SetWindowTitle;
platform_io.Platform_RenderWindow = ImGui_ImplSDL3_RenderWindow;
platform_io.Platform_SwapBuffers = ImGui_ImplSDL3_SwapBuffers;
platform_io.Platform_SetWindowAlpha = ImGui_ImplSDL3_SetWindowAlpha;
platform_io.Platform_CreateVkSurface = ImGui_ImplSDL3_CreateVkSurface;
// Register main window handle (which is owned by the main application, not by us)
// This is mostly for simplicity and consistency, so that our code (e.g. mouse handling etc.) can use same logic for main and secondary viewports.
ImGuiViewport* main_viewport = ImGui::GetMainViewport();
ImGui_ImplSDL3_ViewportData* vd = IM_NEW(ImGui_ImplSDL3_ViewportData)();
vd->Window = window;
vd->WindowID = SDL_GetWindowID(window);
vd->WindowOwned = false;
vd->GLContext = sdl_gl_context;
main_viewport->PlatformUserData = vd;
main_viewport->PlatformHandle = vd->Window;
}
static void ImGui_ImplSDL3_ShutdownPlatformInterface()
{
ImGui::DestroyPlatformWindows();
}

View File

@ -8,6 +8,9 @@
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set] // [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. // [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. // [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
// [x] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable' -> the OS animation effect when window gets created/destroyed is problematic. SDL2 backend doesn't have issue.
// Missing features:
// [ ] Platform: Multi-viewport + Minimized windows seems to break mouse wheel events (at least under Windows).
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.

View File

@ -69,11 +69,23 @@ int main(int, char**)
ImGuiIO& io = ImGui::GetIO(); (void)io; ImGuiIO& io = ImGui::GetIO(); (void)io;
//io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
//io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows
//io.ConfigViewportsNoAutoMerge = true;
//io.ConfigViewportsNoTaskBarIcon = true;
// Setup Dear ImGui style // Setup Dear ImGui style
ImGui::StyleColorsDark(); ImGui::StyleColorsDark();
//ImGui::StyleColorsLight(); //ImGui::StyleColorsLight();
// When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones.
ImGuiStyle& style = ImGui::GetStyle();
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
{
style.WindowRounding = 0.0f;
style.Colors[ImGuiCol_WindowBg].w = 1.0f;
}
// Setup Platform/Renderer backends // Setup Platform/Renderer backends
ImGui_ImplSDL3_InitForOpenGL(window, gl_context); ImGui_ImplSDL3_InitForOpenGL(window, gl_context);
ImGui_ImplOpenGL3_Init(glsl_version); ImGui_ImplOpenGL3_Init(glsl_version);
@ -174,6 +186,19 @@ int main(int, char**)
glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w);
glClear(GL_COLOR_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
// Update and Render additional Platform Windows
// (Platform functions may change the current OpenGL context, so we save/restore it to make it easier to paste this code elsewhere.
// For this specific demo app we could also call SDL_GL_MakeCurrent(window, gl_context) directly)
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
{
SDL_Window* backup_current_window = SDL_GL_GetCurrentWindow();
SDL_GLContext backup_current_context = SDL_GL_GetCurrentContext();
ImGui::UpdatePlatformWindows();
ImGui::RenderPlatformWindowsDefault();
SDL_GL_MakeCurrent(backup_current_window, backup_current_context);
}
SDL_GL_SwapWindow(window); SDL_GL_SwapWindow(window);
} }
#ifdef __EMSCRIPTEN__ #ifdef __EMSCRIPTEN__