From 7267fc1b2fa8caa45410fd062e275d84626a8bb1 Mon Sep 17 00:00:00 2001 From: TheCherno Date: Thu, 12 Mar 2026 14:58:35 +1100 Subject: [PATCH 01/12] Custom UI styling: hamburger menu, tab spacing, centered labels - Replace dock menu arrow with hamburger icon - Set tab spacing to 0, center tab labels - Taller tab bar with 8px padding for docked windows - Simplified close/collapse button hover (no background rect) - Tab border: simple left-side vertical line - Opaque dock window menu popup Authored by Noah (noahxbowers@gmail.com) --- imgui.cpp | 19 +++++++++++++------ imgui_draw.cpp | 11 +++++++++-- imgui_widgets.cpp | 42 ++++++++++++++++++++---------------------- 3 files changed, 42 insertions(+), 30 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 67295286aa97..b070d1de5bf6 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -8052,7 +8052,8 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Lock menu offset so size calculation can use it as menu-bar windows need a minimum size. window->DC.MenuBarOffset.x = ImMax(ImMax(window->WindowPadding.x, style.ItemSpacing.x), g.NextWindowData.MenuBarOffsetMinVal.x); window->DC.MenuBarOffset.y = g.NextWindowData.MenuBarOffsetMinVal.y; - window->TitleBarHeight = (flags & ImGuiWindowFlags_NoTitleBar) ? 0.0f : g.FontSize + g.Style.FramePadding.y * 2.0f; + const float title_bar_padding = (window->DockIsActive) ? 8.0f : g.Style.FramePadding.y; + window->TitleBarHeight = (flags & ImGuiWindowFlags_NoTitleBar) ? 0.0f : g.FontSize + title_bar_padding * 2.0f; window->MenuBarHeight = (flags & ImGuiWindowFlags_MenuBar) ? window->DC.MenuBarOffset.y + g.FontSize + g.Style.FramePadding.y * 2.0f : 0.0f; window->FontRefSize = g.FontSize; // Lock this to discourage calling window->CalcFontSize() outside of current window. @@ -20011,12 +20012,16 @@ static void ImGui::DockNodeWindowMenuUpdate(ImGuiDockNode* node, ImGuiTabBar* ta SetNextWindowPos(ImVec2(node->Pos.x, node->Pos.y + GetFrameHeight()), ImGuiCond_Always, ImVec2(0.0f, 0.0f)); else SetNextWindowPos(ImVec2(node->Pos.x + node->Size.x, node->Pos.y + GetFrameHeight()), ImGuiCond_Always, ImVec2(1.0f, 0.0f)); + ImVec4 popupBg = g.Style.Colors[ImGuiCol_PopupBg]; + popupBg.w = 1.0f; + PushStyleColor(ImGuiCol_PopupBg, popupBg); if (BeginPopup("#WindowMenu")) { node->IsFocused = true; g.DockNodeWindowMenuHandler(&g, node, tab_bar); EndPopup(); } + PopStyleColor(); } // User helper to append/amend into a dock node tab bar. Most commonly used to add e.g. a "+" button. @@ -20414,28 +20419,30 @@ static void ImGui::DockNodeCalcTabBarLayout(const ImGuiDockNode* node, ImRect* o ImGuiContext& g = *GImGui; ImGuiStyle& style = g.Style; - ImRect r = ImRect(node->Pos.x, node->Pos.y, node->Pos.x + node->Size.x, node->Pos.y + g.FontSize + g.Style.FramePadding.y * 2.0f); + const float tab_padding_y = 8.0f; + ImRect r = ImRect(node->Pos.x, node->Pos.y, node->Pos.x + node->Size.x, node->Pos.y + g.FontSize + tab_padding_y * 2.0f); if (out_title_rect) { *out_title_rect = r; } r.Min.x += style.WindowBorderSize; r.Max.x -= style.WindowBorderSize; float button_sz = g.FontSize; + float button_y = r.Min.y + (r.GetHeight() - button_sz) * 0.5f; r.Min.x += style.FramePadding.x; r.Max.x -= style.FramePadding.x; - ImVec2 window_menu_button_pos = ImVec2(r.Min.x, r.Min.y + style.FramePadding.y); + ImVec2 window_menu_button_pos = ImVec2(r.Min.x, button_y); if (node->HasCloseButton) { - if (out_close_button_pos) *out_close_button_pos = ImVec2(r.Max.x - button_sz, r.Min.y + style.FramePadding.y); + if (out_close_button_pos) *out_close_button_pos = ImVec2(r.Max.x - button_sz, button_y); r.Max.x -= button_sz + style.ItemInnerSpacing.x; } if (node->HasWindowMenuButton && style.WindowMenuButtonPosition == ImGuiDir_Left) { - r.Min.x += button_sz + style.ItemInnerSpacing.x; + r.Min.x += button_sz + style.FramePadding.x; } else if (node->HasWindowMenuButton && style.WindowMenuButtonPosition == ImGuiDir_Right) { - window_menu_button_pos = ImVec2(r.Max.x - button_sz, r.Min.y + style.FramePadding.y); + window_menu_button_pos = ImVec2(r.Max.x - button_sz, button_y); r.Max.x -= button_sz + style.ItemInnerSpacing.x; } if (out_tab_bar_rect) { *out_tab_bar_rect = r; } diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 460449eb4fd0..48a9b4aa2ad0 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -5923,8 +5923,15 @@ void ImGui::RenderArrowPointingAt(ImDrawList* draw_list, ImVec2 pos, ImVec2 half // and because the saved space means that the left-most tab label can stay at exactly the same position as the label of a loose window. void ImGui::RenderArrowDockMenu(ImDrawList* draw_list, ImVec2 p_min, float sz, ImU32 col) { - draw_list->AddRectFilled(p_min + ImVec2(sz * 0.20f, sz * 0.15f), p_min + ImVec2(sz * 0.80f, sz * 0.30f), col); - RenderArrowPointingAt(draw_list, p_min + ImVec2(sz * 0.50f, sz * 0.85f), ImVec2(sz * 0.30f, sz * 0.40f), ImGuiDir_Down, col); + // Draw a hamburger menu icon (three horizontal lines) + float cx = p_min.x + sz * 0.5f; + float cy = p_min.y + sz * 0.5f; + float half = sz * 0.30f; + float spacing = sz * 0.22f; + float thickness = ImMax(sz * 0.12f, 1.0f); + draw_list->AddLine(ImVec2(cx - half, cy - spacing), ImVec2(cx + half, cy - spacing), col, thickness); + draw_list->AddLine(ImVec2(cx - half, cy), ImVec2(cx + half, cy), col, thickness); + draw_list->AddLine(ImVec2(cx - half, cy + spacing), ImVec2(cx + half, cy + spacing), col, thickness); } static inline float ImAcos01(float x) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 62373e2da7e2..50ff5aaa14c2 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -912,13 +912,10 @@ bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos) return pressed; // Render - ImU32 bg_col = GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered); - if (hovered) - window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col); RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact); - const ImU32 cross_col = GetColorU32(ImGuiCol_Text); + const ImU32 cross_col = (hovered || held) ? GetColorU32(ImGuiCol_Text) : GetColorU32(ImGuiCol_TextDisabled); const ImVec2 cross_center = bb.GetCenter() - ImVec2(0.5f, 0.5f); - const float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f; + const float cross_extent = g.FontSize * 0.5f * 0.5f - 1.0f; const float cross_thickness = 1.0f; // FIXME-DPI window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, +cross_extent), cross_center + ImVec2(-cross_extent, -cross_extent), cross_col, cross_thickness); window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, -cross_extent), cross_center + ImVec2(-cross_extent, +cross_extent), cross_col, cross_thickness); @@ -941,10 +938,7 @@ bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos, ImGuiDockNode* dock_no // Render //bool is_dock_menu = (window->DockNodeAsHost && !window->Collapsed); - ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); ImU32 text_col = GetColorU32(ImGuiCol_Text); - if (hovered || held) - window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col); RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact); if (dock_node) @@ -9802,7 +9796,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerBySection); // Calculate spacing between sections - const float tab_spacing = g.Style.ItemInnerSpacing.x; + const float tab_spacing = 0.0f; sections[0].Spacing = sections[0].TabCount > 0 && (sections[1].TabCount + sections[2].TabCount) > 0 ? tab_spacing : 0.0f; sections[1].Spacing = sections[1].TabCount > 0 && sections[2].TabCount > 0 ? tab_spacing : 0.0f; @@ -9950,7 +9944,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) ImGuiTabItem* tab = &tab_bar->Tabs[section_tab_index + tab_n]; tab->Offset = tab_offset; tab->NameOffset = -1; - tab_offset += tab->Width + (tab_n < section->TabCount - 1 ? g.Style.ItemInnerSpacing.x : 0.0f); + tab_offset += tab->Width + (tab_n < section->TabCount - 1 ? tab_spacing : 0.0f); } tab_bar->WidthAllTabs += ImMax(section->Width + section->Spacing, 0.0f); tab_offset += section->Spacing; @@ -10212,7 +10206,7 @@ void ImGui::TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, ImGuiTabItem* s if ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) == 0) return; - const float tab_spacing = g.Style.ItemInnerSpacing.x; + const float tab_spacing = 0.0f; const bool is_central_section = (src_tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0; const float bar_offset = tab_bar->BarRect.Min.x - (is_central_section ? tab_bar->ScrollingTarget : 0); @@ -10703,7 +10697,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, if (is_visible) { ImDrawList* display_draw_list = window->DrawList; - const ImU32 tab_col = GetColorU32((held || hovered) ? ImGuiCol_TabHovered : tab_contents_visible ? (tab_bar_focused ? ImGuiCol_TabSelected : ImGuiCol_TabDimmedSelected) : (tab_bar_focused ? ImGuiCol_Tab : ImGuiCol_TabDimmed)); + const ImU32 tab_col = GetColorU32(tab_contents_visible ? (tab_bar_focused ? ImGuiCol_TabSelected : ImGuiCol_TabDimmedSelected) : (tab_bar_focused ? ImGuiCol_Tab : ImGuiCol_TabDimmed)); TabItemBackground(display_draw_list, bb, flags, tab_col); if (tab_contents_visible && (tab_bar->Flags & ImGuiTabBarFlags_DrawSelectedOverline) && style.TabBarOverlineSize > 0.0f) { @@ -10800,11 +10794,12 @@ ImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button_or_unsave { ImGuiContext& g = *GImGui; ImVec2 label_size = CalcTextSize(label, NULL, true); - ImVec2 size = ImVec2(label_size.x + g.Style.FramePadding.x, label_size.y + g.Style.FramePadding.y * 2.0f); + const float tab_padding_y = 8.0f; + ImVec2 size = ImVec2(label_size.x + g.Style.FramePadding.x * 2.0f, label_size.y + tab_padding_y * 2.0f); if (has_close_button_or_unsaved_marker) - size.x += g.Style.FramePadding.x + (g.Style.ItemInnerSpacing.x + g.FontSize); // We use Y intentionally to fit the close button circle. + size.x += g.Style.FramePadding.x * 2.0f + (g.Style.ItemInnerSpacing.x + g.FontSize); // We use Y intentionally to fit the close button circle. else - size.x += g.Style.FramePadding.x + 1.0f; + size.x += g.Style.FramePadding.x * 2.0f + 1.0f; return ImVec2(ImMin(size.x, TabBarCalcMaxTabWidth()), size.y); } @@ -10830,11 +10825,7 @@ void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabI draw_list->PathFillConvex(col); if (g.Style.TabBorderSize > 0.0f) { - draw_list->PathLineTo(ImVec2(bb.Min.x + 0.5f, y2)); - draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding + 0.5f, y1 + rounding + 0.5f), rounding, 6, 9); - draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding - 0.5f, y1 + rounding + 0.5f), rounding, 9, 12); - draw_list->PathLineTo(ImVec2(bb.Max.x - 0.5f, y2)); - draw_list->PathStroke(GetColorU32(ImGuiCol_Border), 0, g.Style.TabBorderSize); + draw_list->AddLine(ImVec2(bb.Min.x + 0.5f, y1), ImVec2(bb.Min.x + 0.5f, y2), IM_COL32(0, 0, 0, 255), g.Style.TabBorderSize); } } @@ -10862,7 +10853,14 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, #endif // Render text label (with clipping + alpha gradient) + unsaved marker - ImRect text_ellipsis_clip_bb(bb.Min.x + frame_padding.x, bb.Min.y + frame_padding.y, bb.Max.x - frame_padding.x, bb.Max.y); + // Center text vertically within visible tab area (background starts at bb.Min.y + 1.0f) + float visible_top = bb.Min.y + 1.0f; + float visible_height = bb.Max.y - visible_top; + float text_y = visible_top + (visible_height - label_size.y) * 0.5f; + // Center text horizontally within the tab + float avail_width = bb.GetWidth() - frame_padding.x * 2.0f; + float text_offset_x = (avail_width > label_size.x) ? (avail_width - label_size.x) * 0.5f : 0.0f; + ImRect text_ellipsis_clip_bb(bb.Min.x + frame_padding.x + text_offset_x, text_y, bb.Max.x - frame_padding.x, bb.Max.y); // Return clipped state ignoring the close button if (out_text_clipped) @@ -10872,7 +10870,7 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, } const float button_sz = g.FontSize; - const ImVec2 button_pos(ImMax(bb.Min.x, bb.Max.x - frame_padding.x - button_sz), bb.Min.y + frame_padding.y); + const ImVec2 button_pos(ImMax(bb.Min.x, bb.Max.x - frame_padding.x - button_sz), bb.Min.y + (bb.GetHeight() - button_sz) * 0.5f); // Close Button & Unsaved Marker // We are relying on a subtle and confusing distinction between 'hovered' and 'g.HoveredId' which happens because we are using ImGuiButtonFlags_AllowOverlapMode + SetItemAllowOverlap() From a8dcee046dc4b07dc42fbd05c3819c7c4130f0cf Mon Sep 17 00:00:00 2001 From: TheCherno Date: Wed, 1 Apr 2026 15:01:46 +1100 Subject: [PATCH 02/12] Added ImPlot submodule + premake --- .gitmodules | 3 +++ implot | 1 + premake5.lua | 12 ++++++++++++ 3 files changed, 16 insertions(+) create mode 100644 .gitmodules create mode 160000 implot diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000000..12c8cfa298e0 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "implot"] + path = implot + url = https://github.com/StudioCherno/implot diff --git a/implot b/implot new file mode 160000 index 000000000000..16db49bbe421 --- /dev/null +++ b/implot @@ -0,0 +1 @@ +Subproject commit 16db49bbe42195a46825c9f0de26721e945b117c diff --git a/premake5.lua b/premake5.lua index 039cc41aedc5..6c7e77501155 100644 --- a/premake5.lua +++ b/premake5.lua @@ -21,6 +21,12 @@ project "ImGui" "imstb_textedit.h", "imstb_truetype.h", "imgui_demo.cpp", + + -- ImPlot submodule + "implot/implot.h", + "implot/implot.cpp", + "implot/implot_internal.h", + "implot/implot_items.cpp", } defines @@ -28,6 +34,12 @@ project "ImGui" "IMGUI_USE_WCHAR32" } + includedirs + { + -- Back to vendor dir for Hazel, which uses imgui/imgui.h include paths + "../" + } + filter "system:linux" pic "On" From bbdb64ebc09e00a4bb41733c3612f203a7fad7b6 Mon Sep 17 00:00:00 2001 From: yokljo Date: Wed, 15 Apr 2026 12:29:26 +1000 Subject: [PATCH 03/12] Allow viewport support on Wayland --- backends/imgui_impl_glfw.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/backends/imgui_impl_glfw.cpp b/backends/imgui_impl_glfw.cpp index 575b783494e6..04beab7d6293 100644 --- a/backends/imgui_impl_glfw.cpp +++ b/backends/imgui_impl_glfw.cpp @@ -703,10 +703,6 @@ static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, Glfw bool has_viewports = false; #ifndef __EMSCRIPTEN__ has_viewports = true; -#if GLFW_HAS_GETPLATFORM - if (glfwGetPlatform() == GLFW_PLATFORM_WAYLAND) - has_viewports = false; -#endif if (has_viewports) io.BackendFlags |= ImGuiBackendFlags_PlatformHasViewports; // We can create multi-viewports on the Platform side (optional) #endif From deac40a8dda00f8d816bc6761f04ac0d1da48df4 Mon Sep 17 00:00:00 2001 From: yokljo Date: Wed, 15 Apr 2026 15:12:50 +1000 Subject: [PATCH 04/12] Remove Wayland-specific content scaling logic --- backends/imgui_impl_glfw.cpp | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/backends/imgui_impl_glfw.cpp b/backends/imgui_impl_glfw.cpp index 04beab7d6293..7d72ecf66e50 100644 --- a/backends/imgui_impl_glfw.cpp +++ b/backends/imgui_impl_glfw.cpp @@ -1064,11 +1064,6 @@ static void ImGui_ImplGlfw_UpdateMonitors() // - Some accessibility applications are declaring virtual monitors with a DPI of 0.0f, see #7902. We preserve this value for caller to handle. float ImGui_ImplGlfw_GetContentScaleForWindow(GLFWwindow* window) { -#if GLFW_HAS_WAYLAND - if (ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(window)) - if (bd->IsWayland) - return 1.0f; -#endif #if GLFW_HAS_PER_MONITOR_DPI && !(defined(__APPLE__) || defined(__EMSCRIPTEN__) || defined(__ANDROID__)) float x_scale, y_scale; glfwGetWindowContentScale(window, &x_scale, &y_scale); @@ -1081,10 +1076,6 @@ float ImGui_ImplGlfw_GetContentScaleForWindow(GLFWwindow* window) float ImGui_ImplGlfw_GetContentScaleForMonitor(GLFWmonitor* monitor) { -#if GLFW_HAS_WAYLAND - if (ImGui_ImplGlfw_IsWayland()) // We can't access our bd->IsWayland cache for a monitor. - return 1.0f; -#endif #if GLFW_HAS_PER_MONITOR_DPI && !(defined(__APPLE__) || defined(__EMSCRIPTEN__) || defined(__ANDROID__)) float x_scale, y_scale; glfwGetMonitorContentScale(monitor, &x_scale, &y_scale); @@ -1103,11 +1094,6 @@ static void ImGui_ImplGlfw_GetWindowSizeAndFramebufferScale(GLFWwindow* window, glfwGetFramebufferSize(window, &display_w, &display_h); float fb_scale_x = (w > 0) ? (float)display_w / (float)w : 1.0f; float fb_scale_y = (h > 0) ? (float)display_h / (float)h : 1.0f; -#if GLFW_HAS_WAYLAND - ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(window); - if (!bd->IsWayland) - fb_scale_x = fb_scale_y = 1.0f; -#endif if (out_size != nullptr) *out_size = ImVec2((float)w, (float)h); if (out_framebuffer_scale != nullptr) From 10fc3f45129f1999df8927add6acc0ef405e75fd Mon Sep 17 00:00:00 2001 From: yokljo Date: Wed, 22 Apr 2026 16:46:28 +1000 Subject: [PATCH 05/12] Fixed incorrect window position information arising in UpdateMouseMovingWindowNewFrame, which would break docking when the windowing system doesn't match the requested window position --- imgui.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index b070d1de5bf6..6c06e8ab4a64 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -5418,10 +5418,21 @@ void ImGui::UpdateMouseMovingWindowNewFrame() ImVec2 pos = g.IO.MousePos - g.ActiveIdClickOffset; if (moving_window->Pos.x != pos.x || moving_window->Pos.y != pos.y) { - SetWindowPos(moving_window, pos, ImGuiCond_Always); + // Use the actual window location returned by the window system if possible, + // since windows can snap to borders and things like that which result in a final + // window location that is not exactly the same as what was requested. + ImVec2 actual = pos; + if (moving_window->Viewport && moving_window->ViewportOwned + && g.PlatformIO.Platform_SetWindowPos && g.PlatformIO.Platform_GetWindowPos) + { + g.PlatformIO.Platform_SetWindowPos(moving_window->Viewport, pos); + actual = g.PlatformIO.Platform_GetWindowPos(moving_window->Viewport); + } + SetWindowPos(moving_window, actual, ImGuiCond_Always); if (moving_window->Viewport && moving_window->ViewportOwned) // Synchronize viewport immediately because some overlays may relies on clipping rectangle before we Begin() into the window. { - moving_window->Viewport->Pos = pos; + moving_window->Viewport->Pos = actual; + moving_window->Viewport->LastPlatformPos = actual; moving_window->Viewport->UpdateWorkRect(); } } From 405786f4c5fc6918d01d85e33d4c87f27cbeccaa Mon Sep 17 00:00:00 2001 From: yokljo Date: Thu, 23 Apr 2026 10:32:26 +1000 Subject: [PATCH 06/12] Fixed jitter problem while dragging docks around --- imgui.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 6c06e8ab4a64..844a60d36e70 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -5431,8 +5431,15 @@ void ImGui::UpdateMouseMovingWindowNewFrame() SetWindowPos(moving_window, actual, ImGuiCond_Always); if (moving_window->Viewport && moving_window->ViewportOwned) // Synchronize viewport immediately because some overlays may relies on clipping rectangle before we Begin() into the window. { - moving_window->Viewport->Pos = actual; - moving_window->Viewport->LastPlatformPos = actual; + // Use moving_window->Pos (which SetWindowPos truncated via + // ImTrunc) rather than raw `actual`. If viewport->Pos kept + // the untruncated value, a later sync site would overwrite it + // with window->Pos (truncated), and UpdatePlatformWindows + // would see the mismatch against LastPlatformPos and push the + // truncated position to the OS — which maps to a different + // OS-pixel than what we just set, causing oscillation. + moving_window->Viewport->Pos = moving_window->Pos; + moving_window->Viewport->LastPlatformPos = moving_window->Pos; moving_window->Viewport->UpdateWorkRect(); } } From c69c53a3888338add39365d879dc9a6709687565 Mon Sep 17 00:00:00 2001 From: Devrim Yasar Date: Fri, 24 Apr 2026 15:29:56 +1000 Subject: [PATCH 07/12] fix: gate systemversion behind Windows filter for macOS compatibility premake5's xcode generator treats systemversion "latest" as a Windows SDK version and fails on macOS. Gate it behind a system:windows filter so non-Windows builds skip it. Co-Authored-By: Claude Opus 4.7 (1M context) --- premake5.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/premake5.lua b/premake5.lua index 6c7e77501155..604ef39da843 100644 --- a/premake5.lua +++ b/premake5.lua @@ -3,7 +3,9 @@ project "ImGui" language "C++" cppdialect "C++20" staticruntime "off" - systemversion "latest" + filter "system:windows" + systemversion "latest" + filter {} targetdir ("bin/" .. outputdir .. "/%{prj.name}") objdir ("bin-int/" .. outputdir .. "/%{prj.name}") From 8a73dbe41c514a7dad15678e4ebf9e4fe6e03750 Mon Sep 17 00:00:00 2001 From: Joshua Worth Date: Fri, 24 Apr 2026 16:46:35 +1000 Subject: [PATCH 08/12] Fixed docking targets not appearing when tearing a tab away from a very specific part of the tab itself --- imgui.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index 844a60d36e70..cdf1d503769d 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -21980,7 +21980,11 @@ void ImGui::BeginDockableDragDropSource(ImGuiWindow* window) g.LastItemData.ID = window->MoveId; window = window->RootWindowDockTree; IM_ASSERT((window->Flags & ImGuiWindowFlags_NoDocking) == 0); - bool is_drag_docking = (g.IO.ConfigDockingWithShift) || ImRect(0, 0, window->SizeFull.x, GetFrameHeight()).Contains(g.ActiveIdClickOffset); // FIXME-DOCKING: Need to make this stateful and explicit + // LuckyEngine: mirrors the custom tab_padding_y in TabItemCalcSize - without this, + // clicks in the lower half of a tall tab fall outside GetFrameHeight() and tear-away + // drags never turn into a drag-drop source (no docking overlay until re-grab). + const float tab_drag_height = ImMax(GetFrameHeight(), g.FontSize + 8.0f * 2.0f); + bool is_drag_docking = (g.IO.ConfigDockingWithShift) || ImRect(0, 0, window->SizeFull.x, tab_drag_height).Contains(g.ActiveIdClickOffset); // FIXME-DOCKING: Need to make this stateful and explicit ImGuiDragDropFlags drag_drop_flags = ImGuiDragDropFlags_SourceNoPreviewTooltip | ImGuiDragDropFlags_SourceNoHoldToOpenOthers | ImGuiDragDropFlags_PayloadAutoExpire | ImGuiDragDropFlags_PayloadNoCrossContext | ImGuiDragDropFlags_PayloadNoCrossProcess; if (is_drag_docking && BeginDragDropSource(drag_drop_flags)) { From 0531a43d31d95f1cc5e3d08af534d388216d6cf6 Mon Sep 17 00:00:00 2001 From: TheCherno Date: Thu, 30 Apr 2026 09:51:29 +1000 Subject: [PATCH 09/12] Added CMakeLists --- CMakeLists.txt | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000000..c6aeb417dae1 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,21 @@ +add_library(ImGui STATIC + imconfig.h + imgui.h + imgui.cpp + imgui_draw.cpp + imgui_internal.h + imgui_tables.cpp + imgui_widgets.cpp + imgui_demo.cpp + imstb_rectpack.h + imstb_textedit.h + imstb_truetype.h +) + +target_include_directories(ImGui PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_compile_features(ImGui PUBLIC cxx_std_20) + +target_compile_definitions(ImGui PUBLIC IMGUI_USE_WCHAR32) +target_compile_definitions(ImGui PUBLIC + $<$:NDEBUG IMGUI_DISABLE_DEMO_WINDOWS IMGUI_DISABLE_DEBUG_TOOLS> +) From 19ced6692d1502e8f2d65d85e560e634a73270f0 Mon Sep 17 00:00:00 2001 From: yokljo Date: Fri, 1 May 2026 15:17:36 +1000 Subject: [PATCH 10/12] Add a flag to disable splitting out docks into new windows --- imgui.cpp | 13 +++++++++++++ imgui.h | 1 + 2 files changed, 14 insertions(+) diff --git a/imgui.cpp b/imgui.cpp index cdf1d503769d..c627d508eb87 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1637,6 +1637,7 @@ ImGuiIO::ImGuiIO() ConfigViewportsNoTaskBarIcon = false; ConfigViewportsNoDecoration = true; ConfigViewportsNoDefaultParent = true; + ConfigViewportsNoFloatingWindows = false; ConfigViewportsPlatformFocusSetsImGuiFocus = true; // Miscellaneous options @@ -17856,6 +17857,18 @@ static void ImGui::WindowSelectViewport(ImGuiWindow* window) SetWindowViewport(window, main_viewport); return; } + + // When ConfigViewportsNoFloatingWindows is set, only popups/tooltips/menus + // may create their own viewport. All other windows stay in the main viewport. + if (g.IO.ConfigViewportsNoFloatingWindows) + { + if ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_ChildMenu)) == 0) + { + SetWindowViewport(window, main_viewport); + return; + } + } + window->ViewportOwned = false; // Appearing popups reset their viewport so they can inherit again diff --git a/imgui.h b/imgui.h index 6c1ac88afded..8fd3b92aa049 100644 --- a/imgui.h +++ b/imgui.h @@ -2521,6 +2521,7 @@ struct ImGuiIO bool ConfigViewportsNoTaskBarIcon; // = false // Disable default OS task bar icon flag for secondary viewports. When a viewport doesn't want a task bar icon, ImGuiViewportFlags_NoTaskBarIcon will be set on it. bool ConfigViewportsNoDecoration; // = true // Disable default OS window decoration flag for secondary viewports. When a viewport doesn't want window decorations, ImGuiViewportFlags_NoDecoration will be set on it. Enabling decoration can create subsequent issues at OS levels (e.g. minimum window size). bool ConfigViewportsNoDefaultParent; // = true // When false: set secondary viewports' ParentViewportId to main viewport ID by default. Expects the platform backend to setup a parent/child relationship between the OS windows based on this value. Some backend may ignore this. Set to true if you want viewports to automatically be parent of main viewport, otherwise all viewports will be top-level OS windows. + bool ConfigViewportsNoFloatingWindows;// = false // When set, only popups, tooltips and menus may create their own viewport. Undocked/floating windows stay inside the main viewport. Useful on platforms (e.g. Wayland) where secondary OS windows for docked panels are problematic. bool ConfigViewportsPlatformFocusSetsImGuiFocus;//= true // When a platform window is focused (e.g. using Alt+Tab, clicking Platform Title Bar), apply corresponding focus on imgui windows (may clear focus/active id from imgui windows location in other platform windows). In principle this is better enabled but we provide an opt-out, because some Linux window managers tend to eagerly focus windows (e.g. on mouse hover, or even a simple window pos/size change). // DPI/Scaling options From 869cddf52ee9430d78852d66bc5eb45c4664dbe3 Mon Sep 17 00:00:00 2001 From: yokljo Date: Thu, 7 May 2026 16:26:17 +1000 Subject: [PATCH 11/12] Fixed bugs in imgui_freetype preventing SVG emojis from rendering correctly --- misc/freetype/imgui_freetype.cpp | 65 +++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index 26a087017327..d864c1ad3fc6 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -621,6 +621,9 @@ struct LunasvgPortState FT_Error err = FT_Err_Ok; lunasvg::Matrix matrix; std::unique_ptr svg = nullptr; +#if LUNASVG_VERSION_MAJOR >= 3 + lunasvg::Element glyphElement; +#endif }; static FT_Error ImGuiLunasvgPortInit(FT_Pointer* _state) @@ -642,11 +645,20 @@ static FT_Error ImGuiLunasvgPortRender(FT_GlyphSlot slot, FT_Pointer* _state) if (state->err != FT_Err_Ok) return state->err; - // rows is height, pitch (or stride) equals to width * sizeof(int32) - lunasvg::Bitmap bitmap((uint8_t*)slot->bitmap.buffer, slot->bitmap.width, slot->bitmap.rows, slot->bitmap.pitch); #if LUNASVG_VERSION_MAJOR >= 3 - state->svg->render(bitmap, state->matrix); // state->matrix is already scaled and translated + if (state->glyphElement.isElement()) + { + lunasvg::Bitmap rendered = state->glyphElement.renderToBitmap(slot->bitmap.width, slot->bitmap.rows); + if (rendered.data()) + memcpy(slot->bitmap.buffer, rendered.data(), (size_t)slot->bitmap.rows * slot->bitmap.pitch); + } + else + { + lunasvg::Bitmap bitmap((uint8_t*)slot->bitmap.buffer, slot->bitmap.width, slot->bitmap.rows, slot->bitmap.pitch); + state->svg->render(bitmap, state->matrix); + } #else + lunasvg::Bitmap bitmap((uint8_t*)slot->bitmap.buffer, slot->bitmap.width, slot->bitmap.rows, slot->bitmap.pitch); state->svg->setMatrix(state->svg->matrix().identity()); // Reset the svg matrix to the default value state->svg->render(bitmap, state->matrix); // state->matrix is already scaled and translated #endif @@ -673,10 +685,26 @@ static FT_Error ImGuiLunasvgPortPresetSlot(FT_GlyphSlot slot, FT_Bool cache, FT_ } #if LUNASVG_VERSION_MAJOR >= 3 - lunasvg::Box box = state->svg->boundingBox(); + // Multi-glyph SVG documents (e.g. NotoColorEmoji) store all glyphs in one SVG + // with elements identified by "glyph". Find the specific glyph element. + char glyph_id_str[64]; + ImFormatString(glyph_id_str, sizeof(glyph_id_str), "glyph%u", slot->glyph_index); + state->glyphElement = state->svg->getElementById(glyph_id_str); + + lunasvg::Box box; + if (state->glyphElement.isElement()) + box = state->glyphElement.getBoundingBox(); + else + box = state->svg->boundingBox(); #else lunasvg::Box box = state->svg->box(); #endif + if (box.w == 0 || box.h == 0) + { + state->err = FT_Err_Invalid_SVG_Document; + return state->err; + } + double scale = std::min(metrics.x_ppem / box.w, metrics.y_ppem / box.h); double xx = (double)document->transform.xx / (1 << 16); double xy = -(double)document->transform.xy / (1 << 16); @@ -686,7 +714,34 @@ static FT_Error ImGuiLunasvgPortPresetSlot(FT_GlyphSlot slot, FT_Bool cache, FT_ double y0 = -(double)document->delta.y / 64 * box.h / metrics.y_ppem; #if LUNASVG_VERSION_MAJOR >= 3 - // Scale, transform and pre-translate the matrix for the rendering step + if (state->glyphElement.isElement()) + { + // renderToBitmap handles positioning internally, so we just need pixel dimensions + double bitmapW = box.w * scale; + double bitmapH = box.h * scale; + + slot->bitmap.width = (unsigned int)(ImCeil((float)bitmapW)); + slot->bitmap.rows = (unsigned int)(ImCeil((float)bitmapH)); + slot->bitmap.pitch = slot->bitmap.width * 4; + slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA; + slot->bitmap_left = 0; + slot->bitmap_top = (FT_Int)slot->bitmap.rows; + + slot->metrics.width = FT_Pos(IM_ROUND(bitmapW * 64.0)); + slot->metrics.height = FT_Pos(IM_ROUND(bitmapH * 64.0)); + slot->metrics.horiBearingX = 0; + slot->metrics.horiBearingY = FT_Pos(IM_ROUND(bitmapH * 64.0)); + slot->metrics.vertBearingX = slot->metrics.horiBearingX / 2 - slot->metrics.horiAdvance / 2; + slot->metrics.vertBearingY = (slot->metrics.vertAdvance - slot->metrics.height) / 2; + + if (slot->metrics.vertAdvance == 0) + slot->metrics.vertAdvance = FT_Pos(bitmapH * 1.2 * 64.0); + + state->err = FT_Err_Ok; + return state->err; + } + + // Fallback: single-glyph SVG documents — use standard matrix-based rendering state->matrix = lunasvg::Matrix::translated(-box.x, -box.y); state->matrix.multiply(lunasvg::Matrix(xx, xy, yx, yy, x0, y0)); state->matrix.scale(scale, scale); From 86e47a45085147969caa772f7b294edcc5f4f752 Mon Sep 17 00:00:00 2001 From: Bailey Chessum Date: Wed, 17 Jun 2026 14:17:59 +1000 Subject: [PATCH 12/12] build: build ImPlot and set PIC/FOLDER in CMakeLists Extend the imported hazel-cmake CMakeLists to compile the ImPlot sources this lineage carries (implot.cpp, implot_items.cpp), mark the target position- independent (it links into the shared engine), and group it under Dependencies. SYSTEM include suppresses third-party header warnings. The include root is a vendor-layout concern and stays in the consuming CMakeLists. --- CMakeLists.txt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c6aeb417dae1..092fb7fc1623 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,12 +10,22 @@ add_library(ImGui STATIC imstb_rectpack.h imstb_textedit.h imstb_truetype.h + + # ImPlot lives in this fork (added on the SimpleUX/Wayland lineage); compiled + # into ImGui to match the premake source split. implot_demo.cpp is excluded. + implot/implot.cpp + implot/implot_items.cpp ) -target_include_directories(ImGui PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +# Bare / "imgui.h" and implot's relative "implot.h". The +# style used across the engine is a vendor-layout convention and is exposed by +# Hazel/vendor/CMakeLists.txt, not assumed by this fork. +target_include_directories(ImGui SYSTEM PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_compile_features(ImGui PUBLIC cxx_std_20) target_compile_definitions(ImGui PUBLIC IMGUI_USE_WCHAR32) target_compile_definitions(ImGui PUBLIC $<$:NDEBUG IMGUI_DISABLE_DEMO_WINDOWS IMGUI_DISABLE_DEBUG_TOOLS> ) + +set_target_properties(ImGui PROPERTIES POSITION_INDEPENDENT_CODE ON FOLDER "Dependencies")