ImGuiWindowFlags_UnsavedDocument/ImGuiTabItmeFlags_UnsavedDocument display a dot instead of a '*'.

This commit is contained in:
ocornut 2021-06-24 14:30:32 +02:00
parent 0b8a247074
commit 1965f38e9e
5 changed files with 52 additions and 24 deletions

View File

@ -36,7 +36,9 @@ HOW TO UPDATE?
----------------------------------------------------------------------- -----------------------------------------------------------------------
Other Changes: Other Changes:
- TabBar: Fixed using more than 32 KB-worth of tab names. (#4176) - Windows: ImGuiWindowFlags_UnsavedDocument/ImGuiTabItmeFlags_UnsavedDocument display a dot instead of a '*' so it
is independent from font style. When in a tab, the dot is displayed at the same position as the close button.
Added extra comments to clarify the purpose of this flag in the context of docked windows.
- Tables: Added ImGuiTableColumnFlags_Disabled acting a master disable over (hidden from user/context menu). (#3935) - Tables: Added ImGuiTableColumnFlags_Disabled acting a master disable over (hidden from user/context menu). (#3935)
- Tables: Clarified that TableSetColumnEnabled() requires the table to use the ImGuiTableFlags_Hideable flag, - Tables: Clarified that TableSetColumnEnabled() requires the table to use the ImGuiTableFlags_Hideable flag,
because it manipulates the user-accessible show/hide state. (#3935) because it manipulates the user-accessible show/hide state. (#3935)
@ -44,6 +46,7 @@ Other Changes:
Convenient for some small columns. Name will still appear in context menu. (#4206). Convenient for some small columns. Name will still appear in context menu. (#4206).
- Tables: Fix columns order on TableSetupScrollFreeze() if previous data got frozen columns out of their section. - Tables: Fix columns order on TableSetupScrollFreeze() if previous data got frozen columns out of their section.
- Tables: Fix invalid data in TableGetSortSpecs() when SpecsDirty flag is unset. (#4233) - Tables: Fix invalid data in TableGetSortSpecs() when SpecsDirty flag is unset. (#4233)
- TabBar: Fixed using more than 32 KB-worth of tab names. (#4176)
- Fixed printf-style format checks on non-MinGW flavors. (#4183, #3592) - Fixed printf-style format checks on non-MinGW flavors. (#4183, #3592)
- Demo: Fixed requirement in 1.83 to link with imgui_demo.cpp if IMGUI_DISABLE_METRICS_WINDOW is not set. (#4171) - Demo: Fixed requirement in 1.83 to link with imgui_demo.cpp if IMGUI_DISABLE_METRICS_WINDOW is not set. (#4171)
Normally the right way to disable compiling the demo is to set IMGUI_DISABLE_DEMO_WINDOWS, but we want to avoid Normally the right way to disable compiling the demo is to set IMGUI_DISABLE_DEMO_WINDOWS, but we want to avoid

View File

@ -5666,8 +5666,7 @@ void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& titl
// Title bar text (with: horizontal alignment, avoiding collapse/close button, optional "unsaved document" marker) // Title bar text (with: horizontal alignment, avoiding collapse/close button, optional "unsaved document" marker)
// FIXME: Refactor text alignment facilities along with RenderText helpers, this is WAY too much messy code.. // FIXME: Refactor text alignment facilities along with RenderText helpers, this is WAY too much messy code..
const char* UNSAVED_DOCUMENT_MARKER = "*"; const float marker_size_x = (flags & ImGuiWindowFlags_UnsavedDocument) ? button_sz * 0.80f : 0.0f;
const float marker_size_x = (flags & ImGuiWindowFlags_UnsavedDocument) ? CalcTextSize(UNSAVED_DOCUMENT_MARKER, NULL, false).x : 0.0f;
const ImVec2 text_size = CalcTextSize(name, NULL, true) + ImVec2(marker_size_x, 0.0f); const ImVec2 text_size = CalcTextSize(name, NULL, true) + ImVec2(marker_size_x, 0.0f);
// As a nice touch we try to ensure that centered title text doesn't get affected by visibility of Close/Collapse button, // As a nice touch we try to ensure that centered title text doesn't get affected by visibility of Close/Collapse button,
@ -5686,15 +5685,20 @@ void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& titl
ImRect layout_r(title_bar_rect.Min.x + pad_l, title_bar_rect.Min.y, title_bar_rect.Max.x - pad_r, title_bar_rect.Max.y); ImRect layout_r(title_bar_rect.Min.x + pad_l, title_bar_rect.Min.y, title_bar_rect.Max.x - pad_r, title_bar_rect.Max.y);
ImRect clip_r(layout_r.Min.x, layout_r.Min.y, ImMin(layout_r.Max.x + g.Style.ItemInnerSpacing.x, title_bar_rect.Max.x), layout_r.Max.y); ImRect clip_r(layout_r.Min.x, layout_r.Min.y, ImMin(layout_r.Max.x + g.Style.ItemInnerSpacing.x, title_bar_rect.Max.x), layout_r.Max.y);
if (flags & ImGuiWindowFlags_UnsavedDocument)
{
ImVec2 marker_pos;
marker_pos.x = ImClamp(layout_r.Min.x + (layout_r.GetWidth() - text_size.x) * style.WindowTitleAlign.x + text_size.x, layout_r.Min.x, layout_r.Max.x);
marker_pos.y = (layout_r.Min.y + layout_r.Max.y) * 0.5f;
if (marker_pos.x > layout_r.Min.x)
{
RenderBullet(window->DrawList, marker_pos, GetColorU32(ImGuiCol_Text));
clip_r.Max.x = ImMin(clip_r.Max.x, marker_pos.x - (int)(marker_size_x * 0.5f));
}
}
//if (g.IO.KeyShift) window->DrawList->AddRect(layout_r.Min, layout_r.Max, IM_COL32(255, 128, 0, 255)); // [DEBUG] //if (g.IO.KeyShift) window->DrawList->AddRect(layout_r.Min, layout_r.Max, IM_COL32(255, 128, 0, 255)); // [DEBUG]
//if (g.IO.KeyCtrl) window->DrawList->AddRect(clip_r.Min, clip_r.Max, IM_COL32(255, 128, 0, 255)); // [DEBUG] //if (g.IO.KeyCtrl) window->DrawList->AddRect(clip_r.Min, clip_r.Max, IM_COL32(255, 128, 0, 255)); // [DEBUG]
RenderTextClipped(layout_r.Min, layout_r.Max, name, NULL, &text_size, style.WindowTitleAlign, &clip_r); RenderTextClipped(layout_r.Min, layout_r.Max, name, NULL, &text_size, style.WindowTitleAlign, &clip_r);
if (flags & ImGuiWindowFlags_UnsavedDocument)
{
ImVec2 marker_pos = ImVec2(ImMax(layout_r.Min.x, layout_r.Min.x + (layout_r.GetWidth() - text_size.x) * style.WindowTitleAlign.x) + text_size.x, layout_r.Min.y) + ImVec2(2 - marker_size_x, 0.0f);
ImVec2 off = ImVec2(0.0f, IM_FLOOR(-g.FontSize * 0.25f));
RenderTextClipped(marker_pos + off, layout_r.Max + off, UNSAVED_DOCUMENT_MARKER, NULL, NULL, ImVec2(0, style.WindowTitleAlign.y), &clip_r);
}
} }
void ImGui::UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags flags, ImGuiWindow* parent_window) void ImGui::UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags flags, ImGuiWindow* parent_window)

View File

@ -932,7 +932,7 @@ enum ImGuiWindowFlags_
ImGuiWindowFlags_AlwaysUseWindowPadding = 1 << 16, // Ensure child windows without border uses style.WindowPadding (ignored by default for non-bordered child windows, because more convenient) ImGuiWindowFlags_AlwaysUseWindowPadding = 1 << 16, // Ensure child windows without border uses style.WindowPadding (ignored by default for non-bordered child windows, because more convenient)
ImGuiWindowFlags_NoNavInputs = 1 << 18, // No gamepad/keyboard navigation within the window ImGuiWindowFlags_NoNavInputs = 1 << 18, // No gamepad/keyboard navigation within the window
ImGuiWindowFlags_NoNavFocus = 1 << 19, // No focusing toward this window with gamepad/keyboard navigation (e.g. skipped by CTRL+TAB) ImGuiWindowFlags_NoNavFocus = 1 << 19, // No focusing toward this window with gamepad/keyboard navigation (e.g. skipped by CTRL+TAB)
ImGuiWindowFlags_UnsavedDocument = 1 << 20, // Append '*' to title without affecting the ID, as a convenience to avoid using the ### operator. When used in a tab/docking context, tab is selected on closure and closure is deferred by one frame to allow code to cancel the closure (with a confirmation popup, etc.) without flicker. ImGuiWindowFlags_UnsavedDocument = 1 << 20, // Display a dot next to the title. When used in a tab/docking context, tab is selected when clicking the X + closure is not assumed (will wait for user to stop submitting the tab). Otherwise closure is assumed when pressing the X, so if you keep submitting the tab may reappear at end of tab bar.
ImGuiWindowFlags_NoNav = ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus, ImGuiWindowFlags_NoNav = ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus,
ImGuiWindowFlags_NoDecoration = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse, ImGuiWindowFlags_NoDecoration = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse,
ImGuiWindowFlags_NoInputs = ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus, ImGuiWindowFlags_NoInputs = ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus,
@ -1070,7 +1070,7 @@ enum ImGuiTabBarFlags_
enum ImGuiTabItemFlags_ enum ImGuiTabItemFlags_
{ {
ImGuiTabItemFlags_None = 0, ImGuiTabItemFlags_None = 0,
ImGuiTabItemFlags_UnsavedDocument = 1 << 0, // Append '*' to title without affecting the ID, as a convenience to avoid using the ### operator. Also: tab is selected on closure and closure is deferred by one frame to allow code to undo it without flicker. ImGuiTabItemFlags_UnsavedDocument = 1 << 0, // Display a dot next to the title + tab is selected when clicking the X + closure is not assumed (will wait for user to stop submitting the tab). Otherwise closure is assumed when pressing the X, so if you keep submitting the tab may reappear at end of tab bar.
ImGuiTabItemFlags_SetSelected = 1 << 1, // Trigger flag to programmatically make the tab selected when calling BeginTabItem() ImGuiTabItemFlags_SetSelected = 1 << 1, // Trigger flag to programmatically make the tab selected when calling BeginTabItem()
ImGuiTabItemFlags_NoCloseWithMiddleMouseButton = 1 << 2, // Disable behavior of closing tabs (that are submitted with p_open != NULL) with middle mouse button. You can still repro this behavior on user's side with if (IsItemHovered() && IsMouseClicked(2)) *p_open = false. ImGuiTabItemFlags_NoCloseWithMiddleMouseButton = 1 << 2, // Disable behavior of closing tabs (that are submitted with p_open != NULL) with middle mouse button. You can still repro this behavior on user's side with if (IsItemHovered() && IsMouseClicked(2)) *p_open = false.
ImGuiTabItemFlags_NoPushId = 1 << 3, // Don't call PushID(tab->ID)/PopID() on BeginTabItem()/EndTabItem() ImGuiTabItemFlags_NoPushId = 1 << 3, // Don't call PushID(tab->ID)/PopID() on BeginTabItem()/EndTabItem()

View File

@ -317,6 +317,7 @@ void ImGui::ShowDemoWindow(bool* p_open)
static bool no_nav = false; static bool no_nav = false;
static bool no_background = false; static bool no_background = false;
static bool no_bring_to_front = false; static bool no_bring_to_front = false;
static bool unsaved_document = false;
ImGuiWindowFlags window_flags = 0; ImGuiWindowFlags window_flags = 0;
if (no_titlebar) window_flags |= ImGuiWindowFlags_NoTitleBar; if (no_titlebar) window_flags |= ImGuiWindowFlags_NoTitleBar;
@ -328,6 +329,7 @@ void ImGui::ShowDemoWindow(bool* p_open)
if (no_nav) window_flags |= ImGuiWindowFlags_NoNav; if (no_nav) window_flags |= ImGuiWindowFlags_NoNav;
if (no_background) window_flags |= ImGuiWindowFlags_NoBackground; if (no_background) window_flags |= ImGuiWindowFlags_NoBackground;
if (no_bring_to_front) window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus; if (no_bring_to_front) window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus;
if (unsaved_document) window_flags |= ImGuiWindowFlags_UnsavedDocument;
if (no_close) p_open = NULL; // Don't pass our bool* to Begin if (no_close) p_open = NULL; // Don't pass our bool* to Begin
// We specify a default position/size in case there's no data in the .ini file. // We specify a default position/size in case there's no data in the .ini file.
@ -509,6 +511,7 @@ void ImGui::ShowDemoWindow(bool* p_open)
ImGui::TableNextColumn(); ImGui::Checkbox("No nav", &no_nav); ImGui::TableNextColumn(); ImGui::Checkbox("No nav", &no_nav);
ImGui::TableNextColumn(); ImGui::Checkbox("No background", &no_background); ImGui::TableNextColumn(); ImGui::Checkbox("No background", &no_background);
ImGui::TableNextColumn(); ImGui::Checkbox("No bring to front", &no_bring_to_front); ImGui::TableNextColumn(); ImGui::Checkbox("No bring to front", &no_bring_to_front);
ImGui::TableNextColumn(); ImGui::Checkbox("Unsaved document", &unsaved_document);
ImGui::EndTable(); ImGui::EndTable();
} }
} }
@ -7476,6 +7479,16 @@ void ShowExampleAppDocuments(bool* p_open)
ImGui::Separator(); ImGui::Separator();
// About the ImGuiWindowFlags_UnsavedDocument / ImGuiTabItemFlags_UnsavedDocument flags.
// They have multiple effects:
// - Display a dot next to the title.
// - Tab is selected when clicking the X close button.
// - Closure is not assumed (will wait for user to stop submitting the tab).
// Otherwise closure is assumed when pressing the X, so if you keep submitting the tab may reappear at end of tab bar.
// We need to assume closure by default otherwise waiting for "lack of submission" on the next frame would leave an empty
// hole for one-frame, both in the tab-bar and in tab-contents when closing a tab/window.
// The rarely used SetTabItemClosed() function is a way to notify of programmatic closure to avoid the one-frame hole.
// Submit Tab Bar and Tabs // Submit Tab Bar and Tabs
{ {
ImGuiTabBarFlags tab_bar_flags = (opt_fitting_flags) | (opt_reorderable ? ImGuiTabBarFlags_Reorderable : 0); ImGuiTabBarFlags tab_bar_flags = (opt_fitting_flags) | (opt_reorderable ? ImGuiTabBarFlags_Reorderable : 0);

View File

@ -8077,14 +8077,7 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb,
#endif #endif
// Render text label (with clipping + alpha gradient) + unsaved marker // Render text label (with clipping + alpha gradient) + unsaved marker
const char* TAB_UNSAVED_MARKER = "*";
ImRect text_pixel_clip_bb(bb.Min.x + frame_padding.x, bb.Min.y + frame_padding.y, bb.Max.x - frame_padding.x, bb.Max.y); ImRect text_pixel_clip_bb(bb.Min.x + frame_padding.x, bb.Min.y + frame_padding.y, bb.Max.x - frame_padding.x, bb.Max.y);
if (flags & ImGuiTabItemFlags_UnsavedDocument)
{
text_pixel_clip_bb.Max.x -= CalcTextSize(TAB_UNSAVED_MARKER, NULL, false).x;
ImVec2 unsaved_marker_pos(ImMin(bb.Min.x + frame_padding.x + label_size.x + 2, text_pixel_clip_bb.Max.x), bb.Min.y + frame_padding.y + IM_FLOOR(-g.FontSize * 0.25f));
RenderTextClippedEx(draw_list, unsaved_marker_pos, bb.Max - frame_padding, TAB_UNSAVED_MARKER, NULL, NULL);
}
ImRect text_ellipsis_clip_bb = text_pixel_clip_bb; ImRect text_ellipsis_clip_bb = text_pixel_clip_bb;
// Return clipped state ignoring the close button // Return clipped state ignoring the close button
@ -8094,7 +8087,10 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb,
//draw_list->AddCircle(text_ellipsis_clip_bb.Min, 3.0f, *out_text_clipped ? IM_COL32(255, 0, 0, 255) : IM_COL32(0, 255, 0, 255)); //draw_list->AddCircle(text_ellipsis_clip_bb.Min, 3.0f, *out_text_clipped ? IM_COL32(255, 0, 0, 255) : IM_COL32(0, 255, 0, 255));
} }
// Close Button const float button_sz = g.FontSize;
const ImVec2 button_pos(ImMax(bb.Min.x, bb.Max.x - frame_padding.x * 2.0f - button_sz), bb.Min.y);
// 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() // We are relying on a subtle and confusing distinction between 'hovered' and 'g.HoveredId' which happens because we are using ImGuiButtonFlags_AllowOverlapMode + SetItemAllowOverlap()
// 'hovered' will be true when hovering the Tab but NOT when hovering the close button // 'hovered' will be true when hovering the Tab but NOT when hovering the close button
// 'g.HoveredId==id' will be true when hovering the Tab including when hovering the close button // 'g.HoveredId==id' will be true when hovering the Tab including when hovering the close button
@ -8102,15 +8098,16 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb,
bool close_button_pressed = false; bool close_button_pressed = false;
bool close_button_visible = false; bool close_button_visible = false;
if (close_button_id != 0) if (close_button_id != 0)
if (is_contents_visible || bb.GetWidth() >= g.Style.TabMinWidthForCloseButton) if (is_contents_visible || bb.GetWidth() >= ImMax(button_sz, g.Style.TabMinWidthForCloseButton))
if (g.HoveredId == tab_id || g.HoveredId == close_button_id || g.ActiveId == tab_id || g.ActiveId == close_button_id) if (g.HoveredId == tab_id || g.HoveredId == close_button_id || g.ActiveId == tab_id || g.ActiveId == close_button_id)
close_button_visible = true; close_button_visible = true;
bool unsaved_marker_visible = (flags & ImGuiTabItemFlags_UnsavedDocument) != 0 && (button_pos.x + button_sz <= bb.Max.x);
if (close_button_visible) if (close_button_visible)
{ {
ImGuiLastItemDataBackup last_item_backup; ImGuiLastItemDataBackup last_item_backup;
const float close_button_sz = g.FontSize;
PushStyleVar(ImGuiStyleVar_FramePadding, frame_padding); PushStyleVar(ImGuiStyleVar_FramePadding, frame_padding);
if (CloseButton(close_button_id, ImVec2(bb.Max.x - frame_padding.x * 2.0f - close_button_sz, bb.Min.y))) if (CloseButton(close_button_id, button_pos))
close_button_pressed = true; close_button_pressed = true;
PopStyleVar(); PopStyleVar();
last_item_backup.Restore(); last_item_backup.Restore();
@ -8118,12 +8115,23 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb,
// Close with middle mouse button // Close with middle mouse button
if (!(flags & ImGuiTabItemFlags_NoCloseWithMiddleMouseButton) && IsMouseClicked(2)) if (!(flags & ImGuiTabItemFlags_NoCloseWithMiddleMouseButton) && IsMouseClicked(2))
close_button_pressed = true; close_button_pressed = true;
}
text_pixel_clip_bb.Max.x -= close_button_sz; else if (unsaved_marker_visible)
{
const ImRect bullet_bb(button_pos, button_pos + ImVec2(button_sz, button_sz) + g.Style.FramePadding * 2.0f);
RenderBullet(bullet_bb.GetCenter());
} }
// This is all rather complicated
// (the main idea is that because the close button only appears on hover, we don't want it to alter the ellipsis position)
// FIXME: if FramePadding is noticeably large, ellipsis_max_x will be wrong here (e.g. #3497), maybe for consistency that parameter of RenderTextEllipsis() shouldn't exist.. // FIXME: if FramePadding is noticeably large, ellipsis_max_x will be wrong here (e.g. #3497), maybe for consistency that parameter of RenderTextEllipsis() shouldn't exist..
float ellipsis_max_x = close_button_visible ? text_pixel_clip_bb.Max.x : bb.Max.x - 1.0f; float ellipsis_max_x = close_button_visible ? text_pixel_clip_bb.Max.x : bb.Max.x - 1.0f;
if (close_button_visible || unsaved_marker_visible)
{
text_pixel_clip_bb.Max.x -= close_button_visible ? (button_sz) : (button_sz * 0.80f);
text_ellipsis_clip_bb.Max.x -= unsaved_marker_visible ? (button_sz * 0.80f) : 0.0f;
ellipsis_max_x = text_pixel_clip_bb.Max.x;
}
RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, text_pixel_clip_bb.Max.x, ellipsis_max_x, label, NULL, &label_size); RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, text_pixel_clip_bb.Max.x, ellipsis_max_x, label, NULL, &label_size);
#if 0 #if 0