Skip to content

Commit 75de34e

Browse files
committed
Viewports, Docking: Added per-viewport work area system for e.g. menu-bars. Fixed DocksapceOverViewport() and demo code (overlay etc) (ocornut#3035, ocornut#2889, ocornut#2474, ocornut#1542, ocornut#2109)
Clarified that BeginMenuMainBar() had an incorrect knowledge of its height (which was previously harmless). Designed to easily allow for status bars although we don't have/use them yet, but custom code could use them.
1 parent f032ad6 commit 75de34e

File tree

5 files changed

+64
-25
lines changed

5 files changed

+64
-25
lines changed

imgui.cpp

+21-7
Original file line numberDiff line numberDiff line change
@@ -5273,6 +5273,7 @@ static ImVec2 CalcWindowAutoFitSize(ImGuiWindow* window, const ImVec2& size_cont
52735273
if (is_popup || is_menu) // Popups and menus bypass style.WindowMinSize by default, but we give then a non-zero minimum size to facilitate understanding problematic cases (e.g. empty popups)
52745274
size_min = ImMin(size_min, ImVec2(4.0f, 4.0f));
52755275

5276+
// FIXME-VIEWPORT-WORKAREA: May want to use GetWorkSize() instead of Size depending on the type of windows?
52765277
ImVec2 avail_size = window->Viewport->Size;
52775278
if (window->ViewportOwned)
52785279
avail_size = ImVec2(FLT_MAX, FLT_MAX);
@@ -6002,6 +6003,10 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
60026003
else
60036004
window->WindowPadding = style.WindowPadding;
60046005

6006+
// Lock menu offset so size calculation can use it as menu-bar windows need a minimum size.
6007+
window->DC.MenuBarOffset.x = ImMax(ImMax(window->WindowPadding.x, style.ItemSpacing.x), g.NextWindowData.MenuBarOffsetMinVal.x);
6008+
window->DC.MenuBarOffset.y = g.NextWindowData.MenuBarOffsetMinVal.y;
6009+
60056010
// Collapse window by double-clicking on title bar
60066011
// At this point we don't have a clipping rectangle setup yet, so we can use the title bar area for hit detection and drawing
60076012
if (!(flags & ImGuiWindowFlags_NoTitleBar) && !(flags & ImGuiWindowFlags_NoCollapse) && !window->DockIsActive)
@@ -6182,13 +6187,14 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
61826187

61836188
// Clamp position/size so window stays visible within its viewport or monitor
61846189
// Ignore zero-sized display explicitly to avoid losing positions if a window manager reports zero-sized window when initializing or minimizing.
6190+
// FIXME: Similar to code in GetWindowAllowedExtentRect()
61856191
ImRect viewport_rect = window->Viewport->GetMainRect();
61866192
if (!window_pos_set_by_api && !(flags & ImGuiWindowFlags_ChildWindow) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0)
61876193
{
61886194
ImVec2 clamp_padding = ImMax(style.DisplayWindowPadding, style.DisplaySafeAreaPadding);
61896195
if (!window->ViewportOwned && viewport_rect.GetWidth() > 0 && viewport_rect.GetHeight() > 0.0f)
61906196
{
6191-
ClampWindowRect(window, viewport_rect, clamp_padding);
6197+
ClampWindowRect(window, window->Viewport->GetWorkRect(), clamp_padding);
61926198
}
61936199
else if (window->ViewportOwned && g.PlatformIO.Monitors.Size > 0)
61946200
{
@@ -6437,8 +6443,6 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
64376443
window->DC.NavHasScroll = (window->ScrollMax.y > 0.0f);
64386444

64396445
window->DC.MenuBarAppending = false;
6440-
window->DC.MenuBarOffset.x = ImMax(ImMax(window->WindowPadding.x, style.ItemSpacing.x), g.NextWindowData.MenuBarOffsetMinVal.x);
6441-
window->DC.MenuBarOffset.y = g.NextWindowData.MenuBarOffsetMinVal.y;
64426446
window->DC.MenuColumns.Update(3, style.ItemSpacing.x, window_just_activated_by_user);
64436447
window->DC.TreeDepth = 0;
64446448
window->DC.TreeJumpToParentOnPopMask = 0x00;
@@ -8514,6 +8518,7 @@ ImVec2 ImGui::FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& s
85148518
return pos;
85158519
}
85168520

8521+
// Note that this is used for popups, which can overlap the non work-area of individual viewports.
85178522
ImRect ImGui::GetWindowAllowedExtentRect(ImGuiWindow* window)
85188523
{
85198524
ImGuiContext& g = *GImGui;
@@ -8527,6 +8532,7 @@ ImRect ImGui::GetWindowAllowedExtentRect(ImGuiWindow* window)
85278532
}
85288533
else
85298534
{
8535+
// Use the full viewport area (not work area) for popups
85308536
r_screen.Min = window->Viewport->Pos;
85318537
r_screen.Max = window->Viewport->Pos + window->Viewport->Size;
85328538
}
@@ -10731,6 +10737,11 @@ static void ImGui::UpdateViewportsNewFrame()
1073110737
// Update/copy monitor info
1073210738
UpdateViewportPlatformMonitor(viewport);
1073310739

10740+
// Lock down space taken by menu bars and status bars, reset the offset for fucntions like BeginMainMenuBar() to alter them again.
10741+
viewport->WorkOffsetMin = viewport->CurrWorkOffsetMin;
10742+
viewport->WorkOffsetMax = viewport->CurrWorkOffsetMax;
10743+
viewport->CurrWorkOffsetMin = viewport->CurrWorkOffsetMax = ImVec2(0.0f, 0.0f);
10744+
1073410745
// Reset alpha every frame. Users of transparency (docking) needs to request a lower alpha back.
1073510746
viewport->Alpha = 1.0f;
1073610747

@@ -13852,14 +13863,14 @@ void ImGui::DockSpace(ImGuiID id, const ImVec2& size_arg, ImGuiDockNodeFlags fla
1385213863
// Tips: Use with ImGuiDockNodeFlags_PassthruCentralNode!
1385313864
// The limitation with this call is that your window won't have a menu bar.
1385413865
// Even though we could pass window flags, it would also require the user to be able to call BeginMenuBar() somehow meaning we can't Begin/End in a single function.
13855-
// So if you want a menu bar you need to repeat this code manually ourselves. As with advanced other Docking API, we may change this function signature.
13866+
// But you can also use BeginMainMenuBar(). If you really want a menu bar inside the same window as the one hosting the dockspace, you will need to copy this code somewhere and tweak it.
1385613867
ImGuiID ImGui::DockSpaceOverViewport(ImGuiViewport* viewport, ImGuiDockNodeFlags dockspace_flags, const ImGuiWindowClass* window_class)
1385713868
{
1385813869
if (viewport == NULL)
1385913870
viewport = GetMainViewport();
1386013871

13861-
SetNextWindowPos(viewport->Pos);
13862-
SetNextWindowSize(viewport->Size);
13872+
SetNextWindowPos(viewport->GetWorkPos());
13873+
SetNextWindowSize(viewport->GetWorkSize());
1386313874
SetNextWindowViewport(viewport->ID);
1386413875

1386513876
ImGuiWindowFlags host_window_flags = 0;
@@ -15230,7 +15241,10 @@ void ImGui::ShowMetricsWindow(bool* p_open)
1523015241
if (ImGui::TreeNode((void*)(intptr_t)viewport->ID, "Viewport #%d, ID: 0x%08X, Parent: 0x%08X, Window: \"%s\"", viewport->Idx, viewport->ID, viewport->ParentViewportId, viewport->Window ? viewport->Window->Name : "N/A"))
1523115242
{
1523215243
ImGuiWindowFlags flags = viewport->Flags;
15233-
ImGui::BulletText("Pos: (%.0f,%.0f), Size: (%.0f,%.0f), Monitor: %d, DpiScale: %.0f%%", viewport->Pos.x, viewport->Pos.y, viewport->Size.x, viewport->Size.y, viewport->PlatformMonitor, viewport->DpiScale * 100.0f);
15244+
ImGui::BulletText("Main Pos: (%.0f,%.0f), Size: (%.0f,%.0f)\nWorkArea Offset Left: %.0f Top: %.0f, Right: %.0f, Bottom: %.0f\nMonitor: %d, DpiScale: %.0f%%",
15245+
viewport->Pos.x, viewport->Pos.y, viewport->Size.x, viewport->Size.y,
15246+
viewport->WorkOffsetMin.x, viewport->WorkOffsetMin.y, viewport->WorkOffsetMax.x, viewport->WorkOffsetMax.y,
15247+
viewport->PlatformMonitor, viewport->DpiScale * 100.0f);
1523415248
if (viewport->Idx > 0) { ImGui::SameLine(); if (ImGui::SmallButton("Reset Pos")) { viewport->Pos = ImVec2(200,200); if (viewport->Window) viewport->Window->Pos = ImVec2(200,200); } }
1523515249
ImGui::BulletText("Flags: 0x%04X =%s%s%s%s%s%s%s", viewport->Flags,
1523615250
(flags & ImGuiViewportFlags_CanHostOtherWindows) ? " CanHostOtherWindows" : "", (flags & ImGuiViewportFlags_NoDecoration) ? " NoDecoration" : "",

imgui.h

+12-4
Original file line numberDiff line numberDiff line change
@@ -2468,14 +2468,18 @@ enum ImGuiViewportFlags_
24682468
ImGuiViewportFlags_CanHostOtherWindows = 1 << 9 // Main viewport: can host multiple imgui windows (secondary viewports are associated to a single window).
24692469
};
24702470

2471-
// The viewports created and managed by imgui. The role of the platform back-end is to create the platform/OS windows corresponding to each viewport.
2471+
// The viewports created and managed by Dear ImGui. The role of the platform back-end is to create the platform/OS windows corresponding to each viewport.
2472+
// - Main Area = entire viewport.
2473+
// - Work Area = entire viewport minus sections optionally used by menu bars, status bars. Some positioning code will prefer to use this. Window are also trying to stay within this area.
24722474
struct ImGuiViewport
24732475
{
24742476
ImGuiID ID; // Unique identifier for the viewport
24752477
ImGuiViewportFlags Flags; // See ImGuiViewportFlags_
2476-
ImVec2 Pos; // Position of viewport both in imgui space and in OS desktop/native space
2477-
ImVec2 Size; // Size of viewport in pixel
2478-
float DpiScale; // 1.0f = 96 DPI = No extra scale
2478+
ImVec2 Pos; // Main Area: Position of the viewport (the imgui coordinates are the same as OS desktop/native coordinates)
2479+
ImVec2 Size; // Main Area: Size of the viewport.
2480+
ImVec2 WorkOffsetMin; // Work Area: Offset from Pos to top-left corner of Work Area. Generally (0,0) or (0,+main_menu_bar_height). Work Area is Full Area but without menu-bars/status-bars (so WorkArea always fit inside Pos/Size!)
2481+
ImVec2 WorkOffsetMax; // Work Area: Offset from Pos+Size to bottom-right corner of Work Area. Generally (0,0) or (0,-status_bar_height).
2482+
float DpiScale; // 1.0f = 96 DPI = No extra scale.
24792483
ImDrawData* DrawData; // The ImDrawData corresponding to this viewport. Valid after Render() and until the next call to NewFrame().
24802484
ImGuiID ParentViewportId; // (Advanced) 0: no parent. Instruct the platform back-end to setup a parent/child relationship between platform windows.
24812485

@@ -2491,6 +2495,10 @@ struct ImGuiViewport
24912495

24922496
ImGuiViewport() { ID = 0; Flags = 0; DpiScale = 0.0f; DrawData = NULL; ParentViewportId = 0; RendererUserData = PlatformUserData = PlatformHandle = PlatformHandleRaw = NULL; PlatformRequestMove = PlatformRequestResize = PlatformRequestClose = false; }
24932497
~ImGuiViewport() { IM_ASSERT(PlatformUserData == NULL && RendererUserData == NULL); }
2498+
2499+
// Access work-area rectangle
2500+
ImVec2 GetWorkPos() { return ImVec2(Pos.x + WorkOffsetMin.x, Pos.y + WorkOffsetMin.y); }
2501+
ImVec2 GetWorkSize() { return ImVec2(Size.x - WorkOffsetMin.x + WorkOffsetMax.x, Size.y - WorkOffsetMin.y + WorkOffsetMax.y); } // This not clamped
24942502
};
24952503

24962504
#if defined(__clang__)

imgui_demo.cpp

+9-7
Original file line numberDiff line numberDiff line change
@@ -222,9 +222,9 @@ void ImGui::ShowDemoWindow(bool* p_open)
222222
IM_ASSERT(ImGui::GetCurrentContext() != NULL && "Missing dear imgui context. Refer to examples app!"); // Exceptionally add an extra assert here for people confused with initial dear imgui setup
223223

224224
// Examples Apps (accessible from the "Examples" menu)
225+
static bool show_app_main_menu_bar = false;
225226
static bool show_app_dockspace = false;
226227
static bool show_app_documents = false;
227-
static bool show_app_main_menu_bar = false;
228228
static bool show_app_console = false;
229229
static bool show_app_log = false;
230230
static bool show_app_layout = false;
@@ -236,9 +236,9 @@ void ImGui::ShowDemoWindow(bool* p_open)
236236
static bool show_app_window_titles = false;
237237
static bool show_app_custom_rendering = false;
238238

239+
if (show_app_main_menu_bar) ShowExampleAppMainMenuBar();
239240
if (show_app_dockspace) ShowExampleAppDockSpace(&show_app_dockspace); // Process the Docking app first, as explicit DockSpace() nodes needs to be submitted early (read comments near the DockSpace function)
240241
if (show_app_documents) ShowExampleAppDocuments(&show_app_documents); // Process the Document app next, as it may also use a DockSpace()
241-
if (show_app_main_menu_bar) ShowExampleAppMainMenuBar();
242242
if (show_app_console) ShowExampleAppConsole(&show_app_console);
243243
if (show_app_log) ShowExampleAppLog(&show_app_log);
244244
if (show_app_layout) ShowExampleAppLayout(&show_app_layout);
@@ -286,8 +286,8 @@ void ImGui::ShowDemoWindow(bool* p_open)
286286
if (no_close) p_open = NULL; // Don't pass our bool* to Begin
287287

288288
// We specify a default position/size in case there's no data in the .ini file. Typically this isn't required! We only do it to make the Demo applications a little more welcoming.
289-
ImVec2 main_viewport_pos = ImGui::GetMainViewport()->Pos;
290-
ImGui::SetNextWindowPos(ImVec2(main_viewport_pos.x + 650, main_viewport_pos.y + 20), ImGuiCond_FirstUseEver);
289+
ImGuiViewport* main_viewport = ImGui::GetMainViewport();
290+
ImGui::SetNextWindowPos(ImVec2(main_viewport->GetWorkPos().x + 650, main_viewport->GetWorkPos().y + 20), ImGuiCond_FirstUseEver);
291291
ImGui::SetNextWindowSize(ImVec2(550, 680), ImGuiCond_FirstUseEver);
292292

293293
// Main body of the Demo window starts here.
@@ -4458,7 +4458,9 @@ static void ShowExampleAppSimpleOverlay(bool* p_open)
44584458
if (corner != -1)
44594459
{
44604460
ImGuiViewport* viewport = ImGui::GetMainViewport();
4461-
ImVec2 window_pos = ImVec2((corner & 1) ? (viewport->Pos.x + viewport->Size.x - DISTANCE) : (viewport->Pos.x + DISTANCE), (corner & 2) ? (viewport->Pos.y + viewport->Size.y - DISTANCE) : (viewport->Pos.y + DISTANCE));
4461+
ImVec2 work_area_pos = viewport->GetWorkPos(); // Instead of using viewport->Pos we use GetWorkPos() to avoid menu bars, if any!
4462+
ImVec2 work_area_size = viewport->GetWorkSize();
4463+
ImVec2 window_pos = ImVec2((corner & 1) ? (work_area_pos.x + work_area_size.x - DISTANCE) : (work_area_pos.x + DISTANCE), (corner & 2) ? (work_area_pos.y + work_area_size.y - DISTANCE) : (work_area_pos.y + DISTANCE));
44624464
ImVec2 window_pos_pivot = ImVec2((corner & 1) ? 1.0f : 0.0f, (corner & 2) ? 1.0f : 0.0f);
44634465
ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot);
44644466
ImGui::SetNextWindowViewport(viewport->ID);
@@ -4714,8 +4716,8 @@ void ShowExampleAppDockSpace(bool* p_open)
47144716
if (opt_fullscreen)
47154717
{
47164718
ImGuiViewport* viewport = ImGui::GetMainViewport();
4717-
ImGui::SetNextWindowPos(viewport->Pos);
4718-
ImGui::SetNextWindowSize(viewport->Size);
4719+
ImGui::SetNextWindowPos(viewport->GetWorkPos());
4720+
ImGui::SetNextWindowSize(viewport->GetWorkSize());
47194721
ImGui::SetNextWindowViewport(viewport->ID);
47204722
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
47214723
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);

imgui_internal.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -934,10 +934,13 @@ struct ImGuiViewportP : public ImGuiViewport
934934
ImVec2 LastPlatformPos;
935935
ImVec2 LastPlatformSize;
936936
ImVec2 LastRendererSize;
937+
ImVec2 CurrWorkOffsetMin; // Work area top-left offset being increased during the frame
938+
ImVec2 CurrWorkOffsetMax; // Work area bottom-right offset being decreased during the frame
937939

938940
ImGuiViewportP() { Idx = -1; LastFrameActive = LastFrameDrawLists[0] = LastFrameDrawLists[1] = LastFrontMostStampCount = -1; LastNameHash = 0; Alpha = LastAlpha = 1.0f; PlatformMonitor = -1; PlatformWindowCreated = false; Window = NULL; DrawLists[0] = DrawLists[1] = NULL; LastPlatformPos = LastPlatformSize = LastRendererSize = ImVec2(FLT_MAX, FLT_MAX); }
939941
~ImGuiViewportP() { if (DrawLists[0]) IM_DELETE(DrawLists[0]); if (DrawLists[1]) IM_DELETE(DrawLists[1]); }
940942
ImRect GetMainRect() const { return ImRect(Pos.x, Pos.y, Pos.x + Size.x, Pos.y + Size.y); }
943+
ImRect GetWorkRect() const { return ImRect(Pos.x + WorkOffsetMin.x, Pos.y + WorkOffsetMin.y, Pos.x + Size.x + WorkOffsetMax.x, Pos.y + Size.y + WorkOffsetMax.y); }
941944
void ClearRequestFlags() { PlatformRequestClose = PlatformRequestMove = PlatformRequestResize = false; }
942945
};
943946

@@ -991,7 +994,7 @@ struct ImGuiNextWindowData
991994
ImGuiID ViewportId;
992995
ImGuiID DockId;
993996
ImGuiWindowClass WindowClass;
994-
ImVec2 MenuBarOffsetMinVal; // *Always on* This is not exposed publicly, so we don't clear it.
997+
ImVec2 MenuBarOffsetMinVal; // (Always on) This is not exposed publicly, so we don't clear it and it doesn't have a corresponding flag (could we? for consistency?)
995998

996999
ImGuiNextWindowData() { memset(this, 0, sizeof(*this)); }
9971000
inline void ClearFlags() { Flags = ImGuiNextWindowDataFlags_None; }

imgui_widgets.cpp

+18-6
Original file line numberDiff line numberDiff line change
@@ -6182,20 +6182,32 @@ void ImGui::EndMenuBar()
61826182
window->DC.MenuBarAppending = false;
61836183
}
61846184

6185-
// For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.
61866185
bool ImGui::BeginMainMenuBar()
61876186
{
61886187
ImGuiContext& g = *GImGui;
6189-
ImGuiViewport* viewport = g.Viewports[0];
6188+
ImGuiViewportP* viewport = g.Viewports[0];
6189+
6190+
// For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.
61906191
g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f));
6191-
SetNextWindowPos(viewport->Pos);
6192-
SetNextWindowSize(ImVec2(viewport->Size.x, g.NextWindowData.MenuBarOffsetMinVal.y + g.FontBaseSize + g.Style.FramePadding.y));
6193-
SetNextWindowViewport(viewport->ID); // Enforce viewport so we don't create our onw viewport when ImGuiConfigFlags_ViewportsNoMerge is set.
6192+
6193+
// Get our rectangle in the work area, and report the size we need for next frame.
6194+
// We don't attempt to calculate our height ahead, as it depends on the per-viewport font size. However menu-bar will affect the minimum window size so we'll get the right height.
6195+
ImVec2 menu_bar_pos = viewport->Pos + viewport->CurrWorkOffsetMin;
6196+
ImVec2 menu_bar_size = ImVec2(viewport->Size.x - viewport->CurrWorkOffsetMin.x + viewport->CurrWorkOffsetMax.x, 1.0f);
6197+
6198+
// Create window
6199+
SetNextWindowPos(menu_bar_pos);
6200+
SetNextWindowSize(menu_bar_size);
6201+
SetNextWindowViewport(viewport->ID); // Enforce viewport so we don't create our own viewport when ImGuiConfigFlags_ViewportsNoMerge is set.
61946202
PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
6195-
PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0));
6203+
PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0)); // Lift normal size constraint, however the presence of a menu-bar will give us the minimum height we want.
61966204
ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;
61976205
bool is_open = Begin("##MainMenuBar", NULL, window_flags) && BeginMenuBar();
61986206
PopStyleVar(2);
6207+
6208+
// Feed back into work area using actual window size
6209+
viewport->CurrWorkOffsetMin.y += GetCurrentWindow()->Size.y;
6210+
61996211
g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f);
62006212
if (!is_open)
62016213
{

0 commit comments

Comments
 (0)