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/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000000..092fb7fc1623 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,31 @@ +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 + + # 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 +) + +# 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") diff --git a/backends/imgui_impl_glfw.cpp b/backends/imgui_impl_glfw.cpp index 575b783494e6..7d72ecf66e50 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 @@ -1068,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); @@ -1085,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); @@ -1107,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) diff --git a/imgui.cpp b/imgui.cpp index 67295286aa97..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 @@ -5418,10 +5419,28 @@ 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; + // 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(); } } @@ -8052,7 +8071,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. @@ -17837,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 @@ -20011,12 +20043,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 +20450,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; } @@ -21955,7 +21993,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)) { 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 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() 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/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); diff --git a/premake5.lua b/premake5.lua index 039cc41aedc5..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}") @@ -21,6 +23,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 +36,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"