From 5b688c5824beb77032b86afa13bf1d785688a8b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= Date: Wed, 27 May 2015 10:51:42 -0700 Subject: [PATCH] Updated imgui. --- 3rdparty/ocornut-imgui/imgui.cpp | 1305 ++++++++++++++++++++++-------- 3rdparty/ocornut-imgui/imgui.h | 184 +++-- 2 files changed, 1057 insertions(+), 432 deletions(-) diff --git a/3rdparty/ocornut-imgui/imgui.cpp b/3rdparty/ocornut-imgui/imgui.cpp index 0971cf89..cdf1ff8b 100644 --- a/3rdparty/ocornut-imgui/imgui.cpp +++ b/3rdparty/ocornut-imgui/imgui.cpp @@ -13,7 +13,7 @@ - API BREAKING CHANGES (read me when you update!) - FREQUENTLY ASKED QUESTIONS (FAQ), TIPS - How do I update to a newer version of ImGui? - - Can I have multiple widgets with the same label? (Yes) + - Can I have multiple widgets with the same label? Can I have widget without a label? (Yes) - Why is my text output blurry? - How can I load a different font than the default? - How can I load multiple fonts? @@ -54,7 +54,7 @@ - TAB/SHIFT+TAB to cycle through keyboard editable fields - use mouse wheel to scroll - use CTRL+mouse wheel to zoom window contents (if IO.FontAllowScaling is true) - - CTRL+Click on a slider to input value as text + - CTRL+Click on a slider or drag box to input value as text - text editor: - Hold SHIFT or use mouse to select text. - CTRL+Left/Right to word jump @@ -136,6 +136,8 @@ Occasionally introducing changes that are breaking the API. The breakage are generally minor and easy to fix. Here is a change-log of API breaking changes, if you are using one of the functions listed, expect to have to fix some code. + - 2015/05/27 (1.39) - removed the third 'repeat_if_held' parameter from Button() - sorry! it was rarely used and inconsistent. Use PushButtonRepeat(true) / PopButtonRepeat() to enable repeat on desired buttons. + - 2015/05/11 (1.39) - changed BeginPopup() API, takes a string identifier instead of a bool. ImGui needs to manage the open/closed state of popups. Call OpenPopup() to actually set the "opened" state of a popup. BeginPopup() returns true if the popup is opened. - 2015/05/03 (1.39) - removed style.AutoFitPadding, using style.WindowPadding makes more sense (the default values were already the same). - 2015/04/13 (1.38) - renamed IsClipped() to IsRectClipped(). Kept inline redirection function (will obsolete). - 2015/04/09 (1.38) - renamed ImDrawList::AddArc() to ImDrawList::AddArcFast() for compatibility with future API @@ -196,38 +198,42 @@ Check the "API BREAKING CHANGES" sections for a list of occasional API breaking changes. If you have a problem with a function, search for its name in the code, there will likely be a comment about it. Please report any issue to the GitHub page! - Q: Can I have multiple widgets with the same label? + Q: Can I have multiple widgets with the same label? Can I have widget without a label? (Yes) A: Yes. A primer on the use of labels/IDs in ImGui.. + - Elements that are not clickable, such as Text() items don't need an ID. + - Interactive widgets require state to be carried over multiple frames (most typically ImGui often needs to remember what is the "active" widget). to do so they need an unique ID. unique ID are typically derived from a string label, an integer index or a pointer. Button("OK"); // Label = "OK", ID = hash of "OK" Button("Cancel"); // Label = "Cancel", ID = hash of "Cancel" - - Elements that are not clickable, such as Text() items don't need an ID. - - ID are uniquely scoped within windows, tree nodes, etc. so no conflict can happen if you have two buttons called "OK" in two different windows or in two different locations of a tree. - if you have a same ID twice in the same location, you'll have a conflict: Button("OK"); - Button("OK"); // ID collision! Both buttons will be treated as the same. + Button("OK"); // ID collision! Both buttons will be treated as the same. Fear not! this is easy to solve and there are many ways to solve it! - when passing a label you can optionally specify extra unique ID information within string itself. This helps solving the simpler collision cases. use "##" to pass a complement to the ID that won't be visible to the end-user: - Button("Play##0"); // Label = "Play", ID = hash of "Play##0" - Button("Play##1"); // Label = "Play", ID = hash of "Play##1" (different from above) + Button("Play##0"); // Label = "Play", ID = hash of "Play##0" + Button("Play##1"); // Label = "Play", ID = hash of "Play##1" (different from above) + + - so if you want to hide the label but need an ID: + + Checkbox("##On", &b); // Label = "", ID = hash of "##On" - occasionally (rarely) you might want change a label while preserving a constant ID. This allows you to animate labels. use "###" to pass a label that isn't part of ID: - Button("Hello###ID"; // Label = "Hello", ID = hash of "ID" - Button("World###ID"; // Label = "World", ID = hash of "ID" (same as above) + Button("Hello###ID"; // Label = "Hello", ID = hash of "ID" + Button("World###ID"; // Label = "World", ID = hash of "ID" (same as above) - use PushID() / PopID() to create scopes and avoid ID conflicts within the same Window. this is the most convenient way of distinguish ID if you are iterating and creating many UI elements. @@ -351,7 +357,8 @@ - combo/listbox: keyboard control. need inputtext like non-active focus + key handling. considering keybord for custom listbox (see github pr #203) - listbox: multiple selection - listbox: user may want to initial scroll to focus on the one selected value? - ! menubar, menus (github issue #126) + - menus: local shortcuts, global shortcuts (github issue #126) + - menus: icons - tabs - gauge: various forms of gauge/loading bars widgets - color: better color editor. @@ -368,6 +375,7 @@ - text edit: field resize behavior - field could stretch when being edited? hover tooltip shows more text? - text edit: add multi-line text edit - tree: add treenode/treepush int variants? because (void*) cast from int warns on some platforms/settings + - tooltip: figure out a way to use TextWrapped() in a tooltip. - settings: write more decent code to allow saving/loading new fields - settings: api for per-tool simple persistent data (bool,int,float,columns sizes,etc.) in .ini file ! style: store rounded corners in texture to use 1 quad per corner (filled and wireframe). so rounding have minor cost. @@ -386,6 +394,7 @@ - input: rework IO to be able to pass actual events to fix temporal aliasing issues. - input: support track pad style scrolling & slider edit. - portability: big-endian test/support (github issue #81) + - memory: add a way to discard allocs of unused/transient windows. with the current architecture new windows (including popup, opened combos, listbox) perform at least 3 allocs. - misc: mark printf compiler attributes on relevant functions - misc: provide a way to compile out the entire implementation while providing a dummy API (e.g. #define IMGUI_DUMMY_IMPL) - misc: double-clicking on title bar to minimize isn't consistent, perhaps move to single-click on left-most collapse icon? @@ -497,14 +506,16 @@ struct ImGuiTextEditState; struct ImGuiIniData; struct ImGuiState; struct ImGuiWindow; -typedef int ImGuiButtonFlags; // enum ImGuiButtonFlags_ +typedef int ImGuiLayoutType; // enum ImGuiLayoutType_ +typedef int ImGuiButtonFlags; // enum ImGuiButtonFlags_ +typedef int ImGuiSelectableFlags; // enum ImGuiSelectableFlags_ static bool ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, bool allow_key_modifiers, ImGuiButtonFlags flags = 0); static void LogText(const ImVec2& ref_pos, const char* text, const char* text_end = NULL); static void RenderText(ImVec2 pos, const char* text, const char* text_end = NULL, bool hide_text_after_hash = true); static void RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width); -static void RenderTextClipped(ImVec2 pos, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& pos_max, const ImVec2* clip_max = NULL, ImGuiAlign align = ImGuiAlign_Default); +static void RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2* clip_max = NULL, ImGuiAlign align = ImGuiAlign_Default); static void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border = true, float rounding = 0.0f); static void RenderCollapseTriangle(ImVec2 p_min, bool opened, float scale = 1.0f, bool shadow = false); static void RenderCheckMark(ImVec2 pos, ImU32 col); @@ -523,6 +534,7 @@ static void Scrollbar(ImGuiWindow* window); static bool CloseWindowButton(bool* p_opened = NULL); static void FocusWindow(ImGuiWindow* window); static ImGuiWindow* FindHoveredWindow(ImVec2 pos, bool excluding_childs); +static void CloseInactivePopups(); // Helpers: String static int ImStricmp(const char* str1, const char* str2); @@ -582,6 +594,7 @@ ImGuiStyle::ImGuiStyle() DisplaySafeAreaPadding = ImVec2(4,4); // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows. Colors[ImGuiCol_Text] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); + Colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); Colors[ImGuiCol_WindowBg] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); Colors[ImGuiCol_ChildWindowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); Colors[ImGuiCol_Border] = ImVec4(0.70f, 0.70f, 0.70f, 0.65f); @@ -591,6 +604,7 @@ ImGuiStyle::ImGuiStyle() Colors[ImGuiCol_FrameBgActive] = ImVec4(0.90f, 0.65f, 0.65f, 0.45f); Colors[ImGuiCol_TitleBg] = ImVec4(0.50f, 0.50f, 1.00f, 0.45f); Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.40f, 0.40f, 0.80f, 0.20f); + Colors[ImGuiCol_MenuBarBg] = ImVec4(0.40f, 0.40f, 0.55f, 0.60f); Colors[ImGuiCol_ScrollbarBg] = ImVec4(0.40f, 0.40f, 0.80f, 0.15f); Colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.40f, 0.40f, 0.80f, 0.30f); Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.40f, 0.80f, 0.40f); @@ -643,6 +657,8 @@ ImGuiIO::ImGuiIO() MouseDoubleClickTime = 0.30f; MouseDoubleClickMaxDist = 6.0f; MouseDragThreshold = 6.0f; + KeyRepeatDelay = 0.250f; + KeyRepeatRate = 0.020f; UserData = NULL; // User functions @@ -703,6 +719,7 @@ static inline ImVec2& operator+=(ImVec2& lhs, const ImVec2& rhs) static inline ImVec2& operator-=(ImVec2& lhs, const ImVec2& rhs) { lhs.x -= rhs.x; lhs.y -= rhs.y; return lhs; } static inline ImVec2& operator*=(ImVec2& lhs, const float rhs) { lhs.x *= rhs; lhs.y *= rhs; return lhs; } //static inline ImVec2& operator/=(ImVec2& lhs, const float rhs) { lhs.x /= rhs; lhs.y /= rhs; return lhs; } +static inline ImVec4 operator-(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x-rhs.x, lhs.y-rhs.y, lhs.z-rhs.z, lhs.w-lhs.w); } static inline int ImMin(int lhs, int rhs) { return lhs < rhs ? lhs : rhs; } static inline int ImMax(int lhs, int rhs) { return lhs >= rhs ? lhs : rhs; } @@ -717,6 +734,15 @@ static inline float ImSaturate(float f) static inline float ImLerp(float a, float b, float t) { return a + (b - a) * t; } static inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, const ImVec2& t) { return ImVec2(a.x + (b.x - a.x) * t.x, a.y + (b.y - a.y) * t.y); } static inline float ImLengthSqr(const ImVec2& lhs) { return lhs.x*lhs.x + lhs.y*lhs.y; } +static inline float ImLengthSqr(const ImVec4& lhs) { return lhs.x*lhs.x + lhs.y*lhs.y + lhs.z*lhs.z + lhs.w*lhs.w; } + +static bool ImIsPointInTriangle(const ImVec2& p, const ImVec2& a, const ImVec2& b, const ImVec2& c) +{ + bool b1 = ((p.x - b.x) * (a.y - b.y) - (p.y - b.y) * (a.x - b.x)) < 0.0f; + bool b2 = ((p.x - c.x) * (b.y - c.y) - (p.y - c.y) * (b.x - c.x)) < 0.0f; + bool b3 = ((p.x - a.x) * (c.y - a.y) - (p.y - a.y) * (c.x - a.x)) < 0.0f; + return ((b1 == b2) && (b2 == b3)); +} static int ImStricmp(const char* str1, const char* str2) { @@ -935,13 +961,29 @@ static bool ImLoadFileToMemory(const char* filename, const char* file_open_mode, //----------------------------------------------------------------------------- +enum ImGuiLayoutType_ +{ + ImGuiLayoutType_Vertical, + ImGuiLayoutType_Horizontal +}; + enum ImGuiButtonFlags_ { - ImGuiButtonFlags_Repeat = (1 << 0), - ImGuiButtonFlags_PressedOnClick = (1 << 1), - ImGuiButtonFlags_FlattenChilds = (1 << 2) + ImGuiButtonFlags_Repeat = (1 << 0), + ImGuiButtonFlags_PressedOnClick = (1 << 1), + ImGuiButtonFlags_FlattenChilds = (1 << 2), + ImGuiButtonFlags_DontClosePopups = (1 << 3), + ImGuiButtonFlags_Disabled = (1 << 4) }; +enum ImGuiSelectableFlags_ +{ + ImGuiSelectableFlags_MenuItem = (1 << 0), + ImGuiSelectableFlags_DontClosePopups = (1 << 1), + ImGuiSelectableFlags_Disabled = (1 << 2) +}; + + struct ImGuiColMod // Color modifier, backup of modified data so we can restore it { ImGuiCol Col; @@ -1002,6 +1044,48 @@ struct ImGuiGroupData float BackupCurrentLineHeight; float BackupCurrentLineTextBaseOffset; float BackupLogLinePosY; + bool AdvanceCursor; +}; + +// Simple column measurement currently used for menu items. This is very short-sighted for now. +struct ImGuiSimpleColumns +{ + int Count; + float Spacing; + float Width, NextWidth; + float Pos[8], NextWidths[8]; + + ImGuiSimpleColumns() { Count = 0; Spacing = 0.0f; Width = 0.0f; NextWidth = 0.0f; memset(Pos, 0, sizeof(Pos)); memset(NextWidths, 0, sizeof(NextWidths)); } + void Update(int count, float spacing, bool clear) + { + IM_ASSERT(Count <= IM_ARRAYSIZE(Pos)); + Count = count; + Width = NextWidth = 0.0f; + Spacing = spacing; + if (clear) memset(NextWidths, 0, sizeof(NextWidths)); + for (int i = 0; i < Count; i++) + { + if (i > 0 && NextWidths[i] > 0.0f) + Width += Spacing; + Pos[i] = (float)(int)Width; + Width += NextWidths[i]; + NextWidths[i] = 0.0f; + } + } + float DeclColumns(float w0, float w1, float w2) // not using va_arg because they promote float to double + { + NextWidth = 0.0f; + NextWidths[0] = ImMax(NextWidths[0], w0); + NextWidths[1] = ImMax(NextWidths[1], w1); + NextWidths[2] = ImMax(NextWidths[2], w2); + for (int i = 0; i < 3; i++) + NextWidth += NextWidths[i] + ((i > 0 && NextWidths[i] > 0.0f) ? Spacing : 0.0f); + return ImMax(Width, NextWidth); + } + float CalcExtraSpace(float avail_w) + { + return ImMax(0.0f, avail_w - Width); + } }; // Temporary per-window data, reset at the beginning of the frame @@ -1010,7 +1094,7 @@ struct ImGuiDrawContext ImVec2 CursorPos; ImVec2 CursorPosPrevLine; ImVec2 CursorStartPos; - ImVec2 CursorMaxPos; // Implicitly calculate the size of our contents, always extending. Saved into window->SizeContents at the end of the frame + ImVec2 CursorMaxPos; // Implicitly calculate the size of our contents, always extending. Saved into window->SizeContents at the end of the frame float CurrentLineHeight; float CurrentLineTextBaseOffset; float PrevLineHeight; @@ -1021,17 +1105,27 @@ struct ImGuiDrawContext ImRect LastItemRect; bool LastItemHoveredAndUsable; bool LastItemHoveredRect; + bool MenuBarAppending; + float MenuBarOffsetX; ImVector ChildWindows; - ImVector AllowKeyboardFocus; - ImVector ItemWidth; // 0.0: default, >0.0: width in pixels, <0.0: align xx pixels to the right of window - ImVector TextWrapPos; - ImVector GroupStack; - ImGuiColorEditMode ColorEditMode; ImGuiStorage* StateStorage; - int StackSizesBackup[5]; // store size of various stacks for asserting + ImGuiLayoutType LayoutType; - float ColumnsStartX; // Indentation / start position from left of window (increased by TreePush/TreePop, etc.) - float ColumnsOffsetX; // Offset to the current column (if ColumnsCurrent > 0). FIXME: This and the above should be a stack to allow use cases like Tree->Column->Tree. Need revamp columns API. + // We store the current settings outside of the vectors to increase memory locality (reduce cache misses). The vectors are rarely modified. Also it allows us to not heap allocate for short-lived windows which are not using those settings. + bool ButtonRepeat; // == ButtonRepeatStack.back() [empty == false] + bool AllowKeyboardFocus; // == AllowKeyboardFocusStack.back() [empty == true] + float ItemWidth; // == ItemWidthStack.back(). 0.0: default, >0.0: width in pixels, <0.0: align xx pixels to the right of window + float TextWrapPos; // == TextWrapPosStack.back() [empty == -1.0f] + ImVector ButtonRepeatStack; + ImVector AllowKeyboardFocusStack; + ImVector ItemWidthStack; + ImVector TextWrapPosStack; + ImVectorGroupStack; + ImGuiColorEditMode ColorEditMode; + int StackSizesBackup[6]; // Store size of various stacks for asserting + + float ColumnsStartX; // Indentation / start position from left of window (increased by TreePush/TreePop, etc.) + float ColumnsOffsetX; // Offset to the current column (if ColumnsCurrent > 0). FIXME: This and the above should be a stack to allow use cases like Tree->Column->Tree. Need revamp columns API. int ColumnsCurrent; int ColumnsCount; ImVec2 ColumnsStartPos; @@ -1039,7 +1133,7 @@ struct ImGuiDrawContext float ColumnsCellMaxY; bool ColumnsShowBorders; ImGuiID ColumnsSetID; - ImVector ColumnsOffsetsT; // Columns offset normalized 0.0 (far left) -> 1.0 (far right) + ImVector ColumnsOffsetsT; // Columns offset normalized 0.0 (far left) -> 1.0 (far right) ImGuiDrawContext() { @@ -1051,8 +1145,11 @@ struct ImGuiDrawContext LastItemID = 0; LastItemRect = ImRect(0.0f,0.0f,0.0f,0.0f); LastItemHoveredAndUsable = LastItemHoveredRect = false; - ColorEditMode = ImGuiColorEditMode_RGB; + MenuBarAppending = false; + MenuBarOffsetX = 0.0f; StateStorage = NULL; + LayoutType = ImGuiLayoutType_Vertical; + ColorEditMode = ImGuiColorEditMode_RGB; memset(StackSizesBackup, 0, sizeof(StackSizesBackup)); ColumnsStartX = 0.0f; @@ -1108,9 +1205,6 @@ struct ImGuiIniData ImVec2 Pos; ImVec2 Size; bool Collapsed; - - ImGuiIniData() { memset(this, 0, sizeof(*this)); } - ~ImGuiIniData() { if (Name) { ImGui::MemFree(Name); Name = NULL; } } }; struct ImGuiMouseCursorData @@ -1122,6 +1216,16 @@ struct ImGuiMouseCursorData ImVec2 TexUvMax[2]; }; +struct ImGuiPopupRef +{ + ImGuiID PopupID; // Set on OpenPopup() + ImGuiWindow* Window; // Resolved on BeginPopup() - may stay unresolved if user never calls OpenPopup() + ImGuiWindow* ParentWindow; // Set on OpenPopup() + ImGuiID ParentMenuSet; // Set on OpenPopup() + + ImGuiPopupRef(ImGuiID id, ImGuiWindow* parent_window, ImGuiID parent_menu_set) { PopupID = id; Window = NULL; ParentWindow = parent_window; ParentMenuSet = parent_menu_set; } +}; + // Main state for ImGui struct ImGuiState { @@ -1140,11 +1244,11 @@ struct ImGuiState ImVector WindowsSortBuffer; ImGuiWindow* CurrentWindow; // Being drawn into ImVector CurrentWindowStack; - int CurrentPopupStackSize; ImGuiWindow* FocusedWindow; // Will catch keyboard inputs ImGuiWindow* HoveredWindow; // Will catch mouse inputs ImGuiWindow* HoveredRootWindow; // Will catch mouse inputs (for focus/move only) ImGuiID HoveredId; // Hovered widget + ImGuiID HoveredIdPreviousFrame; ImGuiID ActiveId; // Active widget ImGuiID ActiveIdPreviousFrame; bool ActiveIdIsAlive; @@ -1152,11 +1256,13 @@ struct ImGuiState bool ActiveIdIsFocusedOnly; // Set only by active widget. Denote focus but no active interaction. ImGuiWindow* MovedWindow; // Track the child window we clicked on to move a window. Only valid if ActiveID is the "#MOVE" identifier of a window. float SettingsDirtyTimer; - ImVector Settings; + ImVector Settings; int DisableHideTextAfterDoubleHash; ImVector ColorModifiers; ImVector StyleModifiers; ImVector FontStack; + ImVector OpenedPopupStack; // Which popups are open + ImVector CurrentPopupStack; // Which level of BeginPopup() we are in (reset every frame) ImVec2 SetNextWindowPosVal; ImGuiSetCond SetNextWindowPosCond; @@ -1179,7 +1285,7 @@ struct ImGuiState // Widget state ImGuiTextEditState InputTextState; ImGuiID ScalarAsInputTextId; // Temporary text input when CTRL+clicking on a slider, etc. - ImGuiStorage ColorEditModeStorage; // for user selection + ImGuiStorage ColorEditModeStorage; // Store user selection of color edit mode ImGuiID ActiveComboID; ImVec2 ActiveClickDeltaToCenter; float DragCurrentValue; // current dragged value, always float, not rounded by end-user precision settings @@ -1215,11 +1321,11 @@ struct ImGuiState FrameCount = 0; FrameCountRendered = -1; CurrentWindow = NULL; - CurrentPopupStackSize = 0; FocusedWindow = NULL; HoveredWindow = NULL; HoveredRootWindow = NULL; HoveredId = 0; + HoveredIdPreviousFrame = 0; ActiveId = 0; ActiveIdPreviousFrame = 0; ActiveIdIsAlive = false; @@ -1287,6 +1393,8 @@ struct ImGuiWindow bool Accessed; // Set to true when any widget access the current window bool Collapsed; // Set when collapsing window to become only title-bar bool SkipItems; // == Visible && !Collapsed + int BeginCount; // Number of Begin() during the current frame (generally 0 or 1, 1+ if appending via multiple Begin/End pairs) + ImGuiID PopupID; // ID in the popup stack when this window is used as a popup/menu (because we use generic Name/ID for recycling) int AutoFitFrames; bool AutoFitOnlyGrows; int AutoPosLastDirection; @@ -1301,6 +1409,7 @@ struct ImGuiWindow ImRect ClippedRect; // = ClipRectStack.front() after setup in Begin() int LastFrameDrawn; float ItemWidthDefault; + ImGuiSimpleColumns MenuColumns; // Simplified columns storage for menu items ImGuiStorage StateStorage; float FontWindowScale; // Scale multiplier per-window ImDrawList* DrawList; @@ -1326,9 +1435,12 @@ public: ImRect Rect() const { return ImRect(Pos, Pos+Size); } float CalcFontSize() const { return GImGui->FontBaseSize * FontWindowScale; } - float TitleBarHeight() const { return (Flags & ImGuiWindowFlags_NoTitleBar) ? 0 : CalcFontSize() + GImGui->Style.FramePadding.y * 2.0f; } + float TitleBarHeight() const { return (Flags & ImGuiWindowFlags_NoTitleBar) ? 0.0f : CalcFontSize() + GImGui->Style.FramePadding.y * 2.0f; } ImRect TitleBarRect() const { return ImRect(Pos, Pos + ImVec2(SizeFull.x, TitleBarHeight())); } - ImVec2 WindowPadding() const { return ((Flags & ImGuiWindowFlags_ChildWindow) && !(Flags & ImGuiWindowFlags_ShowBorders) && !(Flags & ImGuiWindowFlags_ComboBox)) ? ImVec2(0,0) : GImGui->Style.WindowPadding; } + float MenuBarHeight() const { return (Flags & ImGuiWindowFlags_MenuBar) ? CalcFontSize() + GImGui->Style.FramePadding.y * 2.0f : 0.0f; } + ImRect MenuBarRect() const { float y1 = Pos.y + TitleBarHeight(); return ImRect(Pos.x, y1, Pos.x + SizeFull.x, y1 + MenuBarHeight()); } + ImVec2 WindowPadding() const { return ((Flags & ImGuiWindowFlags_ChildWindow) && !(Flags & (ImGuiWindowFlags_ShowBorders | ImGuiWindowFlags_ComboBox | ImGuiWindowFlags_Popup))) ? ImVec2(0,0) : GImGui->Style.WindowPadding; } + float ScrollbarWidth() const { return ScrollbarY ? GImGui->Style.ScrollbarWidth : 0.0f; } ImU32 Color(ImGuiCol idx, float a=1.f) const { ImVec4 c = GImGui->Style.Colors[idx]; c.w *= GImGui->Style.Alpha * a; return ImGui::ColorConvertFloat4ToU32(c); } ImU32 Color(const ImVec4& col) const { ImVec4 c = col; c.w *= GImGui->Style.Alpha; return ImGui::ColorConvertFloat4ToU32(c); } }; @@ -1451,8 +1563,7 @@ void** ImGuiStorage::GetVoidPtrRef(ImGuiID key, void* default_val) return &it->val_p; } -// FIXME-OPT: Wasting CPU because all SetInt() are preceeded by GetInt() calls so we should have the result from lower_bound already in place. -// However we only use SetInt() on explicit user action (so that's maximum once a frame) so the optimisation isn't much needed. +// FIXME-OPT: Need a way to reuse the result of lower_bound when doing GetInt()/SetInt() - not too bad because it only happens on explicit interaction (maximum one a frame) void ImGuiStorage::SetInt(ImU32 key, int val) { ImVector::iterator it = LowerBound(Data, key); @@ -1646,6 +1757,8 @@ ImGuiWindow::ImGuiWindow(const char* name) Accessed = false; Collapsed = false; SkipItems = false; + BeginCount = 0; + PopupID = 0; AutoFitFrames = -1; AutoFitOnlyGrows = false; AutoPosLastDirection = -1; @@ -1695,7 +1808,7 @@ bool ImGuiWindow::FocusItemRegister(bool is_active, bool tab_stop) ImGuiState& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); - const bool allow_keyboard_focus = window->DC.AllowKeyboardFocus.back(); + const bool allow_keyboard_focus = window->DC.AllowKeyboardFocus; FocusIdxAllCounter++; if (allow_keyboard_focus) FocusIdxTabCounter++; @@ -1741,8 +1854,11 @@ static void AddWindowToRenderList(ImVector& out_render_list, ImGuiW for (size_t i = 0; i < window->DC.ChildWindows.size(); i++) { ImGuiWindow* child = window->DC.ChildWindows[i]; - if (child->Active) // clipped children may have been marked not active - AddWindowToRenderList(out_render_list, child); + if (!child->Active) // clipped children may have been marked not active + continue; + if ((child->Flags & ImGuiWindowFlags_Popup) && child->HiddenFrames > 0) + continue; + AddWindowToRenderList(out_render_list, child); } } @@ -1750,11 +1866,13 @@ static void AddWindowToRenderList(ImVector& out_render_list, ImGuiW void* ImGui::MemAlloc(size_t sz) { + GImGui->IO.MetricsAllocs++; return GImGui->IO.MemAllocFn(sz); } void ImGui::MemFree(void* ptr) { + if (ptr) GImGui->IO.MetricsAllocs--; return GImGui->IO.MemFreeFn(ptr); } @@ -1764,7 +1882,7 @@ static ImGuiIniData* FindWindowSettings(const char* name) ImGuiID id = ImHash(name, 0); for (size_t i = 0; i != g.Settings.size(); i++) { - ImGuiIniData* ini = g.Settings[i]; + ImGuiIniData* ini = &g.Settings[i]; if (ini->ID == id) return ini; } @@ -1773,14 +1891,13 @@ static ImGuiIniData* FindWindowSettings(const char* name) static ImGuiIniData* AddWindowSettings(const char* name) { - ImGuiIniData* ini = (ImGuiIniData*)ImGui::MemAlloc(sizeof(ImGuiIniData)); - new(ini) ImGuiIniData(); + GImGui->Settings.resize(GImGui->Settings.size() + 1); + ImGuiIniData* ini = &GImGui->Settings.back(); ini->Name = ImStrdup(name); ini->ID = ImHash(name, 0); ini->Collapsed = false; ini->Pos = ImVec2(FLT_MAX,FLT_MAX); ini->Size = ImVec2(0,0); - GImGui->Settings.push_back(ini); return ini; } @@ -1858,7 +1975,7 @@ static void SaveSettings() return; for (size_t i = 0; i != g.Settings.size(); i++) { - const ImGuiIniData* settings = g.Settings[i]; + const ImGuiIniData* settings = &g.Settings[i]; if (settings->Pos.x == FLT_MAX) continue; const char* name = settings->Name; @@ -1877,7 +1994,6 @@ static void SaveSettings() static void MarkSettingsDirty() { ImGuiState& g = *GImGui; - if (g.SettingsDirtyTimer <= 0.0f) g.SettingsDirtyTimer = g.IO.IniSavingRate; } @@ -1988,6 +2104,7 @@ void ImGui::NewFrame() g.IO.Framerate = 1.0f / (g.FramerateSecPerFrameAccum / (float)IM_ARRAYSIZE(g.FramerateSecPerFrame)); // Clear reference to active widget if the widget isn't alive anymore + g.HoveredIdPreviousFrame = g.HoveredId; g.HoveredId = 0; if (!g.ActiveIdIsAlive && g.ActiveIdPreviousFrame == g.ActiveId && g.ActiveId != 0) SetActiveId(0); @@ -2082,12 +2199,53 @@ void ImGui::NewFrame() // No window should be open at the beginning of the frame. // But in order to allow the user to call NewFrame() multiple times without calling Render(), we are doing an explicit clear. g.CurrentWindowStack.resize(0); + g.CurrentPopupStack.resize(0); + CloseInactivePopups(); // Create implicit window - we will only render it if the user has added something to it. ImGui::SetNextWindowSize(ImVec2(400,400), ImGuiSetCond_FirstUseEver); ImGui::Begin("Debug"); } +static void CloseInactivePopups() +{ + ImGuiState& g = *GImGui; + if (g.OpenedPopupStack.empty()) + return; + + // User has clicked outside of a popup + if (!g.FocusedWindow) + { + g.OpenedPopupStack.resize(0); + return; + } + + // When popups are stacked, clicking on a lower level popups puts focus back to it and close popups above it + // Don't close our own child popup windows + int n; + for (n = 0; n < (int)g.OpenedPopupStack.size(); n++) + { + ImGuiPopupRef& popup = g.OpenedPopupStack[n]; + if (!popup.Window) + continue; + IM_ASSERT((popup.Window->Flags & ImGuiWindowFlags_Popup) != 0); + if (popup.Window->Flags & ImGuiWindowFlags_ChildWindow) + { + if (g.FocusedWindow->RootWindow != popup.Window->RootWindow) + break; + } + else + { + bool has_focus = false; + for (int m = n; m < (int)g.OpenedPopupStack.size() && !has_focus; m++) + has_focus = (g.OpenedPopupStack[m].Window && g.OpenedPopupStack[m].Window->RootWindow == g.FocusedWindow->RootWindow); + if (!has_focus) + break; + } + } + g.OpenedPopupStack.resize(n); +} + // NB: behavior of ImGui after Shutdown() is not tested/guaranteed at the moment. This function is merely here to free heap allocations. void ImGui::Shutdown() { @@ -2109,14 +2267,13 @@ void ImGui::Shutdown() g.HoveredWindow = NULL; g.HoveredRootWindow = NULL; for (size_t i = 0; i < g.Settings.size(); i++) - { - g.Settings[i]->~ImGuiIniData(); - ImGui::MemFree(g.Settings[i]); - } + ImGui::MemFree(g.Settings[i].Name); g.Settings.clear(); g.ColorModifiers.clear(); g.StyleModifiers.clear(); g.FontStack.clear(); + g.OpenedPopupStack.clear(); + g.CurrentPopupStack.clear(); for (size_t i = 0; i < IM_ARRAYSIZE(g.RenderDrawLists); i++) g.RenderDrawLists[i].clear(); g.MouseCursorDrawList.ClearFreeMemory(); @@ -2480,7 +2637,7 @@ static void RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end } } -static void RenderTextClipped(ImVec2 pos, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& pos_max, const ImVec2* clip_max, ImGuiAlign align) +static void RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2* clip_max, ImGuiAlign align) { // Hide anything after a '##' string const char* text_display_end = FindTextDisplayEnd(text, text_end); @@ -2492,13 +2649,15 @@ static void RenderTextClipped(ImVec2 pos, const char* text, const char* text_end ImGuiWindow* window = GetCurrentWindow(); // Perform CPU side clipping for single clipped element to avoid using scissor state - const ImVec2 text_size = text_size_if_known ? *text_size_if_known : ImGui::CalcTextSize(text, text_display_end, false, 0.0f); if (!clip_max) clip_max = &pos_max; + ImVec2 pos = pos_min; + const ImVec2 text_size = text_size_if_known ? *text_size_if_known : ImGui::CalcTextSize(text, text_display_end, false, 0.0f); const bool need_clipping = (pos.x + text_size.x >= clip_max->x) || (pos.y + text_size.y >= clip_max->y); // Align if (align & ImGuiAlign_Center) pos.x = ImMax(pos.x, (pos.x + pos_max.x - text_size.x) * 0.5f); else if (align & ImGuiAlign_Right) pos.x = ImMax(pos.x, pos_max.x - text_size.x); + if (align & ImGuiAlign_VCenter) pos.y = ImMax(pos.y, (pos.y + pos_max.y - text_size.y) * 0.5f); // Render window->DrawList->AddText(g.Font, g.FontSize, pos, window->Color(ImGuiCol_Text), text, text_display_end, 0.0f, need_clipping ? clip_max : NULL); @@ -2718,12 +2877,12 @@ bool ImGui::IsKeyPressed(int key_index, bool repeat) if (t == 0.0f) return true; - // FIXME: Repeat rate should be provided elsewhere? - const float KEY_REPEAT_DELAY = 0.250f; - const float KEY_REPEAT_RATE = 0.020f; - if (repeat && t > KEY_REPEAT_DELAY) - if ((fmodf(t - KEY_REPEAT_DELAY, KEY_REPEAT_RATE) > KEY_REPEAT_RATE*0.5f) != (fmodf(t - KEY_REPEAT_DELAY - g.IO.DeltaTime, KEY_REPEAT_RATE) > KEY_REPEAT_RATE*0.5f)) + if (repeat && t > g.IO.KeyRepeatDelay) + { + float delay = g.IO.KeyRepeatDelay, rate = g.IO.KeyRepeatRate; + if ((fmodf(t - delay, rate) > rate*0.5f) != (fmodf(t - delay - g.IO.DeltaTime, rate) > rate*0.5f)) return true; + } return false; } @@ -2743,12 +2902,12 @@ bool ImGui::IsMouseClicked(int button, bool repeat) if (t == 0.0f) return true; - // FIXME: Repeat rate should be provided elsewhere? - const float MOUSE_REPEAT_DELAY = 0.250f; - const float MOUSE_REPEAT_RATE = 0.020f; - if (repeat && t > MOUSE_REPEAT_DELAY) - if ((fmodf(t - MOUSE_REPEAT_DELAY, MOUSE_REPEAT_RATE) > MOUSE_REPEAT_RATE*0.5f) != (fmodf(t - MOUSE_REPEAT_DELAY - g.IO.DeltaTime, MOUSE_REPEAT_RATE) > MOUSE_REPEAT_RATE*0.5f)) + if (repeat && t > g.IO.KeyRepeatDelay) + { + float delay = g.IO.KeyRepeatDelay, rate = g.IO.KeyRepeatRate; + if ((fmodf(t - delay, rate) > rate*0.5f) != (fmodf(t - delay - g.IO.DeltaTime, rate) > rate*0.5f)) return true; + } return false; } @@ -2912,30 +3071,100 @@ void ImGui::EndTooltip() ImGui::End(); } -void ImGui::BeginPopup(bool* p_opened) +static bool IsPopupOpen(ImGuiID id) +{ + ImGuiState& g = *GImGui; + const bool opened = g.OpenedPopupStack.size() > g.CurrentPopupStack.size() && g.OpenedPopupStack[g.CurrentPopupStack.size()].PopupID == id; + return opened; +} + +// One open popup per level of the popup hierarchy (NB: when assigning we reset the Window member of ImGuiPopupRef to NULL) +void ImGui::OpenPopup(const char* str_id) { ImGuiState& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); - IM_ASSERT(p_opened != NULL); // Must provide a bool at the moment + const ImGuiID id = window->GetID(str_id); + g.OpenedPopupStack.resize(g.CurrentPopupStack.size() + 1); + if (g.OpenedPopupStack.back().PopupID != id) + g.OpenedPopupStack.back() = ImGuiPopupRef(id, window, window->GetID("##menus")); +} + +static void ClosePopupToLevel(int remaining) +{ + ImGuiState& g = *GImGui; + if (remaining > 0) + FocusWindow(g.OpenedPopupStack[remaining-1].Window); + else + FocusWindow(g.OpenedPopupStack[0].ParentWindow); + g.OpenedPopupStack.resize(remaining); +} + +static void ClosePopup(const char* str_id) // not exposed because 'id' scope is misleading +{ + ImGuiState& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + const ImGuiID id = window->GetID(str_id); + if (IsPopupOpen(id)) + ClosePopupToLevel((int)g.CurrentPopupStack.size()); +} + +// Close the popup we have begin-ed into. +void ImGui::CloseCurrentPopup() +{ + ImGuiState& g = *GImGui; + int popup_idx = (int)g.CurrentPopupStack.size() - 1; + if (popup_idx < 0 || popup_idx > (int)g.OpenedPopupStack.size() || g.CurrentPopupStack[popup_idx].PopupID != g.OpenedPopupStack[popup_idx].PopupID) + return; + while (popup_idx > 0 && g.OpenedPopupStack[popup_idx].Window && (g.OpenedPopupStack[popup_idx].Window->Flags & ImGuiWindowFlags_ChildMenu)) + popup_idx--; + ClosePopupToLevel(popup_idx); +} + +static void ClearSetNextWindowData() +{ + ImGuiState& g = *GImGui; + g.SetNextWindowPosCond = g.SetNextWindowSizeCond = g.SetNextWindowCollapsedCond = g.SetNextWindowFocus = 0; +} + +static bool BeginPopupEx(const char* str_id, ImGuiWindowFlags extra_flags) +{ + ImGuiState& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + const ImGuiID id = window->GetID(str_id); + if (!IsPopupOpen(id)) + { + ClearSetNextWindowData(); // We behave like Begin() and need to consume those values + return false; + } ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - ImGuiWindowFlags flags = ImGuiWindowFlags_Popup|ImGuiWindowFlags_ShowBorders|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_AlwaysAutoResize; + ImGuiWindowFlags flags = extra_flags|ImGuiWindowFlags_Popup|ImGuiWindowFlags_ShowBorders|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_AlwaysAutoResize; + + char name[32]; + if (flags & ImGuiWindowFlags_ChildMenu) + ImFormatString(name, 20, "##menu_%d", g.CurrentPopupStack.size()); // Recycle windows based on depth + else + ImFormatString(name, 20, "##popup_%08x", id); // Not recycling, so we can close/open during the same frame float alpha = 1.0f; - char name[20]; - ImFormatString(name, 20, "##Popup%02d", g.CurrentPopupStackSize++); - ImGui::Begin(name, p_opened, ImVec2(0.0f, 0.0f), alpha, flags); - + bool opened = ImGui::Begin(name, NULL, ImVec2(0.0f, 0.0f), alpha, flags); if (!(window->Flags & ImGuiWindowFlags_ShowBorders)) GetCurrentWindow()->Flags &= ~ImGuiWindowFlags_ShowBorders; + if (!opened) // opened can be 'false' when the popup is completely clipped (e.g. zero size display) + ImGui::EndPopup(); + + return opened; +} + +bool ImGui::BeginPopup(const char* str_id) +{ + return BeginPopupEx(str_id, 0); } void ImGui::EndPopup() { - ImGuiState& g = *GImGui; IM_ASSERT(GetCurrentWindow()->Flags & ImGuiWindowFlags_Popup); - IM_ASSERT(g.CurrentPopupStackSize > 0); - g.CurrentPopupStackSize--; + IM_ASSERT(GImGui->CurrentPopupStack.size() > 0); ImGui::End(); ImGui::PopStyleVar(); } @@ -2981,7 +3210,7 @@ bool ImGui::BeginChild(const char* str_id, const ImVec2& size_arg, bool border, bool ImGui::BeginChild(ImGuiID id, const ImVec2& size, bool border, ImGuiWindowFlags extra_flags) { char str_id[32]; - ImFormatString(str_id, IM_ARRAYSIZE(str_id), "child_%x", id); + ImFormatString(str_id, IM_ARRAYSIZE(str_id), "child_%08x", id); bool ret = ImGui::BeginChild(str_id, size, border, extra_flags); return ret; } @@ -2991,7 +3220,7 @@ void ImGui::EndChild() ImGuiWindow* window = GetCurrentWindow(); IM_ASSERT(window->Flags & ImGuiWindowFlags_ChildWindow); - if (window->Flags & ImGuiWindowFlags_ComboBox) + if ((window->Flags & ImGuiWindowFlags_ComboBox) || window->BeginCount > 1) { ImGui::End(); } @@ -3034,25 +3263,26 @@ void ImGui::EndChildFrame() // Save and compare stack sizes on Begin()/End() to detect usage errors static void CheckStacksSize(ImGuiWindow* window, bool write) { - // NOT checking: DC.ItemWidth, DC.AllowKeyboardFocus, DC.TextWrapPos (per window) to allow user to conveniently push once and not pop (they are cleared on Begin) + // NOT checking: DC.ItemWidth, DC.AllowKeyboardFocus, DC.ButtonRepeat, DC.TextWrapPos (per window) to allow user to conveniently push once and not pop (they are cleared on Begin) ImGuiState& g = *GImGui; int* p_backup = &window->DC.StackSizesBackup[0]; { int current = (int)window->IDStack.size(); if (write) *p_backup = current; else IM_ASSERT(*p_backup == current); p_backup++; } // User forgot PopID() { int current = (int)window->DC.GroupStack.size(); if (write) *p_backup = current; else IM_ASSERT(*p_backup == current); p_backup++; } // User forgot EndGroup() + { int current = (int)g.CurrentPopupStack.size(); if (write) *p_backup = current; else IM_ASSERT(*p_backup == current); p_backup++; } // User forgot EndPopup()/EndMenu() { int current = (int)g.ColorModifiers.size(); if (write) *p_backup = current; else IM_ASSERT(*p_backup == current); p_backup++; } // User forgot PopStyleColor() { int current = (int)g.StyleModifiers.size(); if (write) *p_backup = current; else IM_ASSERT(*p_backup == current); p_backup++; } // User forgot PopStyleVar() { int current = (int)g.FontStack.size(); if (write) *p_backup = current; else IM_ASSERT(*p_backup == current); p_backup++; } // User forgot PopFont() IM_ASSERT(p_backup == window->DC.StackSizesBackup + IM_ARRAYSIZE(window->DC.StackSizesBackup)); } -static ImVec2 FindBestWindowPos(const ImVec2& mouse_pos, const ImVec2& size, int* last_dir, const ImRect& r_inner) +static ImVec2 FindBestWindowPos(const ImVec2& base_pos, const ImVec2& size, ImGuiWindowFlags flags, int* last_dir, const ImRect& r_inner) { const ImGuiStyle& style = GImGui->Style; // Clamp into visible area while not overlapping the cursor ImRect r_outer(GetVisibleRect()); r_outer.Reduce(style.DisplaySafeAreaPadding); - ImVec2 mouse_pos_clamped = ImClamp(mouse_pos, r_outer.Min, r_outer.Max - size); + ImVec2 base_pos_clamped = ImClamp(base_pos, r_outer.Min, r_outer.Max - size); for (int n = (*last_dir != -1) ? -1 : 0; n < 4; n++) // Right, down, up, left. Favor last used direction. { @@ -3061,12 +3291,19 @@ static ImVec2 FindBestWindowPos(const ImVec2& mouse_pos, const ImVec2& size, int if (rect.GetWidth() < size.x || rect.GetHeight() < size.y) continue; *last_dir = dir; - return ImVec2(dir == 0 ? r_inner.Max.x : dir == 3 ? r_inner.Min.x - size.x : mouse_pos_clamped.x, dir == 1 ? r_inner.Max.y : dir == 2 ? r_inner.Min.y - size.y : mouse_pos_clamped.y); + return ImVec2(dir == 0 ? r_inner.Max.x : dir == 3 ? r_inner.Min.x - size.x : base_pos_clamped.x, dir == 1 ? r_inner.Max.y : dir == 2 ? r_inner.Min.y - size.y : base_pos_clamped.y); } // Fallback *last_dir = -1; - return mouse_pos + ImVec2(2,2); + if (flags & ImGuiWindowFlags_Tooltip) // For tooltip we prefer avoiding the cursor at all cost even if it means that part of the tooltip won't be visible. + return base_pos + ImVec2(2,2); + + // Otherwise try to keep within display + ImVec2 pos = base_pos; + pos.x = ImMax(ImMin(pos.x + size.x, r_outer.Max.x) - size.x, r_outer.Min.x); + pos.y = ImMax(ImMin(pos.y + size.y, r_outer.Max.y) - size.y, r_outer.Min.y); + return pos; } static ImGuiWindow* FindWindowByName(const char* name) @@ -3170,16 +3407,25 @@ bool ImGui::Begin(const char* name, bool* p_opened, const ImVec2& size_on_first_ window_is_new = true; } window->Flags = (ImGuiWindowFlags)flags; - - const int current_frame = ImGui::GetFrameCount(); - const bool window_was_visible = (window->LastFrameDrawn == current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on - + // Add to stack ImGuiWindow* parent_window = !g.CurrentWindowStack.empty() ? g.CurrentWindowStack.back() : NULL; g.CurrentWindowStack.push_back(window); SetCurrentWindow(window); CheckStacksSize(window, true); + const int current_frame = ImGui::GetFrameCount(); + bool window_was_visible = (window->LastFrameDrawn == current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on + if (flags & ImGuiWindowFlags_Popup) + { + ImGuiPopupRef& popup_ref = g.OpenedPopupStack[g.CurrentPopupStack.size()]; + window_was_visible &= (window->PopupID == popup_ref.PopupID); + window_was_visible &= (window == popup_ref.Window); + popup_ref.Window = window; + g.CurrentPopupStack.push_back(popup_ref); + window->PopupID = popup_ref.PopupID; + } + // Process SetNextWindow***() calls bool window_pos_set_by_api = false; if (g.SetNextWindowPosCond) @@ -3228,34 +3474,30 @@ bool ImGui::Begin(const char* name, bool* p_opened, const ImVec2& size_on_first_ if (first_begin_of_the_frame) { window->Active = true; + window->BeginCount = 0; window->DrawList->Clear(); window->ClipRectStack.resize(0); window->LastFrameDrawn = current_frame; window->IDStack.resize(1); - } - // Setup texture, outer clipping rectangle - window->DrawList->PushTextureID(g.Font->ContainerAtlas->TexID); - if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_ComboBox)) - PushClipRect(parent_window->ClipRectStack.back()); - else - PushClipRect(GetVisibleRect()); + // Setup texture, outer clipping rectangle + window->DrawList->PushTextureID(g.Font->ContainerAtlas->TexID); + if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & (ImGuiWindowFlags_ComboBox|ImGuiWindowFlags_Popup))) + PushClipRect(parent_window->ClipRectStack.back()); + else + PushClipRect(GetVisibleRect()); - if (first_begin_of_the_frame) - { // New windows appears in front if (!window_was_visible) { window->AutoPosLastDirection = -1; - if (!(flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Tooltip)) - { + if (!(flags & (ImGuiWindowFlags_ChildWindow|ImGuiWindowFlags_Tooltip)) || (flags & ImGuiWindowFlags_Popup)) FocusWindow(window); - // Popup first latch mouse position, will position itself when it appears next frame - if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api) - window->PosFloat = g.IO.MousePos; - } + // Popup first latch mouse position, will position itself when it appears next frame + if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api) + window->PosFloat = g.IO.MousePos; } // Collapse window by double-clicking on title bar @@ -3286,7 +3528,7 @@ bool ImGui::Begin(const char* name, bool* p_opened, const ImVec2& size_on_first_ window->SizeContents.y += window->ScrollY; // Hide popup/tooltip window when first appearing while we measure size (because we recycle them) - if ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) != 0 && !window->WasActive) + if ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) != 0 && !window_was_visible) { window->HiddenFrames = 1; window->Size = window->SizeFull = window->SizeContents = ImVec2(0.f, 0.f); // TODO: We don't support SetNextWindowSize() for tooltips or popups yet @@ -3294,17 +3536,18 @@ bool ImGui::Begin(const char* name, bool* p_opened, const ImVec2& size_on_first_ // Calculate auto-fit size ImVec2 size_auto_fit; + ImVec2 window_padding = window->WindowPadding(); if ((flags & ImGuiWindowFlags_Tooltip) != 0) { // Tooltip always resize. We keep the spacing symmetric on both axises for aesthetic purpose. - size_auto_fit = window->SizeContents + style.WindowPadding - ImVec2(0.0f, style.ItemSpacing.y); + size_auto_fit = window->SizeContents + window_padding - ImVec2(0.0f, style.ItemSpacing.y); } else { - size_auto_fit = ImClamp(window->SizeContents + style.WindowPadding, style.WindowMinSize, ImMax(style.WindowMinSize, g.IO.DisplaySize - style.WindowPadding)); - if (size_auto_fit.y < window->SizeContents.y + style.WindowPadding.y) + size_auto_fit = ImClamp(window->SizeContents + window_padding, style.WindowMinSize, ImMax(style.WindowMinSize, g.IO.DisplaySize - window_padding)); + if (size_auto_fit.y < window->SizeContents.y && !(flags & ImGuiWindowFlags_NoScrollbar)) size_auto_fit.x += style.ScrollbarWidth; - size_auto_fit.y -= style.ItemSpacing.y; + size_auto_fit.y = ImMax(size_auto_fit.y - style.ItemSpacing.y, 0.0f); } // Handle automatic resize @@ -3333,31 +3576,46 @@ bool ImGui::Begin(const char* name, bool* p_opened, const ImVec2& size_on_first_ } // Minimum window size - if (!(flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Tooltip)) + if (!(flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_Tooltip))) + { window->SizeFull = ImMax(window->SizeFull, style.WindowMinSize); + if (!window->Collapsed) + window->Size = window->SizeFull; + } // POSITION // Position child window if (flags & ImGuiWindowFlags_ChildWindow) - { parent_window->DC.ChildWindows.push_back(window); + if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup)) + { window->Pos = window->PosFloat = parent_window->DC.CursorPos; - window->SizeFull = size_on_first_use; // NB: argument name 'size_on_first_use' misleading here, it's really just 'size' as provided by user. + window->Size = window->SizeFull = size_on_first_use; // NB: argument name 'size_on_first_use' misleading here, it's really just 'size' as provided by user. } // Position popup - if ((flags & ImGuiWindowFlags_Popup) != 0 && window_appearing_after_being_hidden && !window_pos_set_by_api) + if (flags & ImGuiWindowFlags_ChildMenu) + { + IM_ASSERT(window_pos_set_by_api); + ImRect rect_to_avoid; + if (parent_window->DC.MenuBarAppending) + rect_to_avoid = ImRect(-FLT_MAX, parent_window->Pos.y + parent_window->TitleBarHeight(), FLT_MAX, parent_window->Pos.y + parent_window->TitleBarHeight() + parent_window->MenuBarHeight()); + else + rect_to_avoid = ImRect(parent_window->Pos.x + style.ItemSpacing.x, -FLT_MAX, parent_window->Pos.x + parent_window->Size.x - style.ItemSpacing.x - parent_window->ScrollbarWidth(), FLT_MAX); // We want some overlap to convey the relative depth of each popup (here hard-coded to 4) + window->PosFloat = FindBestWindowPos(window->PosFloat, window->Size, flags, &window->AutoPosLastDirection, rect_to_avoid); + } + else if ((flags & ImGuiWindowFlags_Popup) != 0 && window_appearing_after_being_hidden && !window_pos_set_by_api) { ImRect rect_to_avoid(window->PosFloat.x - 1, window->PosFloat.y - 1, window->PosFloat.x + 1, window->PosFloat.y + 1); - window->PosFloat = FindBestWindowPos(window->PosFloat, window->Size, &window->AutoPosLastDirection, rect_to_avoid); + window->PosFloat = FindBestWindowPos(window->PosFloat, window->Size, flags, &window->AutoPosLastDirection, rect_to_avoid); } // Position tooltip (always follows mouse) if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api) { ImRect rect_to_avoid(g.IO.MousePos.x - 16, g.IO.MousePos.y - 8, g.IO.MousePos.x + 24, g.IO.MousePos.y + 24); // FIXME: Completely hard-coded. Perhaps center on cursor hit-point instead? - window->PosFloat = FindBestWindowPos(g.IO.MousePos, window->Size, &window->AutoPosLastDirection, rect_to_avoid); + window->PosFloat = FindBestWindowPos(g.IO.MousePos, window->Size, flags, &window->AutoPosLastDirection, rect_to_avoid); } // User moving window (at the beginning of the frame to avoid input lag or sheering). Only valid for root windows. @@ -3382,10 +3640,10 @@ bool ImGui::Begin(const char* name, bool* p_opened, const ImVec2& size_on_first_ } } - // Clamp into display + // Clamp position so it stays visible if (!(flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Tooltip)) { - if (window->AutoFitFrames <= 0 && g.IO.DisplaySize.x > 0.0f && g.IO.DisplaySize.y > 0.0f) // Ignore zero-sized display explicitly to avoid losing positions if a window manager reports zero-sized window when initializing or minimizing. + if (!window_pos_set_by_api && window->AutoFitFrames <= 0 && g.IO.DisplaySize.x > 0.0f && g.IO.DisplaySize.y > 0.0f) // Ignore zero-sized display explicitly to avoid losing positions if a window manager reports zero-sized window when initializing or minimizing. { ImVec2 padding = ImMax(style.DisplayWindowPadding, style.DisplaySafeAreaPadding); window->PosFloat = ImMax(window->PosFloat + window->Size, padding) - window->Size; @@ -3398,17 +3656,11 @@ bool ImGui::Begin(const char* name, bool* p_opened, const ImVec2& size_on_first_ if (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) && !(flags & ImGuiWindowFlags_AlwaysAutoResize)) window->ItemWidthDefault = (float)(int)(window->Size.x * 0.65f); else - window->ItemWidthDefault = 200.0f; + window->ItemWidthDefault = (float)(int)(g.FontSize * 16.0f); // Prepare for focus requests - if (window->FocusIdxAllRequestNext == IM_INT_MAX || window->FocusIdxAllCounter == -1) - window->FocusIdxAllRequestCurrent = IM_INT_MAX; - else - window->FocusIdxAllRequestCurrent = (window->FocusIdxAllRequestNext + (window->FocusIdxAllCounter+1)) % (window->FocusIdxAllCounter+1); - if (window->FocusIdxTabRequestNext == IM_INT_MAX || window->FocusIdxTabCounter == -1) - window->FocusIdxTabRequestCurrent = IM_INT_MAX; - else - window->FocusIdxTabRequestCurrent = (window->FocusIdxTabRequestNext + (window->FocusIdxTabCounter+1)) % (window->FocusIdxTabCounter+1); + window->FocusIdxAllRequestCurrent = (window->FocusIdxAllRequestNext == IM_INT_MAX || window->FocusIdxAllCounter == -1) ? IM_INT_MAX : (window->FocusIdxAllRequestNext + (window->FocusIdxAllCounter+1)) % (window->FocusIdxAllCounter+1); + window->FocusIdxTabRequestCurrent = (window->FocusIdxTabRequestNext == IM_INT_MAX || window->FocusIdxTabCounter == -1) ? IM_INT_MAX : (window->FocusIdxTabRequestNext + (window->FocusIdxTabCounter+1)) % (window->FocusIdxTabCounter+1); window->FocusIdxAllCounter = window->FocusIdxTabCounter = -1; window->FocusIdxAllRequestNext = window->FocusIdxTabRequestNext = IM_INT_MAX; @@ -3467,7 +3719,7 @@ bool ImGui::Begin(const char* name, bool* p_opened, const ImVec2& size_on_first_ } // Scrollbar - window->ScrollbarY = (window->SizeContents.y > window->Size.y) && !(flags & ImGuiWindowFlags_NoScrollbar); + window->ScrollbarY = (window->SizeContents.y > window->Size.y + style.ItemSpacing.y) && !(flags & ImGuiWindowFlags_NoScrollbar); // Window background if (bg_alpha > 0.0f) @@ -3476,8 +3728,10 @@ bool ImGui::Begin(const char* name, bool* p_opened, const ImVec2& size_on_first_ window->DrawList->AddRectFilled(window->Pos, window->Pos+window->Size, window->Color(ImGuiCol_ComboBg, bg_alpha), window_rounding); else if ((flags & ImGuiWindowFlags_Tooltip) != 0) window->DrawList->AddRectFilled(window->Pos, window->Pos+window->Size, window->Color(ImGuiCol_TooltipBg, bg_alpha), window_rounding); + else if ((flags & ImGuiWindowFlags_Popup) != 0) + window->DrawList->AddRectFilled(window->Pos, window->Pos+window->Size, window->Color(ImGuiCol_WindowBg, bg_alpha), window_rounding); else if ((flags & ImGuiWindowFlags_ChildWindow) != 0) - window->DrawList->AddRectFilled(window->Pos, window->Pos+window->Size-ImVec2(window->ScrollbarY?style.ScrollbarWidth:0.0f,0.0f), window->Color(ImGuiCol_ChildWindowBg, bg_alpha), window_rounding, window->ScrollbarY ? (1|8) : (0xF)); + window->DrawList->AddRectFilled(window->Pos, window->Pos+window->Size-ImVec2(window->ScrollbarWidth(),0.0f), window->Color(ImGuiCol_ChildWindowBg, bg_alpha), window_rounding, window->ScrollbarY ? (1|8) : (0xF)); else window->DrawList->AddRectFilled(window->Pos, window->Pos+window->Size, window->Color(ImGuiCol_WindowBg, bg_alpha), window_rounding); } @@ -3486,6 +3740,13 @@ bool ImGui::Begin(const char* name, bool* p_opened, const ImVec2& size_on_first_ if (!(flags & ImGuiWindowFlags_NoTitleBar)) window->DrawList->AddRectFilled(title_bar_rect.GetTL(), title_bar_rect.GetBR(), window->Color(ImGuiCol_TitleBg), window_rounding, 1|2); + // Menu bar + if (flags & ImGuiWindowFlags_MenuBar) + { + ImRect menu_bar_rect = window->MenuBarRect(); + window->DrawList->AddRectFilled(menu_bar_rect.GetTL(), menu_bar_rect.GetBR(), window->Color(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, 1|2); + } + // Borders if (flags & ImGuiWindowFlags_ShowBorders) { @@ -3522,20 +3783,25 @@ bool ImGui::Begin(const char* name, bool* p_opened, const ImVec2& size_on_first_ // Setup drawing context window->DC.ColumnsStartX = window->WindowPadding().x; window->DC.ColumnsOffsetX = 0.0f; - window->DC.CursorStartPos = window->Pos + ImVec2(window->DC.ColumnsStartX + window->DC.ColumnsOffsetX, window->TitleBarHeight() + window->WindowPadding().y) - ImVec2(0.0f, window->ScrollY); + window->DC.CursorStartPos = window->Pos + ImVec2(window->DC.ColumnsStartX + window->DC.ColumnsOffsetX, window->TitleBarHeight() + window->MenuBarHeight() + window->WindowPadding().y) - ImVec2(0.0f, window->ScrollY); window->DC.CursorPos = window->DC.CursorStartPos; window->DC.CursorPosPrevLine = window->DC.CursorPos; window->DC.CursorMaxPos = window->DC.CursorStartPos; window->DC.CurrentLineHeight = window->DC.PrevLineHeight = 0.0f; window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset = 0.0f; + window->DC.MenuBarAppending = false; + window->DC.MenuBarOffsetX = ImMax(window->DC.ColumnsStartX, style.ItemSpacing.x); window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; window->DC.ChildWindows.resize(0); - window->DC.ItemWidth.resize(0); - window->DC.ItemWidth.push_back(window->ItemWidthDefault); - window->DC.AllowKeyboardFocus.resize(0); - window->DC.AllowKeyboardFocus.push_back(true); - window->DC.TextWrapPos.resize(0); - window->DC.TextWrapPos.push_back(-1.0f); // disabled + window->DC.LayoutType = ImGuiLayoutType_Vertical; + window->DC.ItemWidth = window->ItemWidthDefault; + window->DC.ItemWidthStack.resize(0); + window->DC.ButtonRepeat = false; + window->DC.ButtonRepeatStack.resize(0); + window->DC.AllowKeyboardFocus = true; + window->DC.AllowKeyboardFocusStack.resize(0); + window->DC.TextWrapPos = -1.0f; // disabled + window->DC.TextWrapPosStack.resize(0); window->DC.ColorEditMode = ImGuiColorEditMode_UserSelect; window->DC.ColumnsCurrent = 0; window->DC.ColumnsCount = 1; @@ -3544,6 +3810,7 @@ bool ImGui::Begin(const char* name, bool* p_opened, const ImVec2& size_on_first_ window->DC.TreeDepth = 0; window->DC.StateStorage = &window->StateStorage; window->DC.GroupStack.resize(0); + window->MenuColumns.Update(3, style.ItemSpacing.x, !window_was_visible); if (window->AutoFitFrames > 0) window->AutoFitFrames--; @@ -3566,19 +3833,7 @@ bool ImGui::Begin(const char* name, bool* p_opened, const ImVec2& size_on_first_ if (style.WindowTitleAlign & ImGuiAlign_Center) pad_right = pad_left; if (pad_left) text_min.x += g.FontSize + style.ItemInnerSpacing.x; if (pad_right) text_max.x -= g.FontSize + style.ItemInnerSpacing.x; - RenderTextClipped(text_min, name, NULL, &text_size, text_max, &clip_max, style.WindowTitleAlign); - } - if (flags & ImGuiWindowFlags_Popup) - { - if (p_opened) - { - if (g.IO.MouseClicked[0] && (!g.HoveredWindow || g.HoveredWindow->RootWindow != window)) - *p_opened = false; - else if (!g.FocusedWindow) - *p_opened = false; - else if (g.FocusedWindow->RootWindow != window)// && !(g.FocusedWindow->RootWindow->Flags & ImGuiWindowFlags_Tooltip)) - *p_opened = false; - } + RenderTextClipped(text_min, text_max, name, NULL, &text_size, &clip_max, style.WindowTitleAlign); } // Save clipped aabb so we can access it in constant-time in FindHoveredWindow() @@ -3594,12 +3849,13 @@ bool ImGui::Begin(const char* name, bool* p_opened, const ImVec2& size_on_first_ ImGui::LogToClipboard(); */ } + window->BeginCount++; // Inner clipping rectangle // We set this up after processing the resize grip so that our clip rectangle doesn't lag by a frame // Note that if our window is collapsed we will end up with a null clipping rectangle which is the correct behavior. const ImRect title_bar_rect = window->TitleBarRect(); - ImVec4 clip_rect(title_bar_rect.Min.x+0.5f+window->WindowPadding().x*0.5f, title_bar_rect.Max.y+0.5f, window->Rect().Max.x+0.5f-window->WindowPadding().x*0.5f, window->Rect().Max.y-1.5f); + ImVec4 clip_rect(title_bar_rect.Min.x+0.5f+window->WindowPadding().x*0.5f, title_bar_rect.Max.y+window->MenuBarHeight()+0.5f, window->Rect().Max.x+0.5f-window->WindowPadding().x*0.5f, window->Rect().Max.y-1.5f); if (window->ScrollbarY) clip_rect.z -= style.ScrollbarWidth; PushClipRect(clip_rect); @@ -3615,8 +3871,11 @@ bool ImGui::Begin(const char* name, bool* p_opened, const ImVec2& size_on_first_ IM_ASSERT((flags & ImGuiWindowFlags_NoTitleBar) != 0); window->Collapsed = parent_window && parent_window->Collapsed; - const ImVec4 clip_rect_t = window->ClipRectStack.back(); - window->Collapsed |= (clip_rect_t.x >= clip_rect_t.z || clip_rect_t.y >= clip_rect_t.w); + if (!(flags & ImGuiWindowFlags_AlwaysAutoResize) && window->AutoFitFrames <= 0) + { + const ImVec4 clip_rect_t = window->ClipRectStack.back(); + window->Collapsed |= (clip_rect_t.x >= clip_rect_t.z || clip_rect_t.y >= clip_rect_t.w); + } // We also hide the window from rendering because we've already added its border to the command list. // (we could perform the check earlier in the function but it is simpler at this point) @@ -3638,8 +3897,6 @@ void ImGui::End() ImGui::Columns(1, "#CloseColumns"); PopClipRect(); // inner window clip rectangle - PopClipRect(); // outer window clip rectangle - window->DrawList->PopTextureID(); // Stop logging if (!(window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME: add more options for scope of logging @@ -3647,8 +3904,10 @@ void ImGui::End() // Pop // NB: we don't clear 'window->RootWindow'. The pointer is allowed to live until the next call to Begin(). - CheckStacksSize(window, false); g.CurrentWindowStack.pop_back(); + if (window->Flags & ImGuiWindowFlags_Popup) + g.CurrentPopupStack.pop_back(); + CheckStacksSize(window, false); SetCurrentWindow(g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back()); } @@ -3752,7 +4011,8 @@ static void FocusWindow(ImGuiWindow* window) void ImGui::PushItemWidth(float item_width) { ImGuiWindow* window = GetCurrentWindow(); - window->DC.ItemWidth.push_back(item_width == 0.0f ? window->ItemWidthDefault : item_width); + window->DC.ItemWidth = (item_width == 0.0f ? window->ItemWidthDefault : item_width); + window->DC.ItemWidthStack.push_back(window->DC.ItemWidth); } static void PushMultiItemsWidths(int components, float w_full = 0.0f) @@ -3763,21 +4023,23 @@ static void PushMultiItemsWidths(int components, float w_full = 0.0f) w_full = ImGui::CalcItemWidth(); const float w_item_one = ImMax(1.0f, (float)(int)((w_full - (style.FramePadding.x*2.0f + style.ItemInnerSpacing.x) * (components-1)) / (float)components)); const float w_item_last = ImMax(1.0f, (float)(int)(w_full - (w_item_one + style.FramePadding.x*2.0f + style.ItemInnerSpacing.x) * (components-1))); - window->DC.ItemWidth.push_back(w_item_last); + window->DC.ItemWidthStack.push_back(w_item_last); for (int i = 0; i < components-1; i++) - window->DC.ItemWidth.push_back(w_item_one); + window->DC.ItemWidthStack.push_back(w_item_one); + window->DC.ItemWidth = window->DC.ItemWidthStack.back(); } void ImGui::PopItemWidth() { ImGuiWindow* window = GetCurrentWindow(); - window->DC.ItemWidth.pop_back(); + window->DC.ItemWidthStack.pop_back(); + window->DC.ItemWidth = window->DC.ItemWidthStack.empty() ? window->ItemWidthDefault : window->DC.ItemWidthStack.back(); } float ImGui::CalcItemWidth() { ImGuiWindow* window = GetCurrentWindow(); - float w = window->DC.ItemWidth.back(); + float w = window->DC.ItemWidth; if (w < 0.0f) { // Align to a right-side limit. We include 1 frame padding in the calculation because this is how the width is always used (we add 2 frame padding to it), but we could move that responsibility to the widget as well. @@ -3792,7 +4054,6 @@ float ImGui::CalcItemWidth() static void SetFont(ImFont* font) { ImGuiState& g = *GImGui; - IM_ASSERT(font && font->IsLoaded()); IM_ASSERT(font->Scale > 0.0f); g.Font = font; @@ -3804,10 +4065,8 @@ static void SetFont(ImFont* font) void ImGui::PushFont(ImFont* font) { ImGuiState& g = *GImGui; - if (!font) font = g.IO.Fonts->Fonts[0]; - SetFont(font); g.FontStack.push_back(font); g.CurrentWindow->DrawList->PushTextureID(font->ContainerAtlas->TexID); @@ -3816,7 +4075,6 @@ void ImGui::PushFont(ImFont* font) void ImGui::PopFont() { ImGuiState& g = *GImGui; - g.CurrentWindow->DrawList->PopTextureID(); g.FontStack.pop_back(); SetFont(g.FontStack.empty() ? g.IO.Fonts->Fonts[0] : g.FontStack.back()); @@ -3825,31 +4083,48 @@ void ImGui::PopFont() void ImGui::PushAllowKeyboardFocus(bool allow_keyboard_focus) { ImGuiWindow* window = GetCurrentWindow(); - window->DC.AllowKeyboardFocus.push_back(allow_keyboard_focus); + window->DC.AllowKeyboardFocus = allow_keyboard_focus; + window->DC.AllowKeyboardFocusStack.push_back(allow_keyboard_focus); } void ImGui::PopAllowKeyboardFocus() { ImGuiWindow* window = GetCurrentWindow(); - window->DC.AllowKeyboardFocus.pop_back(); + window->DC.AllowKeyboardFocusStack.pop_back(); + window->DC.AllowKeyboardFocus = window->DC.AllowKeyboardFocusStack.empty() ? true : window->DC.AllowKeyboardFocusStack.back(); } -void ImGui::PushTextWrapPos(float wrap_x) +void ImGui::PushButtonRepeat(bool repeat) { ImGuiWindow* window = GetCurrentWindow(); - window->DC.TextWrapPos.push_back(wrap_x); + window->DC.ButtonRepeat = repeat; + window->DC.ButtonRepeatStack.push_back(repeat); +} + +void ImGui::PopButtonRepeat() +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.ButtonRepeatStack.pop_back(); + window->DC.ButtonRepeat = window->DC.ButtonRepeatStack.empty() ? false : window->DC.ButtonRepeatStack.back(); +} + +void ImGui::PushTextWrapPos(float wrap_pos_x) +{ + ImGuiWindow* window = GetCurrentWindow(); + window->DC.TextWrapPos = wrap_pos_x; + window->DC.TextWrapPosStack.push_back(wrap_pos_x); } void ImGui::PopTextWrapPos() { ImGuiWindow* window = GetCurrentWindow(); - window->DC.TextWrapPos.pop_back(); + window->DC.TextWrapPosStack.pop_back(); + window->DC.TextWrapPos = window->DC.TextWrapPosStack.empty() ? -1.0f : window->DC.TextWrapPosStack.back(); } void ImGui::PushStyleColor(ImGuiCol idx, const ImVec4& col) { ImGuiState& g = *GImGui; - ImGuiColMod backup; backup.Col = idx; backup.PreviousValue = g.Style.Colors[idx]; @@ -3860,7 +4135,6 @@ void ImGui::PushStyleColor(ImGuiCol idx, const ImVec4& col) void ImGui::PopStyleColor(int count) { ImGuiState& g = *GImGui; - while (count > 0) { ImGuiColMod& backup = g.ColorModifiers.back(); @@ -3891,6 +4165,7 @@ static ImVec2* GetStyleVarVec2Addr(ImGuiStyleVar idx) switch (idx) { case ImGuiStyleVar_WindowPadding: return &g.Style.WindowPadding; + case ImGuiStyleVar_WindowMinSize: return &g.Style.WindowMinSize; case ImGuiStyleVar_FramePadding: return &g.Style.FramePadding; case ImGuiStyleVar_ItemSpacing: return &g.Style.ItemSpacing; case ImGuiStyleVar_ItemInnerSpacing: return &g.Style.ItemInnerSpacing; @@ -3901,7 +4176,6 @@ static ImVec2* GetStyleVarVec2Addr(ImGuiStyleVar idx) void ImGui::PushStyleVar(ImGuiStyleVar idx, float val) { ImGuiState& g = *GImGui; - float* pvar = GetStyleVarFloatAddr(idx); IM_ASSERT(pvar != NULL); // Called function with wrong-type? Variable is not a float. ImGuiStyleMod backup; @@ -3915,7 +4189,6 @@ void ImGui::PushStyleVar(ImGuiStyleVar idx, float val) void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val) { ImGuiState& g = *GImGui; - ImVec2* pvar = GetStyleVarVec2Addr(idx); IM_ASSERT(pvar != NULL); // Called function with wrong-type? Variable is not a ImVec2. ImGuiStyleMod backup; @@ -3928,7 +4201,6 @@ void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val) void ImGui::PopStyleVar(int count) { ImGuiState& g = *GImGui; - while (count > 0) { ImGuiStyleMod& backup = g.StyleModifiers.back(); @@ -3947,6 +4219,7 @@ const char* ImGui::GetStyleColName(ImGuiCol idx) switch (idx) { case ImGuiCol_Text: return "Text"; + case ImGuiCol_TextDisabled: return "TextDisabled"; case ImGuiCol_WindowBg: return "WindowBg"; case ImGuiCol_ChildWindowBg: return "ChildWindowBg"; case ImGuiCol_Border: return "Border"; @@ -3956,6 +4229,7 @@ const char* ImGui::GetStyleColName(ImGuiCol idx) case ImGuiCol_FrameBgActive: return "FrameBgActive"; case ImGuiCol_TitleBg: return "TitleBg"; case ImGuiCol_TitleBgCollapsed: return "TitleBgCollapsed"; + case ImGuiCol_MenuBarBg: return "MenuBarBg"; case ImGuiCol_ScrollbarBg: return "ScrollbarBg"; case ImGuiCol_ScrollbarGrab: return "ScrollbarGrab"; case ImGuiCol_ScrollbarGrabHovered: return "ScrollbarGrabHovered"; @@ -4180,8 +4454,7 @@ ImVec2 ImGui::GetContentRegionMax() } else { - if (window->ScrollbarY) - mx.x -= GImGui->Style.ScrollbarWidth; + mx.x -= window->ScrollbarWidth(); } return mx; } @@ -4189,15 +4462,14 @@ ImVec2 ImGui::GetContentRegionMax() ImVec2 ImGui::GetWindowContentRegionMin() { ImGuiWindow* window = GetCurrentWindow(); - return ImVec2(0, window->TitleBarHeight()) + window->WindowPadding(); + return ImVec2(0, window->TitleBarHeight() + window->MenuBarHeight()) + window->WindowPadding(); } ImVec2 ImGui::GetWindowContentRegionMax() { ImGuiWindow* window = GetCurrentWindow(); ImVec2 m = window->Size - window->WindowPadding(); - if (window->ScrollbarY) - m.x -= GImGui->Style.ScrollbarWidth; + m.x -= window->ScrollbarWidth(); return m; } @@ -4361,6 +4633,21 @@ void ImGui::TextColored(const ImVec4& col, const char* fmt, ...) va_end(args); } +void ImGui::TextDisabledV(const char* fmt, va_list args) +{ + ImGui::PushStyleColor(ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled]); + TextV(fmt, args); + ImGui::PopStyleColor(); +} + +void ImGui::TextDisabled(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextDisabledV(fmt, args); + va_end(args); +} + void ImGui::TextWrappedV(const char* fmt, va_list args) { ImGui::PushTextWrapPos(0.0f); @@ -4388,7 +4675,7 @@ void ImGui::TextUnformatted(const char* text, const char* text_end) if (text_end == NULL) text_end = text + strlen(text); // FIXME-OPT - const float wrap_pos_x = window->DC.TextWrapPos.back(); + const float wrap_pos_x = window->DC.TextWrapPos; const bool wrap_enabled = wrap_pos_x >= 0.0f; if (text_end - text > 2000 && !wrap_enabled) { @@ -4506,9 +4793,6 @@ void ImGui::LabelTextV(const char* label, const char* fmt, va_list args) const ImGuiStyle& style = g.Style; const float w = ImGui::CalcItemWidth(); - const char* value_text_begin = &g.TempBuffer[0]; - const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); - const ImVec2 label_size = CalcTextSize(label, NULL, true); const ImRect value_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w + style.FramePadding.x*2, label_size.y + style.FramePadding.y*2)); const ImRect total_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w + style.FramePadding.x*2 + (label_size.x > 0.0f ? style.ItemInnerSpacing.x : 0.0f), style.FramePadding.y*2) + label_size); @@ -4517,7 +4801,9 @@ void ImGui::LabelTextV(const char* label, const char* fmt, va_list args) return; // Render - RenderTextClipped(ImVec2(value_bb.Min.x, value_bb.Min.y + style.FramePadding.y), value_text_begin, value_text_end, NULL, value_bb.Max); + const char* value_text_begin = &g.TempBuffer[0]; + const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + RenderTextClipped(value_bb.Min, value_bb.Max, value_text_begin, value_text_end, NULL, NULL, ImGuiAlign_VCenter); RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label); } @@ -4531,10 +4817,9 @@ void ImGui::LabelText(const char* label, const char* fmt, ...) static inline bool IsWindowContentHoverable(ImGuiWindow* window) { - ImGuiState& g = *GImGui; - // An active popup disable hovering on other windows (apart from its own children) - if (ImGuiWindow* focused_window = g.FocusedWindow) + ImGuiState& g = *GImGui; + if (ImGuiWindow* focused_window = g.FocusedWindow) if (ImGuiWindow* focused_root_window = focused_window->RootWindow) if ((focused_root_window->Flags & ImGuiWindowFlags_Popup) != 0 && focused_root_window->WasActive && focused_root_window != window->RootWindow) return false; @@ -4545,7 +4830,7 @@ static inline bool IsWindowContentHoverable(ImGuiWindow* window) static bool IsHovered(const ImRect& bb, ImGuiID id, bool flatten_childs = false) { ImGuiState& g = *GImGui; - if (g.HoveredId == 0) + if (g.HoveredId == 0 || g.HoveredId == id) { ImGuiWindow* window = GetCurrentWindow(); if (g.HoveredWindow == window || (flatten_childs && g.HoveredRootWindow == window->RootWindow)) @@ -4561,6 +4846,14 @@ static bool ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool ImGuiState& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); + if (flags & ImGuiButtonFlags_Disabled) + { + if (out_hovered) *out_hovered = false; + if (out_held) *out_held = false; + if (g.ActiveId == id) SetActiveId(0); + return false; + } + bool pressed = false; const bool hovered = IsHovered(bb, id, (flags & ImGuiButtonFlags_FlattenChilds) != 0); if (hovered) @@ -4609,16 +4902,16 @@ static bool ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool return pressed; } -bool ImGui::Button(const char* label, const ImVec2& size_arg, bool repeat_when_held) +static bool ButtonEx(const char* label, const ImVec2& size_arg = ImVec2(0,0), ImGuiButtonFlags flags = 0) { - ImGuiState& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + ImGuiState& g = *GImGui; const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); const ImVec2 size(size_arg.x != 0.0f ? size_arg.x : (label_size.x + style.FramePadding.x*2), size_arg.y != 0.0f ? size_arg.y : (label_size.y + style.FramePadding.y*2)); const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); @@ -4626,46 +4919,35 @@ bool ImGui::Button(const char* label, const ImVec2& size_arg, bool repeat_when_h if (!ItemAdd(bb, &id)) return false; + if (window->DC.ButtonRepeat) flags |= ImGuiButtonFlags_Repeat; bool hovered, held; - bool pressed = ButtonBehavior(bb, id, &hovered, &held, true, repeat_when_held ? ImGuiButtonFlags_Repeat : 0); + bool pressed = ButtonBehavior(bb, id, &hovered, &held, true, flags); // Render const ImU32 col = window->Color((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); + RenderTextClipped(bb.Min, bb.Max, label, NULL, &label_size, NULL, ImGuiAlign_Center | ImGuiAlign_VCenter); - const ImVec2 off = ImVec2(ImMax(0.0f, size.x - label_size.x) * 0.5f, ImMax(0.0f, size.y - label_size.y) * 0.5f); // Center (only applies if we explicitly gave a size bigger than the text size, which isn't the common path) - RenderTextClipped(bb.Min + off, label, NULL, &label_size, bb.Max); // Render clip (only applies if we explicitly gave a size smaller than the text size, which isn't the commmon path) + // Automatically close popups + //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup)) + // ImGui::CloseCurrentPopup(); return pressed; } -// Small buttons fits within text without additional spacing. +bool ImGui::Button(const char* label, const ImVec2& size_arg) +{ + return ButtonEx(label, size_arg, 0); +} + +// Small buttons fits within text without additional vertical spacing. bool ImGui::SmallButton(const char* label) { ImGuiState& g = *GImGui; - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - const ImGuiStyle& style = g.Style; - const ImGuiID id = window->GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); - - ImVec2 text_pos = window->DC.CursorPos; - text_pos.y += window->DC.CurrentLineTextBaseOffset; - ImRect bb(text_pos, text_pos + label_size + ImVec2(style.FramePadding.x*2,0)); - ItemSize(bb); - if (!ItemAdd(bb, &id)) - return false; - - bool hovered, held; - bool pressed = ButtonBehavior(bb, id, &hovered, &held, true); - - // Render - const ImU32 col = window->Color((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); - RenderFrame(bb.Min, bb.Max, col); - RenderText(bb.Min + ImVec2(style.FramePadding.x,0), label); - + float backup_padding_y = g.Style.FramePadding.y; + g.Style.FramePadding.y = 0.0f; + bool pressed = ButtonEx(label); + g.Style.FramePadding.y = backup_padding_y; return pressed; } @@ -5492,8 +5774,7 @@ bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, c // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. char value_buf[64]; const char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v); - const ImVec2 value_text_size = CalcTextSize(value_buf, value_buf_end, true); - RenderTextClipped(ImVec2(ImMax(frame_bb.Min.x + style.FramePadding.x, frame_bb.GetCenter().x - value_text_size.x*0.5f), frame_bb.Min.y + style.FramePadding.y), value_buf, value_buf_end, &value_text_size, frame_bb.Max); + RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, NULL, ImGuiAlign_Center|ImGuiAlign_VCenter); if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); @@ -5541,8 +5822,7 @@ bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float // For the vertical slider we allow centered text to overlap the frame padding char value_buf[64]; char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v); - const ImVec2 value_text_size = CalcTextSize(value_buf, value_buf_end, true); - RenderTextClipped(ImVec2(ImMax(frame_bb.Min.x, frame_bb.GetCenter().x - value_text_size.x*0.5f), frame_bb.Min.y + style.FramePadding.y), value_buf, value_buf_end, &value_text_size, frame_bb.Max); + RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, NULL, ImGuiAlign_Center); if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); @@ -5797,8 +6077,7 @@ bool ImGui::DragFloat(const char* label, float *v, float v_speed, float v_min, f // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. char value_buf[64]; const char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v); - const ImVec2 value_text_size = CalcTextSize(value_buf, value_buf_end, true); - RenderTextClipped(ImVec2(ImMax(frame_bb.Min.x + style.FramePadding.x, inner_bb.GetCenter().x - value_text_size.x*0.5f), frame_bb.Min.y + style.FramePadding.y), value_buf, value_buf_end, &value_text_size, frame_bb.Max); + RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, NULL, ImGuiAlign_Center|ImGuiAlign_VCenter); if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label); @@ -5998,7 +6277,7 @@ static void Plot(ImGuiPlotType plot_type, const char* label, float (*values_gett // Text overlay if (overlay_text) - RenderTextClipped(ImVec2(ImMax(inner_bb.Min.x, inner_bb.GetCenter().x - ImGui::CalcTextSize(overlay_text, NULL, true).x*0.5f), frame_bb.Min.y + style.FramePadding.y), overlay_text, NULL, NULL, frame_bb.Max); + RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, overlay_text, NULL, NULL, NULL, ImGuiAlign_Center); RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label); } @@ -6366,19 +6645,18 @@ bool ImGui::InputFloat(const char* label, float *v, float step, float step_fast, { ImGui::PopItemWidth(); ImGui::SameLine(0, (int)style.ItemInnerSpacing.x); - if (ImGui::Button("-", button_sz, true)) + if (ButtonEx("-", button_sz, ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups)) { *v -= g.IO.KeyCtrl && step_fast > 0.0f ? step_fast : step; value_changed = true; } ImGui::SameLine(0, (int)style.ItemInnerSpacing.x); - if (ImGui::Button("+", button_sz, true)) + if (ButtonEx("+", button_sz, ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups)) { *v += g.IO.KeyCtrl && step_fast > 0.0f ? step_fast : step; value_changed = true; } } - ImGui::PopID(); if (label_size.x > 0) @@ -6387,7 +6665,6 @@ bool ImGui::InputFloat(const char* label, float *v, float step, float step_fast, RenderText(ImVec2(window->DC.CursorPos.x, window->DC.CursorPos.y + style.FramePadding.y), label); ItemSize(label_size, style.FramePadding.y); } - ImGui::EndGroup(); return value_changed; @@ -6811,7 +7088,7 @@ bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputT // Draw blinking cursor if (g.InputTextState.CursorIsVisible()) - window->DrawList->AddRect(cursor_pos - font_off_up + ImVec2(0,2), cursor_pos + font_off_dn - ImVec2(0,3), window->Color(ImGuiCol_Text)); + window->DrawList->AddLine(cursor_pos - font_off_up + ImVec2(0,2), cursor_pos + font_off_dn - ImVec2(0,3), window->Color(ImGuiCol_Text)); // Notify OS of text input position for advanced IME if (io.ImeSetInputScreenPosFn && ImLengthSqr(edit_state.InputCursorScreenPos - cursor_pos) > 0.0001f) @@ -6978,7 +7255,7 @@ bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(voi const ImVec2 label_size = CalcTextSize(label, NULL, true); const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y) + style.FramePadding*2.0f); - const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(style.ItemInnerSpacing.x + label_size.x,0)); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); ItemSize(total_bb, style.FramePadding.y); if (!ItemAdd(total_bb, &id)) return false; @@ -6996,7 +7273,7 @@ bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(voi { const char* item_text; if (items_getter(data, *current_item, &item_text)) - RenderTextClipped(frame_bb.Min + style.FramePadding, item_text, NULL, NULL, value_bb.Max); + RenderTextClipped(frame_bb.Min + style.FramePadding, value_bb.Max, item_text, NULL, NULL); } if (label_size.x > 0) @@ -7069,9 +7346,7 @@ bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(voi return value_changed; } -// Tip: pass an empty label (e.g. "##dummy") then you can use the space to draw other text or image. -// But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID. -bool ImGui::Selectable(const char* label, bool selected, const ImVec2& size_arg) +static bool SelectableEx(const char* label, bool selected, const ImVec2& size_arg, const ImVec2 size_draw_arg, ImGuiSelectableFlags flags) { ImGuiState& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); @@ -7079,22 +7354,27 @@ bool ImGui::Selectable(const char* label, bool selected, const ImVec2& size_arg) return false; const ImGuiStyle& style = g.Style; - const ImGuiID id = window->GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); - - const float w = ImMax(label_size.x, window->Pos.x + ImGui::GetContentRegionMax().x - style.WindowPadding.x - window->DC.CursorPos.x); - const ImVec2 size(size_arg.x != 0.0f ? size_arg.x : w, size_arg.y != 0.0f ? size_arg.y : label_size.y); - ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + ImGuiID id = window->GetID(label); + ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); + ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y); + ImVec2 pos = window->DC.CursorPos; + pos.y += window->DC.CurrentLineTextBaseOffset; + ImRect bb(pos, pos + size); ItemSize(bb); - if (size_arg.x == 0.0f) - bb.Max.x += style.WindowPadding.x; - // Selectables are meant to be tightly packed together. So for both rendering and collision we extend to compensate for spacing. - ImRect bb_with_spacing = bb; - const float spacing_L = (float)(int)(style.ItemSpacing.x * 0.5f); - const float spacing_U = (float)(int)(style.ItemSpacing.y * 0.5f); - const float spacing_R = style.ItemSpacing.x - spacing_L; - const float spacing_D = style.ItemSpacing.y - spacing_U; + // Fill horizontal space. + ImVec2 window_padding = window->WindowPadding(); + float w_draw = ImMax(label_size.x, window->Pos.x + ImGui::GetContentRegionMax().x - window_padding.x - window->DC.CursorPos.x); + ImVec2 size_draw(size_draw_arg.x != 0.0f ? size_draw_arg.x : w_draw, size_draw_arg.y != 0.0f ? size_draw_arg.y : size.y); + ImRect bb_with_spacing(pos, pos + size_draw); + if (size_draw_arg.x == 0.0f) + bb_with_spacing.Max.x += window_padding.x; + + // Selectables are tightly packed together, we extend the box to cover spacing between selectable. + float spacing_L = (float)(int)(style.ItemSpacing.x * 0.5f); + float spacing_U = (float)(int)(style.ItemSpacing.y * 0.5f); + float spacing_R = style.ItemSpacing.x - spacing_L; + float spacing_D = style.ItemSpacing.y - spacing_U; bb_with_spacing.Min.x -= spacing_L; bb_with_spacing.Min.y -= spacing_U; bb_with_spacing.Max.x += spacing_R; @@ -7103,7 +7383,9 @@ bool ImGui::Selectable(const char* label, bool selected, const ImVec2& size_arg) return false; bool hovered, held; - bool pressed = ButtonBehavior(bb_with_spacing, id, &hovered, &held, true); + bool pressed = ButtonBehavior(bb_with_spacing, id, &hovered, &held, true, ((flags & ImGuiSelectableFlags_MenuItem) ? ImGuiButtonFlags_PressedOnClick : 0) | ((flags & ImGuiSelectableFlags_Disabled) ? ImGuiButtonFlags_Disabled : 0)); + if (flags & ImGuiSelectableFlags_Disabled) + selected = false; // Render if (hovered || selected) @@ -7111,16 +7393,26 @@ bool ImGui::Selectable(const char* label, bool selected, const ImVec2& size_arg) const ImU32 col = window->Color((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); RenderFrame(bb_with_spacing.Min, bb_with_spacing.Max, col, false, style.FrameRounding); } + if (flags & ImGuiSelectableFlags_Disabled) ImGui::PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); + RenderTextClipped(bb.Min, bb_with_spacing.Max, label, NULL, &label_size); + if (flags & ImGuiSelectableFlags_Disabled) ImGui::PopStyleColor(); - //const ImVec2 off = ImVec2(ImMax(0.0f, size.x - text_size.x) * 0.5f, ImMax(0.0f, size.y - text_size.y) * 0.5f); - RenderTextClipped(bb.Min, label, NULL, &label_size, bb_with_spacing.Max); - + // Automatically close popups + if (pressed && !(flags & ImGuiSelectableFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup)) + ImGui::CloseCurrentPopup(); return pressed; } +// Tip: pass an empty label (e.g. "##dummy") then you can use the space to draw other text or image. +// But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID. +bool ImGui::Selectable(const char* label, bool selected, const ImVec2& size_arg) +{ + return SelectableEx(label, selected, size_arg, size_arg, 0); +} + bool ImGui::Selectable(const char* label, bool* p_selected, const ImVec2& size_arg) { - if (ImGui::Selectable(label, *p_selected, size_arg)) + if (SelectableEx(label, *p_selected, size_arg, size_arg, 0)) { *p_selected = !*p_selected; return true; @@ -7142,9 +7434,9 @@ bool ImGui::ListBoxHeader(const char* label, const ImVec2& size_arg) ImVec2 size; size.x = (size_arg.x != 0.0f) ? (size_arg.x) : ImGui::CalcItemWidth() + style.FramePadding.x * 2.0f; size.y = (size_arg.y != 0.0f) ? (size_arg.y) : ImGui::GetTextLineHeightWithSpacing() * 7.4f + style.ItemSpacing.y; - const ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y)); - const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size); - const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y)); + ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size); + ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); window->DC.LastItemRect = bb; ImGui::BeginGroup(); @@ -7223,33 +7515,36 @@ bool ImGui::ListBox(const char* label, int* current_item, bool (*items_getter)(v return value_changed; } -bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected) +bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled) { - (void)shortcut; // FIXME-MENU: Shortcut are not supported yet. Argument is reserved. - ImGuiState& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; ImVec2 pos = ImGui::GetCursorScreenPos(); - const ImVec2 label_size = CalcTextSize(label, NULL, true); - const float symbol_spacing = (float)(int)(g.FontSize * 1.50f + 0.5f); - const float w = ImMax(label_size.x + symbol_spacing, window->Pos.x + ImGui::GetContentRegionMax().x - window->DC.CursorPos.x); // Feedback to next frame - bool ret = ImGui::Selectable(label, false, ImVec2(w, 0.0f)); + ImVec2 label_size = CalcTextSize(label, NULL, true); + ImVec2 shortcut_size = shortcut ? CalcTextSize(shortcut, NULL) : ImVec2(0.0f, 0.0f); + float w = window->MenuColumns.DeclColumns(label_size.x, shortcut_size.x, (float)(int)(g.FontSize * 1.20f)); // Feedback for next frame + float extra_w = ImMax(0.0f, window->Pos.x + ImGui::GetContentRegionMax().x - pos.x - w); - if (selected) + bool pressed = SelectableEx(label, false, ImVec2(w, 0.0f), ImVec2(0.0f, 0.0f), ImGuiSelectableFlags_MenuItem | (!enabled ? ImGuiSelectableFlags_Disabled : 0)); + if (shortcut_size.x > 0.0f) { - pos.x = window->Pos.x + ImGui::GetContentRegionMax().x - g.FontSize; - RenderCheckMark(pos, window->Color(ImGuiCol_Text)); + ImGui::PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); + RenderText(pos + ImVec2(window->MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false); + ImGui::PopStyleColor(); } - return ret; + if (selected) + RenderCheckMark(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.20f, 0.0f), window->Color(ImGuiCol_Text)); + + return pressed; } -bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected) +bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled) { - if (ImGui::MenuItem(label, shortcut, p_selected ? *p_selected : false)) + if (ImGui::MenuItem(label, shortcut, p_selected ? *p_selected : false, enabled)) { if (p_selected) *p_selected = !*p_selected; @@ -7258,6 +7553,174 @@ bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected) return false; } +bool ImGui::BeginMainMenuBar() +{ + ImGuiState& g = *GImGui; + ImGui::SetNextWindowPos(ImVec2(0.0f, 0.0f)); + ImGui::SetNextWindowSize(ImVec2(g.IO.DisplaySize.x, g.FontBaseSize + g.Style.FramePadding.y * 2.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0,0)); + if (!ImGui::Begin("##MainMenuBar", NULL, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_MenuBar) + || !ImGui::BeginMenuBar()) + { + ImGui::End(); + ImGui::PopStyleVar(2); + return false; + } + g.CurrentWindow->DC.MenuBarOffsetX += g.Style.DisplaySafeAreaPadding.x; + return true; +} + +void ImGui::EndMainMenuBar() +{ + ImGui::EndMenuBar(); + ImGui::End(); + ImGui::PopStyleVar(2); +} + +bool ImGui::BeginMenuBar() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + if (!(window->Flags & ImGuiWindowFlags_MenuBar)) + return false; + + IM_ASSERT(!window->DC.MenuBarAppending); + ImGui::BeginGroup(); // Save position + ImGui::PushID("##menubar"); + ImRect rect = window->MenuBarRect(); + PushClipRect(ImVec4(rect.Min.x+0.5f, rect.Min.y-0.5f, rect.Max.x+0.5f, rect.Max.y-1.5f), false); + window->DC.CursorPos = ImVec2(rect.Min.x + window->DC.MenuBarOffsetX, rect.Min.y);// + g.Style.FramePadding.y); + window->DC.LayoutType = ImGuiLayoutType_Horizontal; + window->DC.MenuBarAppending = true; + ImGui::AlignFirstTextHeightToWidgets(); + return true; +} + +void ImGui::EndMenuBar() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar); + IM_ASSERT(window->DC.MenuBarAppending); + PopClipRect(); + ImGui::PopID(); + window->DC.MenuBarOffsetX = window->DC.CursorPos.x - window->MenuBarRect().Min.x; + window->DC.GroupStack.back().AdvanceCursor = false; + ImGui::EndGroup(); + window->DC.LayoutType = ImGuiLayoutType_Vertical; + window->DC.MenuBarAppending = false; +} + +bool ImGui::BeginMenu(const char* label, bool enabled) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiState& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + + ImVec2 label_size = CalcTextSize(label, NULL, true); + ImGuiWindow* backed_focused_window = g.FocusedWindow; + + bool pressed; + bool opened = IsPopupOpen(id); + bool menuset_opened = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenedPopupStack.size() > g.CurrentPopupStack.size() && g.OpenedPopupStack[g.CurrentPopupStack.size()].ParentMenuSet == window->GetID("##menus")); + if (menuset_opened) + g.FocusedWindow = window; + + ImVec2 popup_pos, pos = window->DC.CursorPos; + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) + { + popup_pos = ImVec2(pos.x - window->WindowPadding().x, pos.y - style.FramePadding.y + window->MenuBarHeight()); + window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f); + float w = label_size.x; + pressed = SelectableEx(label, opened, ImVec2(w, 0.0f), ImVec2(w, 0.0f), ImGuiSelectableFlags_MenuItem | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0)); + ImGui::PopStyleVar(); + ImGui::SameLine(); + window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f); + } + else + { + popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y); + float w = window->MenuColumns.DeclColumns(label_size.x, 0.0f, (float)(int)(g.FontSize * 1.20f)); // Feedback to next frame + float extra_w = ImMax(0.0f, window->Pos.x + ImGui::GetContentRegionMax().x - pos.x - w); + pressed = SelectableEx(label, opened, ImVec2(w, 0.0f), ImVec2(0.0f, 0.0f), ImGuiSelectableFlags_MenuItem | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0)); + if (!enabled) ImGui::PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); + RenderCollapseTriangle(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.20f, 0.0f), false); + if (!enabled) ImGui::PopStyleColor(); + } + + bool hovered = enabled && IsHovered(window->DC.LastItemRect, id); + if (menuset_opened) + g.FocusedWindow = backed_focused_window; + + bool want_open = false; + if (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) + { + // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers so menus feel more reactive. + bool moving_within_opened_triangle = false; + if (g.HoveredWindow == window && g.OpenedPopupStack.size() > g.CurrentPopupStack.size() && g.OpenedPopupStack[g.CurrentPopupStack.size()].ParentWindow == window) + { + if (ImGuiWindow* next_window = g.OpenedPopupStack[g.CurrentPopupStack.size()].Window) + { + ImRect next_window_rect = next_window->Rect(); + ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta; + ImVec2 tb = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR(); + ImVec2 tc = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR(); + float extra = ImClamp(fabsf(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack. + ta.x += (window->Pos.x < next_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues + tb.y = ta.y + ImMax((tb.y - extra) - ta.y, -100.0f); // triangle is maximum 200 high to limit the slope and the bias toward large sub-menus + tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f); + moving_within_opened_triangle = ImIsPointInTriangle(g.IO.MousePos, ta, tb, tc); + //window->DrawList->PushClipRectFullScreen(); window->DrawList->AddTriangleFilled(ta, tb, tc, moving_within_opened_triangle ? 0x80008000 : 0x80000080); window->DrawList->PopClipRect(); // Debug + } + } + + if (opened && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_within_opened_triangle) + ClosePopup(label); + want_open = (!opened && hovered && !moving_within_opened_triangle) || (!opened && hovered && pressed); + } + else if (opened && pressed && menuset_opened) // menu-bar: click open menu to close + { + ClosePopup(label); + want_open = opened = false; + } + else if (pressed || (hovered && menuset_opened && !opened)) // menu-bar: first click to open, then hover to open others + want_open = true; + + if (!opened && want_open && g.OpenedPopupStack.size() > g.CurrentPopupStack.size()) + { + // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame. + ImGui::OpenPopup(label); + return false; + } + + opened |= want_open; + if (want_open) + ImGui::OpenPopup(label); + + if (opened) + { + ImGui::SetNextWindowPos(popup_pos, ImGuiSetCond_Always); + ImGuiWindowFlags flags = (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) ? ImGuiWindowFlags_ChildMenu|ImGuiWindowFlags_ChildWindow : ImGuiWindowFlags_ChildMenu; + opened = BeginPopupEx(label, flags); // opened can be 'false' when the popup is completely clipped (e.g. zero size display) + } + + return opened; +} + +void ImGui::EndMenu() +{ + ImGui::EndPopup(); +} + // A little colored square. Return true when clicked. bool ImGui::ColorButton(const ImVec4& col, bool small_height, bool outline_border) { @@ -7405,7 +7868,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], bool alpha) { ImGui::SameLine(0, (int)style.ItemInnerSpacing.x); const char* button_titles[3] = { "RGB", "HSV", "HEX" }; - if (ImGui::Button(button_titles[edit_mode])) + if (ButtonEx(button_titles[edit_mode], ImVec2(0,0), ImGuiButtonFlags_DontClosePopups)) g.ColorEditModeStorage.SetInt(id, (edit_mode + 1) % 3); // Don't set local copy of 'edit_mode' right away! ImGui::SameLine(); } @@ -7480,17 +7943,23 @@ void ImGui::Separator() } } -// A little vertical spacing. void ImGui::Spacing() { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; - ItemSize(ImVec2(0,0)); } -// Advance cursor given item size. +void ImGui::Dummy(const ImVec2& size) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + ItemSize(size); +} + +// Advance cursor given item size for layout. static void ItemSize(ImVec2 size, float text_offset_y) { ImGuiState& g = *GImGui; @@ -7538,6 +8007,9 @@ bool ImGui::IsRectClipped(const ImVec2& size) return IsClippedEx(ImRect(window->DC.CursorPos, window->DC.CursorPos + size), NULL, true); } +// Declare item bounding box for clipping and interaction. +// Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface +// declares their minimum size requirement to ItemSize() and then use a larger region for drawing/interaction, which is passed to ItemAdd(). static bool ItemAdd(const ImRect& bb, const ImGuiID* id) { ImGuiWindow* window = GetCurrentWindow(); @@ -7582,6 +8054,7 @@ void ImGui::BeginGroup() group_data.BackupCurrentLineHeight = window->DC.CurrentLineHeight; group_data.BackupCurrentLineTextBaseOffset = window->DC.CurrentLineTextBaseOffset; group_data.BackupLogLinePosY = window->DC.LogLinePosY; + group_data.AdvanceCursor = true; window->DC.ColumnsStartX = window->DC.CursorPos.x - window->Pos.x; window->DC.CursorMaxPos = window->DC.CursorPos; @@ -7605,12 +8078,16 @@ void ImGui::EndGroup() window->DC.CursorPos = group_data.BackupCursorPos; window->DC.CursorMaxPos = ImMax(group_data.BackupCursorMaxPos, window->DC.CursorMaxPos); window->DC.CurrentLineHeight = group_data.BackupCurrentLineHeight; - window->DC.CurrentLineTextBaseOffset = group_data.BackupCurrentLineTextBaseOffset; // FIXME: Ideally we'll grab the base offset from the first line of the group. + window->DC.CurrentLineTextBaseOffset = group_data.BackupCurrentLineTextBaseOffset; window->DC.ColumnsStartX = group_data.BackupColumnsStartX; window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; - ItemSize(group_bb.GetSize(), group_data.BackupCurrentLineTextBaseOffset); - ItemAdd(group_bb, NULL); + if (group_data.AdvanceCursor) + { + window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.PrevLineTextBaseOffset, group_data.BackupCurrentLineTextBaseOffset); // FIXME: Incorrect, we should grab the base offset from the *first line* of the group but it is hard to obtain now. + ItemSize(group_bb.GetSize(), group_data.BackupCurrentLineTextBaseOffset); + ItemAdd(group_bb, NULL); + } window->DC.GroupStack.pop_back(); @@ -8009,7 +8486,11 @@ void ImDrawList::UpdateClipRect() } else { - current_cmd->clip_rect = clip_rect_stack.empty() ? GNullClipRect : clip_rect_stack.back(); + ImVec4 current_clip_rect = clip_rect_stack.empty() ? GNullClipRect : clip_rect_stack.back(); + if (commands.size() > 2 && ImLengthSqr(commands[commands.size()-2].clip_rect - current_clip_rect) < 0.00001f) + commands.pop_back(); + else + current_cmd->clip_rect = current_clip_rect; } } @@ -8669,14 +9150,13 @@ bool ImFontAtlas::Build() stbtt_pack_range& range = data.Ranges[i]; for (int char_idx = 0; char_idx < range.num_chars_in_range; char_idx += 1) { - const int codepoint = range.first_unicode_char_in_range + char_idx; const stbtt_packedchar& pc = range.chardata_for_range[char_idx]; if (!pc.x0 && !pc.x1 && !pc.y0 && !pc.y1) continue; data.OutFont->Glyphs.resize(data.OutFont->Glyphs.size() + 1); ImFont::Glyph& glyph = data.OutFont->Glyphs.back(); - glyph.Codepoint = (ImWchar)codepoint; + glyph.Codepoint = (ImWchar)(range.first_unicode_char_in_range + char_idx); glyph.Width = (signed short)pc.x1 - pc.x0 + 1; glyph.Height = (signed short)pc.y1 - pc.y0 + 1; glyph.XOffset = (signed short)(pc.xoff); @@ -9643,12 +10123,12 @@ void ImGui::ShowUserGuide() if (g.IO.FontAllowUserScaling) ImGui::BulletText("CTRL+Mouse Wheel to zoom window contents."); ImGui::BulletText("TAB/SHIFT+TAB to cycle through keyboard editable fields."); - ImGui::BulletText("CTRL+Click on a slider to input text."); + ImGui::BulletText("CTRL+Click on a slider or drag box to input text."); ImGui::BulletText( "While editing text:\n" "- Hold SHIFT or use mouse to select text\n" "- CTRL+Left/Right to word jump\n" - "- CTRL+A select all\n" + "- CTRL+A or double-click to select all\n" "- CTRL+X,CTRL+C,CTRL+V clipboard\n" "- CTRL+Z,CTRL+Y undo/redo\n" "- ESCAPE to revert\n" @@ -9685,7 +10165,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) ImGui::SliderFloat2("ItemSpacing", (float*)&style.ItemSpacing, 0.0f, 20.0f, "%.0f"); ImGui::SliderFloat2("ItemInnerSpacing", (float*)&style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f"); ImGui::SliderFloat2("TouchExtraPadding", (float*)&style.TouchExtraPadding, 0.0f, 10.0f, "%.0f"); - ImGui::SliderFloat("IndentSpacing", &style.IndentSpacing, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat("IndentSpacing", &style.IndentSpacing, 0.0f, 30.0f, "%.0f"); ImGui::SliderFloat("ScrollbarWidth", &style.ScrollbarWidth, 1.0f, 20.0f, "%.0f"); ImGui::SliderFloat("ScrollbarRounding", &style.ScrollbarRounding, 0.0f, 16.0f, "%.0f"); ImGui::SliderFloat("GrabMinSize", &style.GrabMinSize, 1.0f, 20.0f, "%.0f"); @@ -9712,7 +10192,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) } ImGui::LogFinish(); } - ImGui::SameLine(); ImGui::PushItemWidth(150); ImGui::Combo("##output_type", &output_dest, "To Clipboard\0To TTY"); ImGui::PopItemWidth(); + ImGui::SameLine(); ImGui::PushItemWidth(120); ImGui::Combo("##output_type", &output_dest, "To Clipboard\0To TTY"); ImGui::PopItemWidth(); ImGui::SameLine(); ImGui::Checkbox("Only Modified Fields", &output_only_modified); static ImGuiColorEditMode edit_mode = ImGuiColorEditMode_RGB; @@ -9761,32 +10241,29 @@ static void ShowExampleAppAutoResize(bool* opened); static void ShowExampleAppFixedOverlay(bool* opened); static void ShowExampleAppManipulatingWindowTitle(bool* opened); static void ShowExampleAppCustomRendering(bool* opened); +static void ShowExampleAppMainMenuBar(); +static void ShowExampleMenuFile(); -// Demonstrate ImGui features (unfortunately this makes this function a little bloated!) +// Demonstrate most ImGui features (big function!) void ImGui::ShowTestWindow(bool* opened) { // Examples apps static bool show_app_metrics = false; + static bool show_app_main_menu_bar = false; static bool show_app_console = false; static bool show_app_long_text = false; static bool show_app_auto_resize = false; static bool show_app_fixed_overlay = false; static bool show_app_custom_rendering = false; static bool show_app_manipulating_window_title = false; - if (show_app_metrics) - ImGui::ShowMetricsWindow(&show_app_metrics); - if (show_app_console) - ShowExampleAppConsole(&show_app_console); - if (show_app_long_text) - ShowExampleAppLongText(&show_app_long_text); - if (show_app_auto_resize) - ShowExampleAppAutoResize(&show_app_auto_resize); - if (show_app_fixed_overlay) - ShowExampleAppFixedOverlay(&show_app_fixed_overlay); - if (show_app_manipulating_window_title) - ShowExampleAppManipulatingWindowTitle(&show_app_manipulating_window_title); - if (show_app_custom_rendering) - ShowExampleAppCustomRendering(&show_app_custom_rendering); + if (show_app_metrics) ImGui::ShowMetricsWindow(&show_app_metrics); + if (show_app_main_menu_bar) ShowExampleAppMainMenuBar(); + if (show_app_console) ShowExampleAppConsole(&show_app_console); + if (show_app_long_text) ShowExampleAppLongText(&show_app_long_text); + if (show_app_auto_resize) ShowExampleAppAutoResize(&show_app_auto_resize); + if (show_app_fixed_overlay) ShowExampleAppFixedOverlay(&show_app_fixed_overlay); + if (show_app_manipulating_window_title) ShowExampleAppManipulatingWindowTitle(&show_app_manipulating_window_title); + if (show_app_custom_rendering) ShowExampleAppCustomRendering(&show_app_custom_rendering); static bool no_titlebar = false; static bool no_border = true; @@ -9794,6 +10271,7 @@ void ImGui::ShowTestWindow(bool* opened) static bool no_move = false; static bool no_scrollbar = false; static bool no_collapse = false; + static bool no_menu = false; static float bg_alpha = 0.65f; // Demonstrate the various window flags. Typically you would just use the default. @@ -9804,6 +10282,7 @@ void ImGui::ShowTestWindow(bool* opened) if (no_move) window_flags |= ImGuiWindowFlags_NoMove; if (no_scrollbar) window_flags |= ImGuiWindowFlags_NoScrollbar; if (no_collapse) window_flags |= ImGuiWindowFlags_NoCollapse; + if (!no_menu) window_flags |= ImGuiWindowFlags_MenuBar; if (!ImGui::Begin("ImGui Test", opened, ImVec2(550,680), bg_alpha, window_flags)) { // Early out if the window is collapsed, as an optimization. @@ -9821,6 +10300,29 @@ void ImGui::ShowTestWindow(bool* opened) //ImGui::Text("WantCaptureMouse: %d", ImGui::GetIO().WantCaptureMouse); //ImGui::Text("WantCaptureKeyboard: %d", ImGui::GetIO().WantCaptureKeyboard); + // Menu + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("Menu")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Examples")) + { + ImGui::MenuItem("Metrics", NULL, &show_app_metrics); + ImGui::MenuItem("Main menu bar", NULL, &show_app_main_menu_bar); + ImGui::MenuItem("Console", NULL, &show_app_console); + ImGui::MenuItem("Long text display", NULL, &show_app_long_text); + ImGui::MenuItem("Auto-resizing window", NULL, &show_app_auto_resize); + ImGui::MenuItem("Simple overlay", NULL, &show_app_fixed_overlay); + ImGui::MenuItem("Manipulating window title", NULL, &show_app_manipulating_window_title); + ImGui::MenuItem("Custom rendering", NULL, &show_app_custom_rendering); + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + ImGui::Spacing(); if (ImGui::CollapsingHeader("Help")) { @@ -9836,6 +10338,7 @@ void ImGui::ShowTestWindow(bool* opened) ImGui::Checkbox("no move", &no_move); ImGui::SameLine(150); ImGui::Checkbox("no scrollbar", &no_scrollbar); ImGui::SameLine(300); ImGui::Checkbox("no collapse", &no_collapse); + ImGui::Checkbox("no menu", &no_menu); ImGui::SliderFloat("bg alpha", &bg_alpha, 0.0f, 1.0f); if (ImGui::TreeNode("Style")) @@ -9872,6 +10375,7 @@ void ImGui::ShowTestWindow(bool* opened) if (ImGui::TreeNode("Logging")) { + ImGui::TextWrapped("The logging API redirects all text output of ImGui so you can easily capture the content of a window or a block. Tree nodes can be automatically expanded. You can also call ImGui::LogText() to output directly to the log without a visual output."); ImGui::LogButtons(); ImGui::TreePop(); } @@ -9908,8 +10412,7 @@ void ImGui::ShowTestWindow(bool* opened) ImGui::BulletText("Bullet point 1"); ImGui::BulletText("Bullet point 2\nOn multiple lines"); ImGui::Bullet(); ImGui::Text("Bullet point 3 (two calls)"); - ImGui::Bullet(); ImGui::SmallButton("Button 1"); - ImGui::Bullet(); ImGui::SmallButton("Button 2"); + ImGui::Bullet(); ImGui::SmallButton("Button"); ImGui::TreePop(); } @@ -9918,13 +10421,14 @@ void ImGui::ShowTestWindow(bool* opened) // Using shortcut. You can use PushStyleColor()/PopStyleColor() for more flexibility. ImGui::TextColored(ImVec4(1.0f,0.0f,1.0f,1.0f), "Pink"); ImGui::TextColored(ImVec4(1.0f,1.0f,0.0f,1.0f), "Yellow"); + ImGui::TextDisabled("Disabled"); ImGui::TreePop(); } if (ImGui::TreeNode("Word Wrapping")) { // Using shortcut. You can use PushTextWrapPos()/PopTextWrapPos() for more flexibility. - ImGui::TextWrapped("This text should automatically wrap on the edge of the window. The current implementation for text wrapping follows simple rules that works for English and possibly other languages."); + ImGui::TextWrapped("This text should automatically wrap on the edge of the window. The current implementation for text wrapping follows simple rules suitable for English and possibly other languages."); ImGui::Spacing(); static float wrap_width = 200.0f; @@ -9969,7 +10473,7 @@ void ImGui::ShowTestWindow(bool* opened) ImGui::TextWrapped("On a per-widget basis we are occasionally clipping text if it won't fit in its frame."); ImGui::SliderFloat2("size", (float*)&size, 5.0f, 200.0f); ImGui::Button("Line 1 hello\nLine 2 clip me!", size); - ImGui::TextWrapped("Otherwise we are doing coarser clipping + passing a scissor rectangle to the renderer. The system is designed to try minimizing both execution and rendering cost."); + ImGui::TextWrapped("Otherwise we are doing coarser clipping + passing a scissor rectangle to the renderer. The system is designed to try minimizing both execution and CPU/GPU rendering cost."); ImGui::TreePop(); } @@ -10053,52 +10557,66 @@ void ImGui::ShowTestWindow(bool* opened) if (ImGui::TreeNode("Popup, Menus")) { + ImGui::TextWrapped("When a popup is active, it inhibits interacting with windows that are behind the popup. Clicking outside the popup closes it."); + static int selected_fish = -1; const char* names[] = { "Bream", "Haddock", "Mackerel", "Pollock", "Tilefish" }; static bool toggles[] = { true, false, false, false, false }; + if (ImGui::Button("Select..")) + ImGui::OpenPopup("select"); + ImGui::SameLine(); + ImGui::Text(selected_fish == -1 ? "" : names[selected_fish]); + if (ImGui::BeginPopup("select")) { - static bool popup_open = false; - if (ImGui::Button("Select..")) - popup_open = true; - ImGui::SameLine(); - ImGui::Text(selected_fish == -1 ? "" : names[selected_fish]); - if (popup_open) + ImGui::Text("Aquarium"); + ImGui::Separator(); + for (int i = 0; i < IM_ARRAYSIZE(names); i++) + if (ImGui::Selectable(names[i])) + selected_fish = i; + ImGui::EndPopup(); + } + + if (ImGui::Button("Toggle..")) + ImGui::OpenPopup("toggle"); + if (ImGui::BeginPopup("toggle")) + { + for (int i = 0; i < IM_ARRAYSIZE(names); i++) + ImGui::MenuItem(names[i], "", &toggles[i]); + if (ImGui::BeginMenu("Sub-menu")) + { + ImGui::MenuItem("Click me"); + ImGui::EndMenu(); + } + + ImGui::Separator(); + ImGui::Text("Tooltip here"); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("I am a tooltip over a popup"); + + if (ImGui::Button("Stacked Popup")) + ImGui::OpenPopup("another popup"); + if (ImGui::BeginPopup("another popup")) { - ImGui::BeginPopup(&popup_open); - ImGui::Text("Aquarium"); - ImGui::Separator(); for (int i = 0; i < IM_ARRAYSIZE(names); i++) + ImGui::MenuItem(names[i], "", &toggles[i]); + if (ImGui::BeginMenu("Sub-menu")) { - if (ImGui::Selectable(names[i])) - { - selected_fish = i; - popup_open = false; - } + ImGui::MenuItem("Click me"); + ImGui::EndMenu(); } ImGui::EndPopup(); } + ImGui::EndPopup(); } + + if (ImGui::Button("Popup Menu..")) + ImGui::OpenPopup("context menu"); + if (ImGui::BeginPopup("context menu")) { - static bool popup_open = false; - if (ImGui::Button("Toggle..")) - popup_open = true; - if (popup_open) - { - ImGui::BeginPopup(&popup_open); - for (int i = 0; i < IM_ARRAYSIZE(names); i++) - if (ImGui::MenuItem(names[i], "", &toggles[i])) - popup_open = false; - - ImGui::Separator(); - ImGui::Text("Tooltip here"); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("I am a tooltip over a popup"); - - ImGui::EndPopup(); - } + ShowExampleMenuFile(); + ImGui::EndPopup(); } - ImGui::TreePop(); } @@ -10175,7 +10693,11 @@ void ImGui::ShowTestWindow(bool* opened) static int i0=123; static float f0=0.001f; ImGui::InputText("input text", str0, IM_ARRAYSIZE(str0)); + ImGui::SameLine(); ImGui::TextDisabled("(?)"); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Hold SHIFT or use mouse to select text.\n" "CTRL+Left/Right to word jump.\n" "CTRL+A or double-click to select all.\n" "CTRL+X,CTRL+C,CTRL+V clipboard.\n" "CTRL+Z,CTRL+Y undo/redo.\n" "ESCAPE to revert.\n"); + ImGui::InputInt("input int", &i0); + ImGui::SameLine(); ImGui::TextDisabled("(?)"); if (ImGui::IsItemHovered()) ImGui::SetTooltip("You can apply arithmetic operators +,*,/ on numerical values.\n e.g. [ 100 ], input \'*2\', result becomes [ 200 ]\nUse +- to subtract.\n"); + ImGui::InputFloat("input float", &f0, 0.01f, 1.0f); static float vec4a[4] = { 0.10f, 0.20f, 0.30f, 0.44f }; @@ -10186,10 +10708,7 @@ void ImGui::ShowTestWindow(bool* opened) static int i1=50; static int i2=42; ImGui::DragInt("drag int", &i1, 1); - ImGui::SameLine(); - ImGui::TextColored(ImColor(170,170,170,255), "(?)"); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Click and drag to edit value.\nHold SHIFT/ALT for faster/slower edit.\nDouble-click or CTRL+click to input text"); + ImGui::SameLine(); ImGui::TextDisabled("(?)"); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Click and drag to edit value.\nHold SHIFT/ALT for faster/slower edit.\nDouble-click or CTRL+click to input text."); ImGui::DragInt("drag int 0..100", &i2, 1, 0, 100, "%.0f%%"); @@ -10202,7 +10721,9 @@ void ImGui::ShowTestWindow(bool* opened) { static int i1=0; //static int i2=42; - ImGui::SliderInt("slider int 0..3", &i1, 0, 3); + ImGui::SliderInt("slider int", &i1, 0, 3); + ImGui::SameLine(); ImGui::TextDisabled("(?)"); if (ImGui::IsItemHovered()) ImGui::SetTooltip("CTRL+click to input value."); + //ImGui::SliderInt("slider int -100..100", &i2, -100, 100); static float f1=0.123f; @@ -10216,6 +10737,8 @@ void ImGui::ShowTestWindow(bool* opened) static float col1[3] = { 1.0f,0.0f,0.2f }; static float col2[4] = { 0.4f,0.7f,0.0f,0.5f }; ImGui::ColorEdit3("color 1", col1); + ImGui::SameLine(); ImGui::TextDisabled("(?)"); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Click on the colored square to change edit mode. CTRL+click on individual component to input value.\n"); + ImGui::ColorEdit4("color 2", col2); const char* listbox_items[] = { "Apple", "Banana", "Cherry", "Kiwi", "Mango", "Orange", "Pineapple", "Strawberry", "Watermelon" }; @@ -10240,6 +10763,7 @@ void ImGui::ShowTestWindow(bool* opened) ImGui::DragInt2("drag int2", vec4i, 1, 0, 255); ImGui::InputInt2("input int2", vec4i); ImGui::SliderInt2("slider int2", vec4i, 0, 255); + ImGui::Spacing(); ImGui::InputFloat3("input float3", vec4f); ImGui::DragFloat3("drag float3", vec4f, 0.01f, 0.0f, 1.0f); @@ -10247,6 +10771,7 @@ void ImGui::ShowTestWindow(bool* opened) ImGui::DragInt3("drag int3", vec4i, 1, 0, 255); ImGui::InputInt3("input int3", vec4i); ImGui::SliderInt3("slider int3", vec4i, 0, 255); + ImGui::Spacing(); ImGui::InputFloat4("input float4", vec4f); ImGui::DragFloat4("drag float4", vec4f, 0.01f, 0.0f, 1.0f); @@ -10467,6 +10992,12 @@ void ImGui::ShowTestWindow(bool* opened) } ImGui::PopItemWidth(); + // Dummy + ImVec2 sz(30,30); + ImGui::Button("A", sz); ImGui::SameLine(); + ImGui::Dummy(sz); ImGui::SameLine(); + ImGui::Button("B", sz); + ImGui::TreePop(); } @@ -10788,7 +11319,7 @@ void ImGui::ShowTestWindow(bool* opened) if (ImGui::TreeNode("Mouse cursors")) { - ImGui::TextWrapped("(Your application can render a different mouse cursor based on what ImGui::GetMouseCursor() returns. You can also set io.MouseDrawCursor to ask ImGui to render the cursor for you in software)"); + ImGui::TextWrapped("Your application can render a different mouse cursor based on what ImGui::GetMouseCursor() returns. You can also set io.MouseDrawCursor to ask ImGui to render the cursor for you in software."); ImGui::Checkbox("io.MouseDrawCursor", &ImGui::GetIO().MouseDrawCursor); ImGui::Text("Hover to see mouse cursors:"); for (int i = 0; i < ImGuiMouseCursor_Count_; i++) @@ -10803,17 +11334,6 @@ void ImGui::ShowTestWindow(bool* opened) } } - if (ImGui::CollapsingHeader("App Examples")) - { - ImGui::Checkbox("Metrics", &show_app_metrics); - ImGui::Checkbox("Console", &show_app_console); - ImGui::Checkbox("Long text display", &show_app_long_text); - ImGui::Checkbox("Auto-resizing window", &show_app_auto_resize); - ImGui::Checkbox("Simple overlay", &show_app_fixed_overlay); - ImGui::Checkbox("Manipulating window title", &show_app_manipulating_window_title); - ImGui::Checkbox("Custom rendering", &show_app_custom_rendering); - } - ImGui::End(); } @@ -10824,6 +11344,7 @@ void ImGui::ShowMetricsWindow(bool* opened) ImGui::Text("ImGui %s", ImGui::GetVersion()); ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); ImGui::Text("%d vertices", ImGui::GetIO().MetricsRenderVertices); + ImGui::Text("%d allocations", ImGui::GetIO().MetricsAllocs); ImGui::Separator(); struct Funcs @@ -10875,11 +11396,93 @@ void ImGui::ShowMetricsWindow(bool* opened) Funcs::NodeDrawList(g.RenderDrawLists[0][i], "DrawList"); ImGui::TreePop(); } + if (ImGui::TreeNode("Popups", "Opened Popups (%d)", (int)g.OpenedPopupStack.size())) + { + for (int i = 0; i < (int)g.OpenedPopupStack.size(); i++) + ImGui::BulletText("PopupID: %08x, Window: '%s'", g.OpenedPopupStack[i].PopupID, g.OpenedPopupStack[i].Window ? g.OpenedPopupStack[i].Window->Name : "NULL"); + ImGui::TreePop(); + } g.DisableHideTextAfterDoubleHash--; } ImGui::End(); } +static void ShowExampleAppMainMenuBar() +{ + if (ImGui::BeginMainMenuBar()) + { + if (ImGui::BeginMenu("File")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Edit")) + { + if (ImGui::MenuItem("Undo", "CTRL+Z")) {} + if (ImGui::MenuItem("Redo", "CTRL+Y", false, false)) {} // Disabled item + ImGui::Separator(); + if (ImGui::MenuItem("Cut", "CTRL+X")) {} + if (ImGui::MenuItem("Copy", "CTRL+C")) {} + if (ImGui::MenuItem("Paste", "CTRL+V")) {} + ImGui::EndMenu(); + } + ImGui::EndMainMenuBar(); + } +} + +static void ShowExampleMenuFile() +{ + ImGui::MenuItem("(dummy menu)", NULL, false, false); + if (ImGui::MenuItem("New")) {} + if (ImGui::MenuItem("Open", "Ctrl+O")) {} + if (ImGui::BeginMenu("Open Recent")) + { + ImGui::MenuItem("fish_hat.c"); + ImGui::MenuItem("fish_hat.inl"); + ImGui::MenuItem("fish_hat.h"); + if (ImGui::BeginMenu("More..")) + { + ImGui::MenuItem("Hello"); + ImGui::MenuItem("Sailor"); + if (ImGui::BeginMenu("Recurse..")) + { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + ImGui::EndMenu(); + } + ImGui::EndMenu(); + } + if (ImGui::MenuItem("Save", "Ctrl+S")) {} + if (ImGui::MenuItem("Save As..")) {} + ImGui::Separator(); + if (ImGui::BeginMenu("Options")) + { + static bool enabled = true; + ImGui::MenuItem("Enabled", "", &enabled); + ImGui::BeginChild("child", ImVec2(0, 60), true); + for (int i = 0; i < 10; i++) + ImGui::Text("Scrolling Text %d", i); + ImGui::EndChild(); + static float f = 0.5f; + ImGui::SliderFloat("Value", &f, 0.0f, 1.0f); + ImGui::InputFloat("Input", &f, 0.1f); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Colors")) + { + for (int i = 0; i < ImGuiCol_COUNT; i++) + ImGui::MenuItem(ImGui::GetStyleColName((ImGuiCol)i)); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Disabled", false)) // Disabled + { + IM_ASSERT(0); + } + if (ImGui::MenuItem("Checked", NULL, true)) {} + if (ImGui::MenuItem("Quit", "Alt+F4")) {} +} + static void ShowExampleAppAutoResize(bool* opened) { if (!ImGui::Begin("Example: Auto-Resizing Window", opened, ImGuiWindowFlags_AlwaysAutoResize)) @@ -11024,13 +11627,13 @@ struct ExampleAppConsole { ClearLog(); for (size_t i = 0; i < Items.size(); i++) - ImGui::MemFree(History[i]); + free(History[i]); } void ClearLog() { for (size_t i = 0; i < Items.size(); i++) - ImGui::MemFree(Items[i]); + free(Items[i]); Items.clear(); ScrollToBottom = true; } @@ -11042,7 +11645,7 @@ struct ExampleAppConsole va_start(args, fmt); ImFormatStringV(buf, IM_ARRAYSIZE(buf), fmt, args); va_end(args); - Items.push_back(ImStrdup(buf)); + Items.push_back(strdup(buf)); ScrollToBottom = true; } @@ -11125,11 +11728,11 @@ struct ExampleAppConsole for (int i = (int)History.size()-1; i >= 0; i--) if (ImStricmp(History[i], command_line) == 0) { - ImGui::MemFree(History[i]); + free(History[i]); History.erase(History.begin() + i); break; } - History.push_back(ImStrdup(command_line)); + History.push_back(strdup(command_line)); // Process command if (ImStricmp(command_line, "CLEAR") == 0) diff --git a/3rdparty/ocornut-imgui/imgui.h b/3rdparty/ocornut-imgui/imgui.h index 19a233fb..0522b0c7 100644 --- a/3rdparty/ocornut-imgui/imgui.h +++ b/3rdparty/ocornut-imgui/imgui.h @@ -73,73 +73,8 @@ struct ImVec4 #endif }; -namespace ImGui -{ - // Proxy functions to access the MemAllocFn/MemFreeFn pointers in ImGui::GetIO(). The only reason they exist here is to allow ImVector<> to compile inline. - IMGUI_API void* MemAlloc(size_t sz); - IMGUI_API void MemFree(void* ptr); -} - -// Lightweight std::vector<> like class to avoid dragging dependencies (also: windows implementation of STL with debug enabled is absurdly slow, so let's bypass it so our code runs fast in debug). -// Use '#define ImVector std::vector' if you want to use the STL type or your own type. -// Our implementation does NOT call c++ constructors because we don't use them in ImGui. Don't use this class as a straight std::vector replacement in your code! -#ifndef ImVector -template -class ImVector -{ -protected: - size_t Size; - size_t Capacity; - T* Data; - -public: - typedef T value_type; - typedef value_type* iterator; - typedef const value_type* const_iterator; - - ImVector() { Size = Capacity = 0; Data = NULL; } - ~ImVector() { if (Data) ImGui::MemFree(Data); } - - inline bool empty() const { return Size == 0; } - inline size_t size() const { return Size; } - inline size_t capacity() const { return Capacity; } - - inline value_type& at(size_t i) { IM_ASSERT(i < Size); return Data[i]; } - inline const value_type& at(size_t i) const { IM_ASSERT(i < Size); return Data[i]; } - inline value_type& operator[](size_t i) { IM_ASSERT(i < Size); return Data[i]; } - inline const value_type& operator[](size_t i) const { IM_ASSERT(i < Size); return Data[i]; } - - inline void clear() { if (Data) { Size = Capacity = 0; ImGui::MemFree(Data); Data = NULL; } } - inline iterator begin() { return Data; } - inline const_iterator begin() const { return Data; } - inline iterator end() { return Data + Size; } - inline const_iterator end() const { return Data + Size; } - inline value_type& front() { IM_ASSERT(Size > 0); return Data[0]; } - inline const value_type& front() const { IM_ASSERT(Size > 0); return Data[0]; } - inline value_type& back() { IM_ASSERT(Size > 0); return Data[Size-1]; } - inline const value_type& back() const { IM_ASSERT(Size > 0); return Data[Size-1]; } - inline void swap(ImVector& rhs) { const size_t rhs_size = rhs.Size; rhs.Size = Size; Size = rhs_size; const size_t rhs_cap = rhs.Capacity; rhs.Capacity = Capacity; Capacity = rhs_cap; value_type* rhs_data = rhs.Data; rhs.Data = Data; Data = rhs_data; } - - inline void resize(size_t new_size) { if (new_size > Capacity) reserve(new_size); Size = new_size; } - inline void reserve(size_t new_capacity) - { - if (new_capacity <= Capacity) return; - T* new_data = (value_type*)ImGui::MemAlloc(new_capacity * sizeof(value_type)); - memcpy(new_data, Data, Size * sizeof(value_type)); - ImGui::MemFree(Data); - Data = new_data; - Capacity = new_capacity; - } - - inline void push_back(const value_type& v) { if (Size == Capacity) reserve(Capacity ? Capacity * 2 : 4); Data[Size++] = v; } - inline void pop_back() { IM_ASSERT(Size > 0); Size--; } - - inline iterator erase(const_iterator it) { IM_ASSERT(it >= begin() && it < end()); const ptrdiff_t off = it - begin(); memmove(Data + off, Data + off + 1, (Size - (size_t)off - 1) * sizeof(value_type)); Size--; return Data + off; } - inline iterator insert(const_iterator it, const value_type& v) { IM_ASSERT(it >= begin() && it <= end()); const ptrdiff_t off = it - begin(); if (Size == Capacity) reserve(Capacity ? Capacity * 2 : 4); if (off < (int)Size) memmove(Data + off + 1, Data + off, (Size - (size_t)off) * sizeof(value_type)); Data[off] = v; Size++; return Data + off; } -}; -#endif // #ifndef ImVector - // Helpers at bottom of the file: +// - class ImVector<> // Lightweight std::vector like class. Use '#define ImVector std::vector' if you want to use the STL type or your own type. // - IMGUI_ONCE_UPON_A_FRAME // Execute a block of code once per frame only (convenient for creating UI within deep-nested code that runs multiple times) // - struct ImGuiTextFilter // Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" // - struct ImGuiTextBuffer // Text buffer for logging/accumulating text @@ -222,6 +157,8 @@ namespace ImGui IMGUI_API void PopAllowKeyboardFocus(); IMGUI_API void PushTextWrapPos(float wrap_pos_x = 0.0f); // word-wrapping for Text*() commands. < 0.0f: no wrapping; 0.0f: wrap to end of window (or column); > 0.0f: wrap at 'wrap_pos_x' position in window local space IMGUI_API void PopTextWrapPos(); + IMGUI_API void PushButtonRepeat(bool repeat); // in 'repeat' mode, Button*() functions return true multiple times as you hold them (uses io.KeyRepeatDelay/io.KeyRepeatRate for now) + IMGUI_API void PopButtonRepeat(); // Tooltip IMGUI_API void SetTooltip(const char* fmt, ...); // set tooltip under mouse-cursor, typically use with ImGui::IsHovered(). last call wins @@ -230,15 +167,18 @@ namespace ImGui IMGUI_API void EndTooltip(); // Popup - IMGUI_API void BeginPopup(bool* p_opened); + IMGUI_API void OpenPopup(const char* str_id); // mark popup as open. popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level). close childs popups if any. will close popup when user click outside, or activate a pressable item, or CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block. + IMGUI_API bool BeginPopup(const char* str_id); // return true if popup if opened and start outputting to it. only call EndPopup() if BeginPopup() returned true! IMGUI_API void EndPopup(); + IMGUI_API void CloseCurrentPopup(); // close the popup we have begin-ed into. clicking on a MenuItem or Selectable automatically close the current popup. // Layout - IMGUI_API void BeginGroup(); + IMGUI_API void BeginGroup(); // once closing a group it is seen as a single item (so you can use IsItemHovered() on a group, SameLine() between groups, etc. IMGUI_API void EndGroup(); IMGUI_API void Separator(); // horizontal line IMGUI_API void SameLine(int column_x = 0, int spacing_w = -1); // call between widgets or groups to layout them horizontally - IMGUI_API void Spacing(); // add vertical spacing + IMGUI_API void Spacing(); // add spacing + IMGUI_API void Dummy(const ImVec2& size); // add a dummy item of given size IMGUI_API void Indent(); // move content position toward the right by style.IndentSpacing pixels IMGUI_API void Unindent(); // move content position back to the left (cancel Indent) IMGUI_API void Columns(int count = 1, const char* id = NULL, bool border=true); // setup number of columns. use an identifier to distinguish multiple column sets. close with Columns(1). @@ -277,7 +217,9 @@ namespace ImGui IMGUI_API void TextV(const char* fmt, va_list args); IMGUI_API void TextColored(const ImVec4& col, const char* fmt, ...); // shortcut for PushStyleColor(ImGuiCol_Text, col); Text(fmt, ...); PopStyleColor(); IMGUI_API void TextColoredV(const ImVec4& col, const char* fmt, va_list args); - IMGUI_API void TextWrapped(const char* fmt, ...); // shortcut for PushTextWrapPos(0.0f); Text(fmt, ...); PopTextWrapPos(); + IMGUI_API void TextDisabled(const char* fmt, ...); // shortcut for PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); Text(fmt, ...); PopStyleColor(); + IMGUI_API void TextDisabledV(const char* fmt, va_list args); + IMGUI_API void TextWrapped(const char* fmt, ...); // shortcut for PushTextWrapPos(0.0f); Text(fmt, ...); PopTextWrapPos();. Note that this won't work on an auto-resizing window if there's no other widgets to extend the window width, yoy may need to set a size using SetNextWindowSize(). IMGUI_API void TextWrappedV(const char* fmt, va_list args); IMGUI_API void TextUnformatted(const char* text, const char* text_end = NULL); // doesn't require null terminated string if 'text_end' is specified. no copy done to any bounded stack buffer, recommended for long chunks of text IMGUI_API void LabelText(const char* label, const char* fmt, ...); // display text+label aligned the same way as value+label widgets @@ -285,7 +227,7 @@ namespace ImGui IMGUI_API void Bullet(); IMGUI_API void BulletText(const char* fmt, ...); IMGUI_API void BulletTextV(const char* fmt, va_list args); - IMGUI_API bool Button(const char* label, const ImVec2& size = ImVec2(0,0), bool repeat_when_held = false); + IMGUI_API bool Button(const char* label, const ImVec2& size = ImVec2(0,0)); IMGUI_API bool SmallButton(const char* label); IMGUI_API bool InvisibleButton(const char* str_id, const ImVec2& size); IMGUI_API void Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0,0), const ImVec2& uv1 = ImVec2(1,1), const ImVec4& tint_col = ImVec4(1,1,1,1), const ImVec4& border_col = ImVec4(0,0,0,0)); @@ -362,9 +304,14 @@ namespace ImGui IMGUI_API void ListBoxFooter(); // terminate the scrolling region // Widgets: Menus - // FIXME-WIP: v1.39 in development - IMGUI_API bool MenuItem(const char* label, const char* shortcut = NULL, bool selected = false); // bool enabled = true - IMGUI_API bool MenuItem(const char* label, const char* shortcut, bool* p_selected); // bool enabled = true + IMGUI_API bool BeginMainMenuBar(); // create and append to a full screen menu-bar. only call EndMainMenuBar() if this returns true! + IMGUI_API void EndMainMenuBar(); + IMGUI_API bool BeginMenuBar(); // append to menu-bar of current window. only call EndMenuBar() if this returns true! + IMGUI_API void EndMenuBar(); + IMGUI_API bool BeginMenu(const char* label, bool enabled = true); // create a sub-menu entry. only call EndMenu() if this returns true! + IMGUI_API void EndMenu(); + IMGUI_API bool MenuItem(const char* label, const char* shortcut = NULL, bool selected = false, bool enabled = true); // return true when activated. shortcuts are displayed for convenience but not processed by ImGui + IMGUI_API bool MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled = true); // return true when activated + toggle (*p_selected) if p_selected != NULL // Widgets: Value() Helpers. Output single value in "name: value" format (tip: freely declare your own within the ImGui namespace!) IMGUI_API void Value(const char* prefix, bool b); @@ -424,6 +371,10 @@ namespace ImGui IMGUI_API void ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v); IMGUI_API void ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b); + // Proxy functions to access the MemAllocFn/MemFreeFn pointers in ImGui::GetIO() + IMGUI_API void* MemAlloc(size_t sz); + IMGUI_API void MemFree(void* ptr); + // Internal state access - if you want to share ImGui state between modules (e.g. DLL) or allocate it yourself IMGUI_API const char* GetVersion(); IMGUI_API void* GetInternalState(); @@ -454,13 +405,15 @@ enum ImGuiWindowFlags_ ImGuiWindowFlags_AlwaysAutoResize = 1 << 6, // Resize every window to its content every frame ImGuiWindowFlags_ShowBorders = 1 << 7, // Show borders around windows and items ImGuiWindowFlags_NoSavedSettings = 1 << 8, // Never load/save settings in .ini file + ImGuiWindowFlags_MenuBar = 1 << 9, // Has a menubar // [Internal] - ImGuiWindowFlags_ChildWindow = 1 << 9, // For internal use by BeginChild() - ImGuiWindowFlags_ChildWindowAutoFitX = 1 << 10, // For internal use by BeginChild() - ImGuiWindowFlags_ChildWindowAutoFitY = 1 << 11, // For internal use by BeginChild() - ImGuiWindowFlags_ComboBox = 1 << 12, // For internal use by ComboBox() - ImGuiWindowFlags_Tooltip = 1 << 13, // For internal use by BeginTooltip() - ImGuiWindowFlags_Popup = 1 << 14 // For internal use by BeginPopup() + ImGuiWindowFlags_ChildWindow = 1 << 20, // Don't use! For internal use by BeginChild() + ImGuiWindowFlags_ChildWindowAutoFitX = 1 << 21, // Don't use! For internal use by BeginChild() + ImGuiWindowFlags_ChildWindowAutoFitY = 1 << 22, // Don't use! For internal use by BeginChild() + ImGuiWindowFlags_ComboBox = 1 << 23, // Don't use! For internal use by ComboBox() + ImGuiWindowFlags_Tooltip = 1 << 24, // Don't use! For internal use by BeginTooltip() + ImGuiWindowFlags_Popup = 1 << 25, // Don't use! For internal use by BeginPopup() + ImGuiWindowFlags_ChildMenu = 1 << 26 // Don't use! For internal use by BeginMenu() }; // Flags for ImGui::InputText() @@ -506,6 +459,7 @@ enum ImGuiKey_ enum ImGuiCol_ { ImGuiCol_Text, + ImGuiCol_TextDisabled, ImGuiCol_WindowBg, ImGuiCol_ChildWindowBg, ImGuiCol_Border, @@ -515,6 +469,7 @@ enum ImGuiCol_ ImGuiCol_FrameBgActive, ImGuiCol_TitleBg, ImGuiCol_TitleBgCollapsed, + ImGuiCol_MenuBarBg, ImGuiCol_ScrollbarBg, ImGuiCol_ScrollbarGrab, ImGuiCol_ScrollbarGrabHovered, @@ -554,6 +509,7 @@ enum ImGuiStyleVar_ ImGuiStyleVar_Alpha, // float ImGuiStyleVar_WindowPadding, // ImVec2 ImGuiStyleVar_WindowRounding, // float + ImGuiStyleVar_WindowMinSize, // ImVec2 ImGuiStyleVar_ChildWindowRounding, // float ImGuiStyleVar_FramePadding, // ImVec2 ImGuiStyleVar_FrameRounding, // float @@ -568,7 +524,9 @@ enum ImGuiAlign_ ImGuiAlign_Left = 1 << 0, ImGuiAlign_Center = 1 << 1, ImGuiAlign_Right = 1 << 2, - ImGuiAlign_Default = ImGuiAlign_Left, + ImGuiAlign_Top = 1 << 3, + ImGuiAlign_VCenter = 1 << 4, + ImGuiAlign_Default = ImGuiAlign_Left | ImGuiAlign_Top, }; // Enumeration for ColorEditMode() @@ -647,6 +605,8 @@ struct ImGuiIO float MouseDoubleClickMaxDist; // = 6.0f // Distance threshold to stay in to validate a double-click, in pixels. float MouseDragThreshold; // = 6.0f // Distance threshold before considering we are dragging int KeyMap[ImGuiKey_COUNT]; // // Map of indices into the KeysDown[512] entries array + float KeyRepeatDelay; // = 0.250f // When holding a key/button, time before it starts repeating, in seconds. (for actions where 'repeat' is active) + float KeyRepeatRate; // = 0.020f // When holding a key/button, rate at which it repeats, in seconds. void* UserData; // = NULL // Store your own data for retrieval by callbacks. ImFontAtlas* Fonts; // // Load and assemble one or more fonts into a single tightly packed texture. Output to Fonts array. @@ -702,6 +662,7 @@ struct ImGuiIO bool WantCaptureMouse; // Mouse is hovering a window or widget is active (= ImGui will use your mouse input) bool WantCaptureKeyboard; // Widget is active (= ImGui will use your keyboard input) float Framerate; // Framerate estimation, in frame per second. Rolling average estimation based on IO.DeltaTime over 120 frames + int MetricsAllocs; // Number of active memory allocations int MetricsRenderVertices; // Vertices processed during last call to Render() int MetricsActiveWindows; // Number of visible windows (exclude child windows) @@ -727,6 +688,67 @@ struct ImGuiIO // Helpers //----------------------------------------------------------------------------- +// Lightweight std::vector<> like class to avoid dragging dependencies (also: windows implementation of STL with debug enabled is absurdly slow, so let's bypass it so our code runs fast in debug). +// Use '#define ImVector std::vector' if you want to use the STL type or your own type. +// Our implementation does NOT call c++ constructors because we don't use them in ImGui. Don't use this class as a straight std::vector replacement in your code! +#ifndef ImVector +template +class ImVector +{ +protected: + size_t Size; + size_t Capacity; + T* Data; + +public: + typedef T value_type; + typedef value_type* iterator; + typedef const value_type* const_iterator; + + ImVector() { Size = Capacity = 0; Data = NULL; } + ~ImVector() { if (Data) ImGui::MemFree(Data); } + + inline bool empty() const { return Size == 0; } + inline size_t size() const { return Size; } + inline size_t capacity() const { return Capacity; } + + inline value_type& at(size_t i) { IM_ASSERT(i < Size); return Data[i]; } + inline const value_type& at(size_t i) const { IM_ASSERT(i < Size); return Data[i]; } + inline value_type& operator[](size_t i) { IM_ASSERT(i < Size); return Data[i]; } + inline const value_type& operator[](size_t i) const { IM_ASSERT(i < Size); return Data[i]; } + + inline void clear() { if (Data) { Size = Capacity = 0; ImGui::MemFree(Data); Data = NULL; } } + inline iterator begin() { return Data; } + inline const_iterator begin() const { return Data; } + inline iterator end() { return Data + Size; } + inline const_iterator end() const { return Data + Size; } + inline value_type& front() { IM_ASSERT(Size > 0); return Data[0]; } + inline const value_type& front() const { IM_ASSERT(Size > 0); return Data[0]; } + inline value_type& back() { IM_ASSERT(Size > 0); return Data[Size-1]; } + inline const value_type& back() const { IM_ASSERT(Size > 0); return Data[Size-1]; } + inline void swap(ImVector& rhs) { const size_t rhs_size = rhs.Size; rhs.Size = Size; Size = rhs_size; const size_t rhs_cap = rhs.Capacity; rhs.Capacity = Capacity; Capacity = rhs_cap; value_type* rhs_data = rhs.Data; rhs.Data = Data; Data = rhs_data; } + + inline size_t _grow_capacity(size_t new_size) { size_t new_capacity = Capacity ? (Capacity + Capacity/2) : 8; return new_capacity > new_size ? new_capacity : new_size; } + + inline void resize(size_t new_size) { if (new_size > Capacity) reserve(_grow_capacity(new_size)); Size = new_size; } + inline void reserve(size_t new_capacity) + { + if (new_capacity <= Capacity) return; + T* new_data = (value_type*)ImGui::MemAlloc(new_capacity * sizeof(value_type)); + memcpy(new_data, Data, Size * sizeof(value_type)); + ImGui::MemFree(Data); + Data = new_data; + Capacity = new_capacity; + } + + inline void push_back(const value_type& v) { if (Size == Capacity) reserve(_grow_capacity(Size+1)); Data[Size++] = v; } + inline void pop_back() { IM_ASSERT(Size > 0); Size--; } + + inline iterator erase(const_iterator it) { IM_ASSERT(it >= begin() && it < end()); const ptrdiff_t off = it - begin(); memmove(Data + off, Data + off + 1, (Size - (size_t)off - 1) * sizeof(value_type)); Size--; return Data + off; } + inline iterator insert(const_iterator it, const value_type& v) { IM_ASSERT(it >= begin() && it <= end()); const ptrdiff_t off = it - begin(); if (Size == Capacity) reserve(Capacity ? Capacity * 2 : 4); if (off < (int)Size) memmove(Data + off + 1, Data + off, (Size - (size_t)off) * sizeof(value_type)); Data[off] = v; Size++; return Data + off; } +}; +#endif // #ifndef ImVector + // Helper: execute a block of code once a frame only // Convenient if you want to quickly create an UI within deep-nested code that runs multiple times every frame. // Usage: