Menus: rework to allow for an icon column (not yet exposed, but usable via internals) + fix menus being affected by style.SelectableTextAlign (#126)

This commit is contained in:
ocornut 2021-07-08 14:44:32 +02:00
parent 30d164eb26
commit f8fae02270
3 changed files with 62 additions and 24 deletions

View File

@ -53,6 +53,7 @@ Other Changes:
- 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) - TabBar: Fixed using more than 32 KB-worth of tab names. (#4176)
- Drag and Drop: drop target highlight doesn't try to bypass host clipping rectangle. (#4281, #3272) - Drag and Drop: drop target highlight doesn't try to bypass host clipping rectangle. (#4281, #3272)
- Menus: MenuItem() and BeginMenu() are not affected/overlapping when style.SelectableTextAlign is altered.
- Fixed printf-style format checks on non-MinGW flavors. (#4183, #3592) - Fixed printf-style format checks on non-MinGW flavors. (#4183, #3592)
- Fonts: Functions with a 'float size_pixels' parameter can accept zero if it is set in ImFontSize::SizePixels. - Fonts: Functions with a 'float size_pixels' parameter can accept zero if it is set in ImFontSize::SizePixels.
- Fonts: Prefer using U+FFFD character for fallback instead of '?', if available. (#4269) - Fonts: Prefer using U+FFFD character for fallback instead of '?', if available. (#4269)

View File

@ -1038,13 +1038,15 @@ struct IMGUI_API ImGuiMenuColumns
ImU32 TotalWidth; ImU32 TotalWidth;
ImU32 NextTotalWidth; ImU32 NextTotalWidth;
ImU16 Spacing; ImU16 Spacing;
ImU16 OffsetShortcut; // Offsets are locked in Update() ImU16 OffsetIcon; // Always zero for now
ImU16 OffsetLabel; // Offsets are locked in Update()
ImU16 OffsetShortcut;
ImU16 OffsetMark; ImU16 OffsetMark;
ImU16 Widths[3]; // Width of: Label, Shortcut, Mark (accumulators for current frame) ImU16 Widths[4]; // Width of: Icon, Label, Shortcut, Mark (accumulators for current frame)
ImGuiMenuColumns() { memset(this, 0, sizeof(*this)); } ImGuiMenuColumns() { memset(this, 0, sizeof(*this)); }
void Update(float spacing, bool window_reappearing); void Update(float spacing, bool window_reappearing);
float DeclColumns(float w_label, float w_shortcut, float w_mark); float DeclColumns(float w_icon, float w_label, float w_shortcut, float w_mark);
void CalcNextTotalWidth(bool update_offsets); void CalcNextTotalWidth(bool update_offsets);
}; };
@ -2440,6 +2442,9 @@ namespace ImGui
IMGUI_API ImVec2 FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_outer, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy); IMGUI_API ImVec2 FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_outer, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy);
IMGUI_API bool BeginViewportSideBar(const char* name, ImGuiViewport* viewport, ImGuiDir dir, float size, ImGuiWindowFlags window_flags); IMGUI_API bool BeginViewportSideBar(const char* name, ImGuiViewport* viewport, ImGuiDir dir, float size, ImGuiWindowFlags window_flags);
// Menus
IMGUI_API bool MenuItemEx(const char* label, const char* icon, const char* shortcut = NULL, bool selected = false, bool enabled = true);
// Combos // Combos
IMGUI_API bool BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags flags); IMGUI_API bool BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags flags);
IMGUI_API bool BeginComboPreview(); IMGUI_API bool BeginComboPreview();

View File

@ -6614,19 +6614,21 @@ void ImGuiMenuColumns::CalcNextTotalWidth(bool update_offsets)
want_spacing |= (width > 0); want_spacing |= (width > 0);
if (update_offsets) if (update_offsets)
{ {
if (i == 1) { OffsetShortcut = offset; } if (i == 1) { OffsetLabel = offset; }
if (i == 2) { OffsetMark = offset; } if (i == 2) { OffsetShortcut = offset; }
if (i == 3) { OffsetMark = offset; }
} }
offset += width; offset += width;
} }
NextTotalWidth = offset; NextTotalWidth = offset;
} }
float ImGuiMenuColumns::DeclColumns(float w_label, float w_shortcut, float w_mark) float ImGuiMenuColumns::DeclColumns(float w_icon, float w_label, float w_shortcut, float w_mark)
{ {
Widths[0] = ImMax(Widths[0], (ImU16)w_label); Widths[0] = ImMax(Widths[0], (ImU16)w_icon);
Widths[1] = ImMax(Widths[1], (ImU16)w_shortcut); Widths[1] = ImMax(Widths[1], (ImU16)w_label);
Widths[2] = ImMax(Widths[2], (ImU16)w_mark); Widths[2] = ImMax(Widths[2], (ImU16)w_shortcut);
Widths[3] = ImMax(Widths[3], (ImU16)w_mark);
CalcNextTotalWidth(false); CalcNextTotalWidth(false);
return (float)ImMax(TotalWidth, NextTotalWidth); return (float)ImMax(TotalWidth, NextTotalWidth);
} }
@ -6818,6 +6820,10 @@ bool ImGui::BeginMenu(const char* label, bool enabled)
// However the final position is going to be different! It is chosen by FindBestWindowPosForPopup(). // However the final position is going to be different! It is chosen by FindBestWindowPosForPopup().
// e.g. Menus tend to overlap each other horizontally to amplify relative Z-ordering. // e.g. Menus tend to overlap each other horizontally to amplify relative Z-ordering.
ImVec2 popup_pos, pos = window->DC.CursorPos; ImVec2 popup_pos, pos = window->DC.CursorPos;
PushID(label);
if (!enabled)
PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]);
const ImGuiMenuColumns* offsets = &window->DC.MenuColumns;
if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
{ {
// Menu inside an horizontal menu bar // Menu inside an horizontal menu bar
@ -6827,7 +6833,9 @@ bool ImGui::BeginMenu(const char* label, bool enabled)
window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f); window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f);
PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y)); PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y));
float w = label_size.x; float w = label_size.x;
pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f)); ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
pressed = Selectable("", menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));
RenderText(text_pos, label);
PopStyleVar(); PopStyleVar();
window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
} }
@ -6837,12 +6845,18 @@ bool ImGui::BeginMenu(const char* label, bool enabled)
// (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f. // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f.
// Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system. // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system.
popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y); popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y);
float min_w = window->DC.MenuColumns.DeclColumns(label_size.x, 0.0f, IM_FLOOR(g.FontSize * 1.20f)); // Feedback to next frame float icon_w = 0.0f; // FIXME: This not currently exposed for BeginMenu() however you can call window->DC.MenuColumns.DeclColumns(w, 0, 0, 0) yourself
float checkmark_w = IM_FLOOR(g.FontSize * 1.20f);
float min_w = window->DC.MenuColumns.DeclColumns(icon_w, label_size.x, 0.0f, checkmark_w); // Feedback to next frame
float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w); float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w);
pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_SpanAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(min_w, 0.0f)); ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
ImU32 text_col = GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled); pressed = Selectable("", menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_SpanAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(min_w, 0.0f));
RenderArrow(window->DrawList, pos + ImVec2(window->DC.MenuColumns.OffsetMark + extra_w + g.FontSize * 0.30f, 0.0f), text_col, ImGuiDir_Right); RenderText(text_pos, label);
RenderArrow(window->DrawList, pos + ImVec2(offsets->OffsetMark + extra_w + g.FontSize * 0.30f, 0.0f), GetColorU32(ImGuiCol_Text), ImGuiDir_Right);
} }
if (!enabled)
PopStyleColor();
PopID();
const bool hovered = enabled && ItemHoverable(window->DC.LastItemRect, id); const bool hovered = enabled && ItemHoverable(window->DC.LastItemRect, id);
if (menuset_is_open) if (menuset_is_open)
@ -6958,7 +6972,7 @@ void ImGui::EndMenu()
EndPopup(); EndPopup();
} }
bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled) bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut, bool selected, bool enabled)
{ {
ImGuiWindow* window = GetCurrentWindow(); ImGuiWindow* window = GetCurrentWindow();
if (window->SkipItems) if (window->SkipItems)
@ -6973,6 +6987,10 @@ bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, boo
// but I am unsure whether this should be kept at all. For now moved it to be an opt-in feature used by menus only. // but I am unsure whether this should be kept at all. For now moved it to be an opt-in feature used by menus only.
ImGuiSelectableFlags flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_SetNavIdOnHover | (enabled ? 0 : ImGuiSelectableFlags_Disabled); ImGuiSelectableFlags flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_SetNavIdOnHover | (enabled ? 0 : ImGuiSelectableFlags_Disabled);
bool pressed; bool pressed;
PushID(label);
if (!enabled)
PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]);
const ImGuiMenuColumns* offsets = &window->DC.MenuColumns;
if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
{ {
// Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful
@ -6980,8 +6998,9 @@ bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, boo
float w = label_size.x; float w = label_size.x;
window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f); window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f);
PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y)); PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y));
pressed = Selectable(label, selected, flags, ImVec2(w, 0.0f)); pressed = Selectable("", selected, flags, ImVec2(w, 0.0f));
PopStyleVar(); PopStyleVar();
RenderText(pos + ImVec2(offsets->OffsetLabel, 0.0f), label);
window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
} }
else else
@ -6989,27 +7008,40 @@ bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, boo
// Menu item inside a vertical menu // Menu item inside a vertical menu
// (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f. // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f.
// Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system. // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system.
float shortcut_w = shortcut ? CalcTextSize(shortcut, NULL).x : 0.0f; float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f;
float min_w = window->DC.MenuColumns.DeclColumns(label_size.x, shortcut_w, IM_FLOOR(g.FontSize * 1.20f)); // Feedback for next frame float shortcut_w = (shortcut && shortcut[0]) ? CalcTextSize(shortcut, NULL).x : 0.0f;
float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w); float checkmark_w = IM_FLOOR(g.FontSize * 1.20f);
pressed = Selectable(label, false, flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, 0.0f)); float min_w = window->DC.MenuColumns.DeclColumns(icon_w, label_size.x, shortcut_w, checkmark_w); // Feedback for next frame
float stretch_w = ImMax(0.0f, GetContentRegionAvail().x - min_w);
pressed = Selectable("", false, flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, 0.0f));
RenderText(pos + ImVec2(offsets->OffsetLabel, 0.0f), label);
if (icon_w > 0.0f)
RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon);
if (shortcut_w > 0.0f) if (shortcut_w > 0.0f)
{ {
PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]);
RenderText(pos + ImVec2(window->DC.MenuColumns.OffsetShortcut + extra_w, 0.0f), shortcut, NULL, false); RenderText(pos + ImVec2(offsets->OffsetShortcut + stretch_w, 0.0f), shortcut, NULL, false);
PopStyleColor(); PopStyleColor();
} }
if (selected) if (selected)
RenderCheckMark(window->DrawList, pos + ImVec2(window->DC.MenuColumns.OffsetMark + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), g.FontSize * 0.866f); RenderCheckMark(window->DrawList, pos + ImVec2(offsets->OffsetMark + stretch_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(ImGuiCol_Text), g.FontSize * 0.866f);
} }
if (!enabled)
PopStyleColor();
PopID();
IMGUI_TEST_ENGINE_ITEM_INFO(window->DC.LastItemId, label, window->DC.LastItemStatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0)); IMGUI_TEST_ENGINE_ITEM_INFO(window->DC.LastItemId, label, window->DC.LastItemStatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0));
return pressed; return pressed;
} }
bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled)
{
return MenuItemEx(label, NULL, shortcut, selected, enabled);
}
bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled) bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled)
{ {
if (MenuItem(label, shortcut, p_selected ? *p_selected : false, enabled)) if (MenuItemEx(label, NULL, shortcut, p_selected ? *p_selected : false, enabled))
{ {
if (p_selected) if (p_selected)
*p_selected = !*p_selected; *p_selected = !*p_selected;