|
|
@@ -2082,6 +2082,7 @@ static const char* ImAtoi(const char* src, TYPE* output)
|
|
|
// - stb_sprintf.h supports several new modifiers which format numbers in a way that also makes them incompatible atof/atoi.
|
|
|
static void SanitizeFormatString(const char* fmt, char* fmt_out, size_t fmt_out_size)
|
|
|
{
|
|
|
+ IM_UNUSED(fmt_out_size);
|
|
|
const char* fmt_end = ImParseFormatFindEnd(fmt);
|
|
|
IM_ASSERT((size_t)(fmt_end - fmt + 1) < fmt_out_size); // Format is too long, let us know if this happens to you!
|
|
|
while (fmt < fmt_end)
|
|
|
@@ -4819,7 +4820,8 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag
|
|
|
char* p = buf;
|
|
|
while (*p == '#' || ImCharIsBlankA(*p))
|
|
|
p++;
|
|
|
- i[0] = i[1] = i[2] = i[3] = 0;
|
|
|
+ i[0] = i[1] = i[2] = 0;
|
|
|
+ i[3] = 0xFF; // alpha default to 255 is not parsed by scanf (e.g. inputting #FFFFFF omitting alpha)
|
|
|
if (alpha)
|
|
|
sscanf(p, "%02X%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2], (unsigned int*)&i[3]); // Treat at unsigned (%X is unsigned)
|
|
|
else
|
|
|
@@ -6617,46 +6619,63 @@ void ImGui::EndMenuBar()
|
|
|
window->DC.MenuBarAppending = false;
|
|
|
}
|
|
|
|
|
|
-bool ImGui::BeginMainMenuBar()
|
|
|
+// Important: calling order matters!
|
|
|
+// FIXME: Somehow overlapping with docking tech.
|
|
|
+// FIXME: The "rect-cut" aspect of this could be formalized into a lower-level helper (rect-cut: https://halt.software/dead-simple-layouts)
|
|
|
+bool ImGui::BeginViewportSideBar(const char* name, ImGuiViewport* viewport_p, ImGuiDir dir, float axis_size, ImGuiWindowFlags window_flags)
|
|
|
{
|
|
|
- ImGuiContext& g = *GImGui;
|
|
|
- ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)GetMainViewport();
|
|
|
- ImGuiWindow* menu_bar_window = FindWindowByName("##MainMenuBar");
|
|
|
-
|
|
|
- // For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.
|
|
|
- g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f));
|
|
|
+ IM_ASSERT(dir != ImGuiDir_None);
|
|
|
|
|
|
- // Get our rectangle at the top of the work area
|
|
|
- if (menu_bar_window == NULL || menu_bar_window->BeginCount == 0)
|
|
|
+ ImGuiWindow* bar_window = FindWindowByName(name);
|
|
|
+ if (bar_window == NULL || bar_window->BeginCount == 0)
|
|
|
{
|
|
|
- // Set window position
|
|
|
- // 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.
|
|
|
- ImVec2 menu_bar_pos = viewport->Pos + viewport->CurrWorkOffsetMin;
|
|
|
- ImVec2 menu_bar_size = ImVec2(viewport->Size.x - viewport->CurrWorkOffsetMin.x + viewport->CurrWorkOffsetMax.x, 1.0f);
|
|
|
- SetNextWindowPos(menu_bar_pos);
|
|
|
- SetNextWindowSize(menu_bar_size);
|
|
|
+ // Calculate and set window size/position
|
|
|
+ ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)(viewport_p ? viewport_p : GetMainViewport());
|
|
|
+ ImRect avail_rect = viewport->GetBuildWorkRect();
|
|
|
+ ImGuiAxis axis = (dir == ImGuiDir_Up || dir == ImGuiDir_Down) ? ImGuiAxis_Y : ImGuiAxis_X;
|
|
|
+ ImVec2 pos = avail_rect.Min;
|
|
|
+ if (dir == ImGuiDir_Right || dir == ImGuiDir_Down)
|
|
|
+ pos[axis] = avail_rect.Max[axis] - axis_size;
|
|
|
+ ImVec2 size = avail_rect.GetSize();
|
|
|
+ size[axis] = axis_size;
|
|
|
+ SetNextWindowPos(pos);
|
|
|
+ SetNextWindowSize(size);
|
|
|
+
|
|
|
+ // Report our size into work area (for next frame) using actual window size
|
|
|
+ if (dir == ImGuiDir_Up || dir == ImGuiDir_Left)
|
|
|
+ viewport->BuildWorkOffsetMin[axis] += axis_size;
|
|
|
+ else if (dir == ImGuiDir_Down || dir == ImGuiDir_Right)
|
|
|
+ viewport->BuildWorkOffsetMax[axis] -= axis_size;
|
|
|
}
|
|
|
|
|
|
- // Create window
|
|
|
+ window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
|
|
|
PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
|
|
|
- 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.
|
|
|
- ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;
|
|
|
- bool is_open = Begin("##MainMenuBar", NULL, window_flags) && BeginMenuBar();
|
|
|
+ PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0)); // Lift normal size constraint
|
|
|
+ bool is_open = Begin(name, NULL, window_flags);
|
|
|
PopStyleVar(2);
|
|
|
|
|
|
- // Report our size into work area (for next frame) using actual window size
|
|
|
- menu_bar_window = GetCurrentWindow();
|
|
|
- if (menu_bar_window->BeginCount == 1)
|
|
|
- viewport->CurrWorkOffsetMin.y += menu_bar_window->Size.y;
|
|
|
+ return is_open;
|
|
|
+}
|
|
|
+
|
|
|
+bool ImGui::BeginMainMenuBar()
|
|
|
+{
|
|
|
+ ImGuiContext& g = *GImGui;
|
|
|
+ ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)GetMainViewport();
|
|
|
|
|
|
+ // For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.
|
|
|
+ // FIXME: This could be generalized as an opt-in way to clamp window->DC.CursorStartPos to avoid SafeArea?
|
|
|
+ // FIXME: Consider removing support for safe area down the line... it's messy. Nowadays consoles have support for TV calibration in OS settings.
|
|
|
+ g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f));
|
|
|
+ ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;
|
|
|
+ float height = GetFrameHeight();
|
|
|
+ bool is_open = BeginViewportSideBar("##MainMenuBar", viewport, ImGuiDir_Up, height, window_flags);
|
|
|
g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f);
|
|
|
- if (!is_open)
|
|
|
- {
|
|
|
+
|
|
|
+ if (is_open)
|
|
|
+ BeginMenuBar();
|
|
|
+ else
|
|
|
End();
|
|
|
- return false;
|
|
|
- }
|
|
|
- return true; //-V1020
|
|
|
+ return is_open;
|
|
|
}
|
|
|
|
|
|
void ImGui::EndMainMenuBar()
|
|
|
@@ -6958,12 +6977,17 @@ ImGuiTabBar::ImGuiTabBar()
|
|
|
LastTabItemIdx = -1;
|
|
|
}
|
|
|
|
|
|
+static inline int TabItemGetSectionIdx(const ImGuiTabItem* tab)
|
|
|
+{
|
|
|
+ return (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1;
|
|
|
+}
|
|
|
+
|
|
|
static int IMGUI_CDECL TabItemComparerBySection(const void* lhs, const void* rhs)
|
|
|
{
|
|
|
const ImGuiTabItem* a = (const ImGuiTabItem*)lhs;
|
|
|
const ImGuiTabItem* b = (const ImGuiTabItem*)rhs;
|
|
|
- const int a_section = (a->Flags & ImGuiTabItemFlags_Leading) ? 0 : (a->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1;
|
|
|
- const int b_section = (b->Flags & ImGuiTabItemFlags_Leading) ? 0 : (b->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1;
|
|
|
+ const int a_section = TabItemGetSectionIdx(a);
|
|
|
+ const int b_section = TabItemGetSectionIdx(b);
|
|
|
if (a_section != b_section)
|
|
|
return a_section - b_section;
|
|
|
return (int)(a->IndexDuringLayout - b->IndexDuringLayout);
|
|
|
@@ -7132,11 +7156,11 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
|
|
|
tab->IndexDuringLayout = (ImS16)tab_dst_n;
|
|
|
|
|
|
// We will need sorting if tabs have changed section (e.g. moved from one of Leading/Central/Trailing to another)
|
|
|
- int curr_tab_section_n = (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1;
|
|
|
+ int curr_tab_section_n = TabItemGetSectionIdx(tab);
|
|
|
if (tab_dst_n > 0)
|
|
|
{
|
|
|
ImGuiTabItem* prev_tab = &tab_bar->Tabs[tab_dst_n - 1];
|
|
|
- int prev_tab_section_n = (prev_tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (prev_tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1;
|
|
|
+ int prev_tab_section_n = TabItemGetSectionIdx(prev_tab);
|
|
|
if (curr_tab_section_n == 0 && prev_tab_section_n != 0)
|
|
|
need_sort_by_section = true;
|
|
|
if (prev_tab_section_n == 2 && curr_tab_section_n != 2)
|
|
|
@@ -7208,7 +7232,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
|
|
|
const bool has_close_button = (tab->Flags & ImGuiTabItemFlags_NoCloseButton) ? false : true;
|
|
|
tab->ContentWidth = TabItemCalcSize(tab_name, has_close_button).x;
|
|
|
|
|
|
- int section_n = (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1;
|
|
|
+ int section_n = TabItemGetSectionIdx(tab);
|
|
|
ImGuiTabBarSection* section = §ions[section_n];
|
|
|
section->Width += tab->ContentWidth + (section_n == curr_section_n ? g.Style.ItemInnerSpacing.x : 0.0f);
|
|
|
curr_section_n = section_n;
|
|
|
@@ -7263,7 +7287,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
|
|
|
if (shrinked_width < 0.0f)
|
|
|
continue;
|
|
|
|
|
|
- int section_n = (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1;
|
|
|
+ int section_n = TabItemGetSectionIdx(tab);
|
|
|
sections[section_n].Width -= (tab->Width - shrinked_width);
|
|
|
tab->Width = shrinked_width;
|
|
|
}
|
|
|
@@ -7408,7 +7432,7 @@ static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGui
|
|
|
ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id);
|
|
|
if (tab == NULL)
|
|
|
return;
|
|
|
- if (tab->Flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing))
|
|
|
+ if (tab->Flags & ImGuiTabItemFlags_SectionMask_)
|
|
|
return;
|
|
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
|
@@ -7437,12 +7461,48 @@ static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGui
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-void ImGui::TabBarQueueReorder(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, int dir)
|
|
|
+void ImGui::TabBarQueueReorder(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, int offset)
|
|
|
{
|
|
|
- IM_ASSERT(dir == -1 || dir == +1);
|
|
|
+ IM_ASSERT(offset != 0);
|
|
|
IM_ASSERT(tab_bar->ReorderRequestTabId == 0);
|
|
|
tab_bar->ReorderRequestTabId = tab->ID;
|
|
|
- tab_bar->ReorderRequestDir = (ImS8)dir;
|
|
|
+ tab_bar->ReorderRequestOffset = (ImS16)offset;
|
|
|
+}
|
|
|
+
|
|
|
+void ImGui::TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, const ImGuiTabItem* src_tab, ImVec2 mouse_pos)
|
|
|
+{
|
|
|
+ ImGuiContext& g = *GImGui;
|
|
|
+ IM_ASSERT(tab_bar->ReorderRequestTabId == 0);
|
|
|
+ if ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) == 0)
|
|
|
+ return;
|
|
|
+
|
|
|
+ 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);
|
|
|
+
|
|
|
+ // Count number of contiguous tabs we are crossing over
|
|
|
+ const int dir = (bar_offset + src_tab->Offset) > mouse_pos.x ? -1 : +1;
|
|
|
+ const int src_idx = tab_bar->Tabs.index_from_ptr(src_tab);
|
|
|
+ int dst_idx = src_idx;
|
|
|
+ for (int i = src_idx; i >= 0 && i < tab_bar->Tabs.Size; i += dir)
|
|
|
+ {
|
|
|
+ // Reordered tabs must share the same section
|
|
|
+ const ImGuiTabItem* dst_tab = &tab_bar->Tabs[i];
|
|
|
+ if (dst_tab->Flags & ImGuiTabItemFlags_NoReorder)
|
|
|
+ break;
|
|
|
+ if ((dst_tab->Flags & ImGuiTabItemFlags_SectionMask_) != (src_tab->Flags & ImGuiTabItemFlags_SectionMask_))
|
|
|
+ break;
|
|
|
+ dst_idx = i;
|
|
|
+
|
|
|
+ // Include spacing after tab, so when mouse cursor is between tabs we would not continue checking further tabs that are not hovered.
|
|
|
+ const float x1 = bar_offset + dst_tab->Offset - g.Style.ItemInnerSpacing.x;
|
|
|
+ const float x2 = bar_offset + dst_tab->Offset + dst_tab->Width + g.Style.ItemInnerSpacing.x;
|
|
|
+ //GetForegroundDrawList()->AddRect(ImVec2(x1, tab_bar->BarRect.Min.y), ImVec2(x2, tab_bar->BarRect.Max.y), IM_COL32(255, 0, 0, 255));
|
|
|
+ if ((dir < 0 && mouse_pos.x > x1) || (dir > 0 && mouse_pos.x < x2))
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (dst_idx != src_idx)
|
|
|
+ TabBarQueueReorder(tab_bar, src_tab, dst_idx - src_idx);
|
|
|
}
|
|
|
|
|
|
bool ImGui::TabBarProcessReorder(ImGuiTabBar* tab_bar)
|
|
|
@@ -7452,19 +7512,23 @@ bool ImGui::TabBarProcessReorder(ImGuiTabBar* tab_bar)
|
|
|
return false;
|
|
|
|
|
|
//IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_Reorderable); // <- this may happen when using debug tools
|
|
|
- int tab2_order = tab_bar->GetTabOrder(tab1) + tab_bar->ReorderRequestDir;
|
|
|
+ int tab2_order = tab_bar->GetTabOrder(tab1) + tab_bar->ReorderRequestOffset;
|
|
|
if (tab2_order < 0 || tab2_order >= tab_bar->Tabs.Size)
|
|
|
return false;
|
|
|
|
|
|
- // Reordered TabItem must share the same position flags than target
|
|
|
+ // Reordered tabs must share the same section
|
|
|
+ // (Note: TabBarQueueReorderFromMousePos() also has a similar test but since we allow direct calls to TabBarQueueReorder() we do it here too)
|
|
|
ImGuiTabItem* tab2 = &tab_bar->Tabs[tab2_order];
|
|
|
if (tab2->Flags & ImGuiTabItemFlags_NoReorder)
|
|
|
return false;
|
|
|
- if ((tab1->Flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) != (tab2->Flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)))
|
|
|
+ if ((tab1->Flags & ImGuiTabItemFlags_SectionMask_) != (tab2->Flags & ImGuiTabItemFlags_SectionMask_))
|
|
|
return false;
|
|
|
|
|
|
ImGuiTabItem item_tmp = *tab1;
|
|
|
- *tab1 = *tab2;
|
|
|
+ ImGuiTabItem* src_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 + 1 : tab2;
|
|
|
+ ImGuiTabItem* dst_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 : tab2 + 1;
|
|
|
+ const int move_count = (tab_bar->ReorderRequestOffset > 0) ? tab_bar->ReorderRequestOffset : -tab_bar->ReorderRequestOffset;
|
|
|
+ memmove(dst_tab, src_tab, move_count * sizeof(ImGuiTabItem));
|
|
|
*tab2 = item_tmp;
|
|
|
|
|
|
if (tab_bar->Flags & ImGuiTabBarFlags_SaveSettings)
|
|
|
@@ -7746,7 +7810,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open,
|
|
|
const ImVec2 backup_main_cursor_pos = window->DC.CursorPos;
|
|
|
|
|
|
// Layout
|
|
|
- const bool is_central_section = (tab->Flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) == 0;
|
|
|
+ const bool is_central_section = (tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0;
|
|
|
size.x = tab->Width;
|
|
|
if (is_central_section)
|
|
|
window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(IM_FLOOR(tab->Offset - tab_bar->ScrollingAnim), 0.0f);
|
|
|
@@ -7794,13 +7858,11 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open,
|
|
|
// While moving a tab it will jump on the other side of the mouse, so we also test for MouseDelta.x
|
|
|
if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < bb.Min.x)
|
|
|
{
|
|
|
- if (tab_bar->Flags & ImGuiTabBarFlags_Reorderable)
|
|
|
- TabBarQueueReorder(tab_bar, tab, -1);
|
|
|
+ TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos);
|
|
|
}
|
|
|
else if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > bb.Max.x)
|
|
|
{
|
|
|
- if (tab_bar->Flags & ImGuiTabBarFlags_Reorderable)
|
|
|
- TabBarQueueReorder(tab_bar, tab, +1);
|
|
|
+ TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos);
|
|
|
}
|
|
|
}
|
|
|
}
|