From 30c0ce8d2c6dc02d3238c6b094dba9b97e9ff846 Mon Sep 17 00:00:00 2001 From: WerWolv Date: Sun, 15 Nov 2020 00:45:37 +0100 Subject: [PATCH] Updated ImGui to Docking + Table branch --- CMakeLists.txt | 1 - libs/ImGui/include/imconfig.h | 2 +- libs/ImGui/include/imgui.h | 291 +- libs/ImGui/include/imgui_internal.h | 450 +- libs/ImGui/source/imgui.cpp | 572 +-- libs/ImGui/source/imgui_demo.cpp | 5867 --------------------------- libs/ImGui/source/imgui_draw.cpp | 46 +- libs/ImGui/source/imgui_widgets.cpp | 3060 +++++++++++++- 8 files changed, 4037 insertions(+), 6252 deletions(-) delete mode 100644 libs/ImGui/source/imgui_demo.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 81b7d1ca3..e67582488 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,7 +31,6 @@ add_executable(ImHex libs/glad/source/glad.c libs/ImGui/source/imgui.cpp - libs/ImGui/source/imgui_demo.cpp libs/ImGui/source/imgui_draw.cpp libs/ImGui/source/imgui_widgets.cpp libs/ImGui/source/imgui_impl_glfw.cpp diff --git a/libs/ImGui/include/imconfig.h b/libs/ImGui/include/imconfig.h index 015540f3d..736958b3e 100644 --- a/libs/ImGui/include/imconfig.h +++ b/libs/ImGui/include/imconfig.h @@ -31,7 +31,7 @@ // It is very strongly recommended to NOT disable the demo windows during development. Please read comments in imgui_demo.cpp. //#define IMGUI_DISABLE // Disable everything: all headers and source files will be empty. //#define IMGUI_DISABLE_DEMO_WINDOWS // Disable demo windows: ShowDemoWindow()/ShowStyleEditor() will be empty. Not recommended. -//#define IMGUI_DISABLE_METRICS_WINDOW // Disable debug/metrics window: ShowMetricsWindow() will be empty. +//#define IMGUI_DISABLE_METRICS_WINDOW // Disable metrics/debugger window: ShowMetricsWindow() will be empty. //---- Don't implement some functions to reduce linkage requirements. //#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. diff --git a/libs/ImGui/include/imgui.h b/libs/ImGui/include/imgui.h index e776a5177..e0b590228 100644 --- a/libs/ImGui/include/imgui.h +++ b/libs/ImGui/include/imgui.h @@ -34,6 +34,8 @@ Index of this file: // Font API (ImFontConfig, ImFontGlyph, ImFontGlyphRangesBuilder, ImFontAtlasFlags, ImFontAtlas, ImFont) // Platform interface for multi-viewport support (ImGuiPlatformIO, ImGuiPlatformMonitor, ImGuiViewportFlags, ImGuiViewport) +// FIXME-TABLE: Add ImGuiTableSortSpecsColumn and ImGuiTableSortSpecs in "Misc data structures" section above (we don't do it right now to facilitate merging various branches) + */ #pragma once @@ -61,7 +63,7 @@ Index of this file: // Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals. Work in progress versions typically starts at XYY99 then bounce up to XYY00, XYY01 etc. when release tagging happens) #define IMGUI_VERSION "1.80 WIP" -#define IMGUI_VERSION_NUM 17905 +#define IMGUI_VERSION_NUM 17906 #define IMGUI_CHECKVERSION() ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert), sizeof(ImDrawIdx)) #define IMGUI_HAS_VIEWPORT 1 // Viewport WIP branch #define IMGUI_HAS_DOCK 1 // Docking WIP branch @@ -81,13 +83,6 @@ Index of this file: #include #define IM_ASSERT(_EXPR) assert(_EXPR) // You can override the default assert handler by editing imconfig.h #endif -#if !defined(IMGUI_USE_STB_SPRINTF) && (defined(__clang__) || defined(__GNUC__)) -#define IM_FMTARGS(FMT) __attribute__((format(printf, FMT, FMT+1))) // To apply printf-style warnings to our functions. -#define IM_FMTLIST(FMT) __attribute__((format(printf, FMT, 0))) -#else -#define IM_FMTARGS(FMT) -#define IM_FMTLIST(FMT) -#endif #define IM_ARRAYSIZE(_ARR) ((int)(sizeof(_ARR) / sizeof(*(_ARR)))) // Size of a static C-style array. Don't use on pointers! #define IM_UNUSED(_VAR) ((void)(_VAR)) // Used to silence "unused variable warnings". Often useful as asserts may be stripped out from final builds. #if (__cplusplus >= 201100) @@ -95,6 +90,16 @@ Index of this file: #else #define IM_OFFSETOF(_TYPE,_MEMBER) ((size_t)&(((_TYPE*)0)->_MEMBER)) // Offset of _MEMBER within _TYPE. Old style macro. #endif +#if !defined(IMGUI_USE_STB_SPRINTF) && defined(__clang__) +#define IM_FMTARGS(FMT) __attribute__((format(printf, FMT, FMT+1))) // Apply printf-style warnings to our formatting functions. +#define IM_FMTLIST(FMT) __attribute__((format(printf, FMT, 0))) +#elif !defined(IMGUI_USE_STB_SPRINTF) && defined(__GNUC__) && defined(__MINGW32__) +#define IM_FMTARGS(FMT) __attribute__((format(gnu_printf, FMT, FMT+1))) // Apply printf-style warnings to our formatting functions. +#define IM_FMTLIST(FMT) __attribute__((format(gnu_printf, FMT, 0))) +#else +#define IM_FMTARGS(FMT) +#define IM_FMTLIST(FMT) +#endif // Warnings #if defined(__clang__) @@ -138,6 +143,8 @@ struct ImGuiPlatformMonitor; // Multi-viewport support: user-provided bou struct ImGuiSizeCallbackData; // Callback data when using SetNextWindowSizeConstraints() (rare/advanced use) struct ImGuiStorage; // Helper for key->value storage struct ImGuiStyle; // Runtime data for styling/colors +struct ImGuiTableSortSpecs; // Sorting specifications for a table (often handling sort specs for a single column, occasionally more) +struct ImGuiTableSortSpecsColumn; // Sorting specification for one column of a table struct ImGuiTextBuffer; // Helper to hold and append into a text buffer (~string builder) struct ImGuiTextFilter; // Helper to parse and apply text filters (e.g. "aaaaa[,bbbbb][,ccccc]") struct ImGuiViewport; // Viewport (generally ~1 per window to output to at the OS level. Need per-platform support to use multiple viewports) @@ -155,7 +162,9 @@ typedef int ImGuiKey; // -> enum ImGuiKey_ // Enum: A typedef int ImGuiNavInput; // -> enum ImGuiNavInput_ // Enum: An input identifier for navigation typedef int ImGuiMouseButton; // -> enum ImGuiMouseButton_ // Enum: A mouse button identifier (0=left, 1=right, 2=middle) typedef int ImGuiMouseCursor; // -> enum ImGuiMouseCursor_ // Enum: A mouse cursor identifier +typedef int ImGuiSortDirection; // -> enum ImGuiSortDirection_ // Enum: A sorting direction (ascending or descending) typedef int ImGuiStyleVar; // -> enum ImGuiStyleVar_ // Enum: A variable identifier for styling +typedef int ImGuiTableBgTarget; // -> enum ImGuiTableBgTarget_ // Enum: A color target for TableSetBgColor() typedef int ImDrawCornerFlags; // -> enum ImDrawCornerFlags_ // Flags: for ImDrawList::AddRect(), AddRectFilled() etc. typedef int ImDrawListFlags; // -> enum ImDrawListFlags_ // Flags: for ImDrawList typedef int ImFontAtlasFlags; // -> enum ImFontAtlasFlags_ // Flags: for ImFontAtlas build @@ -175,6 +184,9 @@ typedef int ImGuiSelectableFlags; // -> enum ImGuiSelectableFlags_ // Flags: f typedef int ImGuiSliderFlags; // -> enum ImGuiSliderFlags_ // Flags: for DragFloat(), DragInt(), SliderFloat(), SliderInt() etc. typedef int ImGuiTabBarFlags; // -> enum ImGuiTabBarFlags_ // Flags: for BeginTabBar() typedef int ImGuiTabItemFlags; // -> enum ImGuiTabItemFlags_ // Flags: for BeginTabItem() +typedef int ImGuiTableFlags; // -> enum ImGuiTableFlags_ // Flags: For BeginTable() +typedef int ImGuiTableColumnFlags; // -> enum ImGuiTableColumnFlags_// Flags: For TableSetupColumn() +typedef int ImGuiTableRowFlags; // -> enum ImGuiTableRowFlags_ // Flags: For TableNextRow() typedef int ImGuiTreeNodeFlags; // -> enum ImGuiTreeNodeFlags_ // Flags: for TreeNode(), TreeNodeEx(), CollapsingHeader() typedef int ImGuiViewportFlags; // -> enum ImGuiViewportFlags_ // Flags: for ImGuiViewport typedef int ImGuiWindowFlags; // -> enum ImGuiWindowFlags_ // Flags: for Begin(), BeginChild() @@ -266,7 +278,7 @@ namespace ImGui // Demo, Debug, Information IMGUI_API void ShowDemoWindow(bool* p_open = NULL); // create Demo window (previously called ShowTestWindow). demonstrate most ImGui features. call this to learn about the library! try to make it always available in your application! IMGUI_API void ShowAboutWindow(bool* p_open = NULL); // create About window. display Dear ImGui version, credits and build/system information. - IMGUI_API void ShowMetricsWindow(bool* p_open = NULL); // create Debug/Metrics window. display Dear ImGui internals: draw commands (with individual draw calls and vertices), window list, basic internal state, etc. + IMGUI_API void ShowMetricsWindow(bool* p_open = NULL); // create Metrics/Debugger window. display Dear ImGui internals: windows, draw commands, various internal state, etc. IMGUI_API void ShowStyleEditor(ImGuiStyle* ref = NULL); // add style editor block (not a window). you can pass in a reference ImGuiStyle structure to compare to, revert to and save to (else it uses the default style) IMGUI_API bool ShowStyleSelector(const char* label); // add style selector block (not a window), essentially a combo listing the default styles. IMGUI_API void ShowFontSelector(const char* label); // add font selector block (not a window), essentially a combo listing the loaded fonts. @@ -297,7 +309,10 @@ namespace ImGui // - Use child windows to begin into a self-contained independent scrolling/clipping regions within a host window. Child windows can embed their own child. // - For each independent axis of 'size': ==0.0f: use remaining host window size / >0.0f: fixed size / <0.0f: use remaining window size minus abs(size) / Each axis can use a different mode, e.g. ImVec2(0,400). // - BeginChild() returns false to indicate the window is collapsed or fully clipped, so you may early out and omit submitting anything to the window. - // Always call a matching EndChild() for each BeginChild() call, regardless of its return value [as with Begin: this is due to legacy reason and inconsistent with most BeginXXX functions apart from the regular Begin() which behaves like BeginChild().] + // Always call a matching EndChild() for each BeginChild() call, regardless of its return value. + // [Important: due to legacy reason, this is inconsistent with most other functions such as BeginMenu/EndMenu, + // BeginPopup/EndPopup, etc. where the EndXXX call should only be called if the corresponding BeginXXX function + // returned true. Begin and BeginChild are the only odd ones out. Will be fixed in a future update.] IMGUI_API bool BeginChild(const char* str_id, const ImVec2& size = ImVec2(0, 0), bool border = false, ImGuiWindowFlags flags = 0); IMGUI_API bool BeginChild(ImGuiID id, const ImVec2& size = ImVec2(0, 0), bool border = false, ImGuiWindowFlags flags = 0); IMGUI_API void EndChild(); @@ -364,6 +379,10 @@ namespace ImGui IMGUI_API void PushStyleVar(ImGuiStyleVar idx, float val); IMGUI_API void PushStyleVar(ImGuiStyleVar idx, const ImVec2& val); IMGUI_API void PopStyleVar(int count = 1); + IMGUI_API void PushAllowKeyboardFocus(bool allow_keyboard_focus); // allow focusing using TAB/Shift-TAB, enabled by default but you can disable it for certain widgets + IMGUI_API void PopAllowKeyboardFocus(); + IMGUI_API void PushButtonRepeat(bool repeat); // in 'repeat' mode, Button*() functions return repeated true in a typematic manner (using io.KeyRepeatDelay/io.KeyRepeatRate setting). Note that you can call IsItemActive() after any Button() to tell if the button is held in the current frame. + IMGUI_API void PopButtonRepeat(); IMGUI_API const ImVec4& GetStyleColorVec4(ImGuiCol idx); // retrieve style color as stored in ImGuiStyle structure. use to feed back into PushStyleColor(), otherwise use GetColorU32() to get style color with style alpha baked in. IMGUI_API ImFont* GetFont(); // get current font IMGUI_API float GetFontSize(); // get current font size (= height in pixels) of current font with current scale applied @@ -379,10 +398,6 @@ namespace ImGui IMGUI_API float CalcItemWidth(); // width of item given pushed settings and current cursor position. NOT necessarily the width of last item unlike most 'Item' functions. IMGUI_API void PushTextWrapPos(float wrap_local_pos_x = 0.0f); // push word-wrapping position 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 PushAllowKeyboardFocus(bool allow_keyboard_focus); // allow focusing using TAB/Shift-TAB, enabled by default but you can disable it for certain widgets - IMGUI_API void PopAllowKeyboardFocus(); - IMGUI_API void PushButtonRepeat(bool repeat); // in 'repeat' mode, Button*() functions return repeated true in a typematic manner (using io.KeyRepeatDelay/io.KeyRepeatRate setting). Note that you can call IsItemActive() after any Button() to tell if the button is held in the current frame. - IMGUI_API void PopButtonRepeat(); // Cursor / Layout // - By "cursor" we mean the current output position. @@ -396,8 +411,8 @@ namespace ImGui IMGUI_API void NewLine(); // undo a SameLine() or force a new line when in an horizontal-layout context. IMGUI_API void Spacing(); // add vertical spacing. IMGUI_API void Dummy(const ImVec2& size); // add a dummy item of given size. unlike InvisibleButton(), Dummy() won't take the mouse click or be navigable into. - IMGUI_API void Indent(float indent_w = 0.0f); // move content position toward the right, by style.IndentSpacing or indent_w if != 0 - IMGUI_API void Unindent(float indent_w = 0.0f); // move content position back to the left, by style.IndentSpacing or indent_w if != 0 + IMGUI_API void Indent(float indent_w = 0.0f); // move content position toward the right, by indent_w, or style.IndentSpacing if indent_w <= 0 + IMGUI_API void Unindent(float indent_w = 0.0f); // move content position back to the left, by indent_w, or style.IndentSpacing if indent_w <= 0 IMGUI_API void BeginGroup(); // lock horizontal starting position IMGUI_API void EndGroup(); // unlock horizontal starting position + capture the whole group bounding box into one "item" (so you can use IsItemHovered() or layout primitives such as SameLine() on whole group, etc.) IMGUI_API ImVec2 GetCursorPos(); // cursor position in window coordinates (relative to window position) @@ -456,6 +471,7 @@ namespace ImGui 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)); IMGUI_API bool ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1,1), int frame_padding = -1, const ImVec4& bg_col = ImVec4(0,0,0,0), const ImVec4& tint_col = ImVec4(1,1,1,1)); // <0 frame_padding uses default frame padding settings. 0 for no padding IMGUI_API bool Checkbox(const char* label, bool* v); + IMGUI_API bool CheckboxFlags(const char* label, int* flags, int flags_value); IMGUI_API bool CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value); IMGUI_API bool RadioButton(const char* label, bool active); // use with e.g. if (RadioButton("one", my_value==1)) { my_value = 1; } IMGUI_API bool RadioButton(const char* label, int* v, int v_button); // shortcut to handle the above pattern when value is an integer @@ -648,11 +664,66 @@ namespace ImGui // - IsPopupOpen() with ImGuiPopupFlags_AnyPopupId + ImGuiPopupFlags_AnyPopupLevel: return true if any popup is open. IMGUI_API bool IsPopupOpen(const char* str_id, ImGuiPopupFlags flags = 0); // return true if the popup is open. - // Columns + // Tables + // [ALPHA API] API may evolve! + // - Full-featured replacement for old Columns API. + // - See Demo->Tables for details. + // - See ImGuiTableFlags_ and ImGuiTableColumnFlags_ enums for a description of available flags. + // The typical call flow is: + // - 1. Call BeginTable() + // - 2. Optionally call TableSetupColumn() to submit column name/flags/defaults + // - 3. Optionally call TableSetupScrollFreeze() to request scroll freezing of columns/rows + // - 4. Optionally call TableHeadersRow() to submit a header row (names will be pulled from data submitted to TableSetupColumns) + // - 5. Populate contents + // - In most situations you can use TableNextRow() + TableSetColumnIndex(xx) to start appending into a column. + // - If you are using tables as a sort of grid, where every columns is holding the same type of contents, + // you may prefer using TableNextColumn() instead of TableNextRow() + TableSetColumnIndex(). + // TableNextColumn() will automatically wrap-around into the next row if needed. + // - IMPORTANT: Comparatively to the old Columns() API, we need to call TableNextColumn() for the first column! + // - Both TableSetColumnIndex() and TableNextColumn() return false when the column is not visible, so you can + // skip submitting the contents of a cell but only if you know the contents is not going to alter row height. + // - Summary of possible call flow: + // ---------------------------------------------------------------------------------------------------------- + // TableNextRow() -> TableSetColumnIndex(0) -> Text("Hello 0") -> TableSetColumnIndex(1) -> Text("Hello 1") // OK + // TableNextRow() -> TableNextColumn() Text("Hello 0") -> TableNextColumn() -> Text("Hello 1") // OK + // TableNextColumn() Text("Hello 0") -> TableNextColumn() -> Text("Hello 1") // OK: TableNextColumn() automatically gets to next row! + // TableNextRow() Text("Hello 0") // Not OK! Missing TableSetColumnIndex() or TableNextColumn()! Text will not appear! + // ---------------------------------------------------------------------------------------------------------- + // - 5. Call EndTable() + #define IMGUI_HAS_TABLE 1 + IMGUI_API bool BeginTable(const char* str_id, int columns_count, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0, 0), float inner_width = 0.0f); + IMGUI_API void EndTable(); // only call EndTable() if BeginTable() returns true! + IMGUI_API void TableNextRow(ImGuiTableRowFlags row_flags = 0, float min_row_height = 0.0f); // append into the first cell of a new row. + IMGUI_API bool TableNextColumn(); // append into the next column (or first column of next row if currently in last column). Return false when column is not visible. + IMGUI_API bool TableSetColumnIndex(int column_n); // append into the specified column. Return false when column is not visible. + IMGUI_API int TableGetColumnIndex(); // return current column index. + // Tables: Headers & Columns declaration + // - Use TableSetupColumn() to specify label, resizing policy, default width/weight, id, various other flags etc. + // Important: this will not display anything! The name passed to TableSetupColumn() is used by TableHeadersRow() and context-menus. + // - Use TableHeadersRow() to create a row and automatically submit a TableHeader() for each column. + // Headers are required to perform: reordering, sorting, and opening the context menu (but context menu can also be available in columns body using ImGuiTableFlags_ContextMenuInBody). + // - You may manually submit headers using TableNextRow() + TableHeader() calls, but this is only useful in some advanced cases (e.g. adding custom widgets in header row). + // - Use TableSetupScrollFreeze() to lock columns (from the right) or rows (from the top) so they stay visible when scrolled. + IMGUI_API void TableSetupColumn(const char* label, ImGuiTableColumnFlags flags = 0, float init_width_or_weight = -1.0f, ImU32 user_id = 0); + IMGUI_API void TableSetupScrollFreeze(int cols, int rows); // lock columns/rows so they stay visible when scrolled. + IMGUI_API void TableHeadersRow(); // submit all headers cells based on data provided to TableSetupColumn() + submit context menu + IMGUI_API void TableHeader(const char* label); // submit one header cell manually (rarely used) + // Tables: Miscellaneous functions + // - Most functions taking 'int column_n' treat the default value of -1 as the same as passing the current column index + // - Sorting: call TableGetSortSpecs() to retrieve latest sort specs for the table. Return value will be NULL if no sorting. + // When 'SpecsDirty == true' you should sort your data. It will be true when sorting specs have changed since last call, or the first time. + // Make sure to set 'SpecsDirty = false' after sorting, else you may wastefully sort your data every frame! + // Lifetime: don't hold on this pointer over multiple frames or past any subsequent call to BeginTable(). + IMGUI_API int TableGetColumnCount(); // return number of columns (value passed to BeginTable) + IMGUI_API const char* TableGetColumnName(int column_n = -1); // return "" if column didn't have a name declared by TableSetupColumn(). Pass -1 to use current column. + IMGUI_API bool TableGetColumnIsVisible(int column_n = -1); // return true if column is visible. Same value is also returned by TableNextColumn() and TableSetColumnIndex(). Pass -1 to use current column. + IMGUI_API bool TableGetColumnIsSorted(int column_n = -1); // return true if column is included in the sort specs. Rarely used, can be useful to tell if a data change should trigger resort. Equivalent to test ImGuiTableSortSpecs's ->ColumnsMask & (1 << column_n). Pass -1 to use current column. + IMGUI_API int TableGetHoveredColumn(); // return hovered column. return -1 when table is not hovered. return columns_count if the unused space at the right of visible columns is hovered. + IMGUI_API ImGuiTableSortSpecs* TableGetSortSpecs(); // get latest sort specs for the table (NULL if not sorting). + IMGUI_API void TableSetBgColor(ImGuiTableBgTarget bg_target, ImU32 color, int column_n = -1); // change the color of a cell, row, or column. See ImGuiTableBgTarget_ flags for details. + + // Legacy Columns API (2020: prefer using Tables!) // - You can also use SameLine(pos_x) to mimic simplified columns. - // - The columns API is work-in-progress and rather lacking (columns are arguably the worst part of dear imgui at the moment!) - // - There is a maximum of 64 columns. - // - Currently working on new 'Tables' api which will replace columns around Q2 2020 (see GitHub #2957). IMGUI_API void Columns(int count = 1, const char* id = NULL, bool border = true); IMGUI_API void NextColumn(); // next column, defaults to current row or next row if the current row is finished IMGUI_API int GetColumnIndex(); // get current column index @@ -1004,6 +1075,122 @@ enum ImGuiTabItemFlags_ ImGuiTabItemFlags_Trailing = 1 << 7 // Enforce the tab position to the right of the tab bar (before the scrolling buttons) }; +// Flags for ImGui::BeginTable() +// - Important! Sizing policies have particularly complex and subtle side effects, more so than you would expect. +// Read comments/demos carefully + experiment with live demos to get acquainted with them. +// - The default sizing policy for columns depends on whether the ScrollX flag is set on the table: +// When ScrollX is off: +// - Table defaults to ImGuiTableFlags_SizingPolicyStretchX -> all Columns defaults to ImGuiTableColumnFlags_WidthStretch. +// - Columns sizing policy allowed: Fixed/Auto or Stretch. +// - Stretch Columns will share the width available in table. +// - Fixed Columns will generally obtain their requested width unless the Table cannot fit them all. +// When ScrollX is on: +// - Table defaults to ImGuiTableFlags_SizingPolicyFixedX -> all Columns defaults to ImGuiTableColumnFlags_WidthFixed. +// - Columns sizing policy allowed: Fixed/Auto mostly! Using Stretch columns OFTEN DOES NOT MAKE SENSE if ScrollX is on, UNLESS you have specified a value for 'inner_width' in BeginTable(). +// - Fixed Columns can be enlarged as needed. Table will show an horizontal scrollbar if needed. +// - Stretch Columns, if any, will calculate their width using inner_width, assuming no scrolling (it really doesn't make sense to do otherwise). +// - Mixing up columns with different sizing policy is possible BUT can be tricky and has some side-effects and restrictions. +// (their visible order and the scrolling state have subtle but necessary effects on how they can be manually resized). +// The typical use of mixing sizing policies is to have ScrollX disabled, one or two Stretch Column and many Fixed Columns. +enum ImGuiTableFlags_ +{ + // Features + ImGuiTableFlags_None = 0, + ImGuiTableFlags_Resizable = 1 << 0, // Allow resizing columns. + ImGuiTableFlags_Reorderable = 1 << 1, // Allow reordering columns in header row (need calling TableSetupColumn() + TableHeadersRow() to display headers) + ImGuiTableFlags_Hideable = 1 << 2, // Allow hiding columns in context menu. + ImGuiTableFlags_Sortable = 1 << 3, // Allow sorting on one column (sort_specs_count will always be == 1). Call TableGetSortSpecs() to obtain sort specs. + ImGuiTableFlags_MultiSortable = 1 << 4, // Allow sorting on multiple columns by holding Shift (sort_specs_count may be > 1). Call TableGetSortSpecs() to obtain sort specs. + ImGuiTableFlags_NoSavedSettings = 1 << 5, // Disable persisting columns order, width and sort settings in the .ini file. + ImGuiTableFlags_ContextMenuInBody = 1 << 6, // Right-click on columns body/contents will display table context menu. By default it is available in TableHeadersRow(). + // Decoration + ImGuiTableFlags_RowBg = 1 << 7, // Set each RowBg color with ImGuiCol_TableRowBg or ImGuiCol_TableRowBgAlt (equivalent to calling TableSetBgColor with ImGuiTableBgFlags_RowBg0 on each row manually) + ImGuiTableFlags_BordersInnerH = 1 << 8, // Draw horizontal borders between rows. + ImGuiTableFlags_BordersOuterH = 1 << 9, // Draw horizontal borders at the top and bottom. + ImGuiTableFlags_BordersInnerV = 1 << 10, // Draw vertical borders between columns. + ImGuiTableFlags_BordersOuterV = 1 << 11, // Draw vertical borders on the left and right sides. + ImGuiTableFlags_BordersH = ImGuiTableFlags_BordersInnerH | ImGuiTableFlags_BordersOuterH, // Draw horizontal borders. + ImGuiTableFlags_BordersV = ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_BordersOuterV, // Draw vertical borders. + ImGuiTableFlags_BordersInner = ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_BordersInnerH, // Draw inner borders. + ImGuiTableFlags_BordersOuter = ImGuiTableFlags_BordersOuterV | ImGuiTableFlags_BordersOuterH, // Draw outer borders. + ImGuiTableFlags_Borders = ImGuiTableFlags_BordersInner | ImGuiTableFlags_BordersOuter, // Draw all borders. + ImGuiTableFlags_NoBordersInBody = 1 << 12, // Disable vertical borders in columns Body (borders will always appears in Headers). + ImGuiTableFlags_NoBordersInBodyUntilResize = 1 << 13, // Disable vertical borders in columns Body until hovered for resize (borders will always appears in Headers). + // Sizing + ImGuiTableFlags_SizingPolicyFixedX = 1 << 14, // Default if ScrollX is on. Columns will default to use _WidthFixed or _WidthAlwaysAutoResize policy. Read description above for more details. + ImGuiTableFlags_SizingPolicyStretchX = 1 << 15, // Default if ScrollX is off. Columns will default to use _WidthStretch policy. Read description above for more details. + ImGuiTableFlags_NoHeadersWidth = 1 << 16, // Disable header width contribution to automatic width calculation. + ImGuiTableFlags_NoHostExtendY = 1 << 17, // (FIXME-TABLE: Reword as SizingPolicy?) Disable extending past the limit set by outer_size.y, only meaningful when neither of ScrollX|ScrollY are set (data below the limit will be clipped and not visible) + ImGuiTableFlags_NoKeepColumnsVisible = 1 << 18, // Disable keeping column always minimally visible when table width gets too small and ScrllX is off. + ImGuiTableFlags_PreciseStretchWidths = 1 << 19, // Disable distributing remainder width to stretched columns (width allocation on a 100-wide table with 3 columns: Without this flag: 33,33,34. With this flag: 33,33,33). With larger number of columns, resizing will appear to be less smooth. + ImGuiTableFlags_NoClip = 1 << 20, // Disable clipping rectangle for every individual columns (reduce draw command count, items will be able to overflow into other columns). Generally incompatible with TableSetupScrollFreeze(). + // Padding + ImGuiTableFlags_PadOuterX = 1 << 21, // Default if BordersOuterV is on. Enable outer-most padding. + ImGuiTableFlags_NoPadOuterX = 1 << 22, // Default if BordersOuterV is off. Disable outer-most padding. + ImGuiTableFlags_NoPadInnerX = 1 << 23, // Disable inner padding between columns (double inner padding if BordersOuterV is on, single inner padding if BordersOuterV is off). + // Scrolling + ImGuiTableFlags_ScrollX = 1 << 24, // Enable horizontal scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. Changes default sizing policy. Because this create a child window, ScrollY is currently generally recommended when using ScrollX. + ImGuiTableFlags_ScrollY = 1 << 25, // Enable vertical scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. + + // [Internal] Combinations and masks + ImGuiTableFlags_SizingPolicyMaskX_ = ImGuiTableFlags_SizingPolicyStretchX | ImGuiTableFlags_SizingPolicyFixedX +}; + +// Flags for ImGui::TableSetupColumn() +// FIXME-TABLE: Rename to ImGuiColumns_*, stick old columns api flags in there under an obsolete api block +enum ImGuiTableColumnFlags_ +{ + ImGuiTableColumnFlags_None = 0, + ImGuiTableColumnFlags_DefaultHide = 1 << 0, // Default as a hidden column. + ImGuiTableColumnFlags_DefaultSort = 1 << 1, // Default as a sorting column. + ImGuiTableColumnFlags_WidthFixed = 1 << 2, // Column will keep a fixed size, preferable with horizontal scrolling enabled (default if table sizing policy is SizingPolicyFixedX and table is resizable). + ImGuiTableColumnFlags_WidthStretch = 1 << 3, // Column will stretch, preferable with horizontal scrolling disabled (default if table sizing policy is SizingPolicyStretchX). + ImGuiTableColumnFlags_WidthAlwaysAutoResize = 1 << 4, // Column will keep resizing based on submitted contents (with a one frame delay) == Fixed with auto resize (default if table sizing policy is SizingPolicyFixedX and table is not resizable). + ImGuiTableColumnFlags_NoResize = 1 << 5, // Disable manual resizing. + ImGuiTableColumnFlags_NoClipX = 1 << 6, // Disable clipping for this column (all NoClipX columns will render in a same draw command). + ImGuiTableColumnFlags_NoSort = 1 << 7, // Disable ability to sort on this field (even if ImGuiTableFlags_Sortable is set on the table). + ImGuiTableColumnFlags_NoSortAscending = 1 << 8, // Disable ability to sort in the ascending direction. + ImGuiTableColumnFlags_NoSortDescending = 1 << 9, // Disable ability to sort in the descending direction. + ImGuiTableColumnFlags_NoHide = 1 << 10, // Disable hiding this column. + ImGuiTableColumnFlags_NoHeaderWidth = 1 << 11, // Header width don't contribute to automatic column width. + ImGuiTableColumnFlags_PreferSortAscending = 1 << 12, // Make the initial sort direction Ascending when first sorting on this column (default). + ImGuiTableColumnFlags_PreferSortDescending = 1 << 13, // Make the initial sort direction Descending when first sorting on this column. + ImGuiTableColumnFlags_IndentEnable = 1 << 14, // Use current Indent value when entering cell (default for 1st column). + ImGuiTableColumnFlags_IndentDisable = 1 << 15, // Ignore current Indent value when entering cell (default for columns after the 1st one). Indentation changes _within_ the cell will still be honored. + ImGuiTableColumnFlags_NoReorder = 1 << 16, // Disable reordering this column, this will also prevent other columns from crossing over this column. + + // [Internal] Combinations and masks + ImGuiTableColumnFlags_WidthMask_ = ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_WidthAlwaysAutoResize, + ImGuiTableColumnFlags_IndentMask_ = ImGuiTableColumnFlags_IndentEnable | ImGuiTableColumnFlags_IndentDisable, + ImGuiTableColumnFlags_NoDirectResize_ = 1 << 20 // [Internal] Disable user resizing this column directly (it may however we resized indirectly from its left edge) +}; + +// Flags for ImGui::TableNextRow() +enum ImGuiTableRowFlags_ +{ + ImGuiTableRowFlags_None = 0, + ImGuiTableRowFlags_Headers = 1 << 0 // Identify header row (set default background color + width of its contents accounted different for auto column width) +}; + +// Enum for ImGui::TableSetBgColor() +// Background colors are rendering in 3 layers: +// - Layer 0: draw with RowBg0 color if set, otherwise draw with ColumnBg0 if set. +// - Layer 1: draw with RowBg1 color if set, otherwise draw with ColumnBg1 if set. +// - Layer 2: draw with CellBg color if set. +// The purpose of the two row/columns layers is to let you decide if a background color changes should override or blend with the existing color. +// When using ImGuiTableFlags_RowBg on the table, each row has the RowBg0 color automatically set for odd/even rows. +// If you set the color of RowBg0 target, your color will override the existing RowBg0 color. +// If you set the color of RowBg1 or ColumnBg1 target, your color will blend over the RowBg0 color. +enum ImGuiTableBgTarget_ +{ + ImGuiTableBgTarget_None = 0, + //ImGuiTableBgTarget_ColumnBg0 = 1, // FIXME-TABLE: Todo. Set column background color 0 (generally used for background + //ImGuiTableBgTarget_ColumnBg1 = 2, // FIXME-TABLE: Todo. Set column background color 1 (generally used for selection marking) + ImGuiTableBgTarget_RowBg0 = 3, // Set row background color 0 (generally used for background, automatically set when ImGuiTableFlags_RowBg is used) + ImGuiTableBgTarget_RowBg1 = 4, // Set row background color 1 (generally used for selection marking) + ImGuiTableBgTarget_CellBg = 5 // Set cell background color (top-most color) +}; + // Flags for ImGui::IsWindowFocused() enum ImGuiFocusedFlags_ { @@ -1096,6 +1283,14 @@ enum ImGuiDir_ ImGuiDir_COUNT }; +// A sorting direction +enum ImGuiSortDirection_ +{ + ImGuiSortDirection_None = 0, + ImGuiSortDirection_Ascending = 1, // Ascending = 0->9, A->Z etc. + ImGuiSortDirection_Descending = 2 // Descending = 9->0, Z->A etc. +}; + // User fill ImGuiIO.KeyMap[] array with indices into the ImGuiIO.KeysDown[512] array enum ImGuiKey_ { @@ -1256,6 +1451,11 @@ enum ImGuiCol_ ImGuiCol_PlotLinesHovered, ImGuiCol_PlotHistogram, ImGuiCol_PlotHistogramHovered, + ImGuiCol_TableHeaderBg, // Table header background + ImGuiCol_TableBorderStrong, // Table outer and header borders (prefer using Alpha=1.0 here) + ImGuiCol_TableBorderLight, // Table inner borders (prefer using Alpha=1.0 here) + ImGuiCol_TableRowBg, // Table row background (even rows) + ImGuiCol_TableRowBgAlt, // Table row background (odd rows) ImGuiCol_TextSelectedBg, ImGuiCol_DragDropTarget, ImGuiCol_NavHighlight, // Gamepad/keyboard: current highlighted item @@ -1296,6 +1496,7 @@ enum ImGuiStyleVar_ ImGuiStyleVar_ItemSpacing, // ImVec2 ItemSpacing ImGuiStyleVar_ItemInnerSpacing, // ImVec2 ItemInnerSpacing ImGuiStyleVar_IndentSpacing, // float IndentSpacing + ImGuiStyleVar_CellPadding, // ImVec2 CellPadding ImGuiStyleVar_ScrollbarSize, // float ScrollbarSize ImGuiStyleVar_ScrollbarRounding, // float ScrollbarRounding ImGuiStyleVar_GrabMinSize, // float GrabMinSize @@ -1532,6 +1733,7 @@ struct ImGuiStyle float FrameBorderSize; // Thickness of border around frames. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly). ImVec2 ItemSpacing; // Horizontal and vertical spacing between widgets/lines. ImVec2 ItemInnerSpacing; // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label). + ImVec2 CellPadding; // Padding within a table cell ImVec2 TouchExtraPadding; // Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately we don't sort widgets so priority on overlap will always be given to the first widget. So don't grow this too much! float IndentSpacing; // Horizontal indentation when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2). float ColumnsMinSpacing; // Minimum horizontal spacing between two columns. Preferably > (FramePadding.x + 1). @@ -1611,7 +1813,7 @@ struct ImGuiIO bool ConfigInputTextCursorBlink; // = true // Set to false to disable blinking cursor, for users who consider it distracting. (was called: io.OptCursorBlink prior to 1.63) bool ConfigWindowsResizeFromEdges; // = true // Enable resizing of windows from their edges and from the lower-left corner. This requires (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) because it needs mouse cursor feedback. (This used to be a per-window ImGuiWindowFlags_ResizeFromAnySide flag) bool ConfigWindowsMoveFromTitleBarOnly; // = false // [BETA] Set to true to only allow moving windows when clicked+dragged from the title bar. Windows without a title bar are not affected. - float ConfigWindowsMemoryCompactTimer;// = 60.0f // [BETA] Compact window memory usage when unused. Set to -1.0f to disable. + float ConfigMemoryCompactTimer; // = 60.0f // [BETA] Free transient windows/tables memory buffers when unused for given amount of time. Set to -1.0f to disable. //------------------------------------------------------------------ // Platform Functions @@ -1797,6 +1999,31 @@ struct ImGuiPayload bool IsDelivery() const { return Delivery; } }; +// Sorting specification for one column of a table (sizeof == 8 bytes) +struct ImGuiTableSortSpecsColumn +{ + ImGuiID ColumnUserID; // User id of the column (if specified by a TableSetupColumn() call) + ImU8 ColumnIndex; // Index of the column + ImU8 SortOrder; // Index within parent ImGuiTableSortSpecs (always stored in order starting from 0, tables sorted on a single criteria will always have a 0 here) + ImGuiSortDirection SortDirection : 8; // ImGuiSortDirection_Ascending or ImGuiSortDirection_Descending (you can use this or SortSign, whichever is more convenient for your sort function) + + ImGuiTableSortSpecsColumn() { ColumnUserID = 0; ColumnIndex = 0; SortOrder = 0; SortDirection = ImGuiSortDirection_Ascending; } +}; + +// Sorting specifications for a table (often handling sort specs for a single column, occasionally more) +// Obtained by calling TableGetSortSpecs(). +// When 'SpecsDirty == true' you can sort your data. It will be true with sorting specs have changed since last call, or the first time. +// Make sure to set 'SpecsDirty = false' after sorting, else you may wastefully sort your data every frame! +struct ImGuiTableSortSpecs +{ + const ImGuiTableSortSpecsColumn* Specs; // Pointer to sort spec array. + int SpecsCount; // Sort spec count. Most often 1 unless e.g. ImGuiTableFlags_MultiSortable is enabled. + bool SpecsDirty; // Set to true when specs have changed since last time! Use this to sort again, then clear the flag. + ImU64 ColumnsMask; // Set to the mask of column indexes included in the Specs array. e.g. (1 << N) when column N is sorted. + + ImGuiTableSortSpecs() { Specs = NULL; SpecsCount = 0; SpecsDirty = false; ColumnsMask = 0x00; } +}; + //----------------------------------------------------------------------------- // Obsolete functions (Will be removed! Read 'API BREAKING CHANGES' section in imgui.cpp for details) // Please keep your copy of dear imgui up to date! Occasionally set '#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS' in imconfig.h to stay ahead. @@ -1985,6 +2212,7 @@ struct ImGuiListClipper // [Internal] int ItemsCount; int StepNo; + int ItemsFrozen; float ItemsHeight; float StartPosY; @@ -2110,22 +2338,31 @@ struct ImDrawVert IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT; #endif -// For use by ImDrawListSplitter. +// [Internal] For use by ImDrawList +struct ImDrawCmdHeader +{ + ImVec4 ClipRect; + ImTextureID TextureId; + unsigned int VtxOffset; +}; + +// [Internal] For use by ImDrawListSplitter struct ImDrawChannel { ImVector _CmdBuffer; ImVector _IdxBuffer; }; + // Split/Merge functions are used to split the draw list into different layers which can be drawn into out of order. -// This is used by the Columns api, so items of each column can be batched together in a same draw call. +// This is used by the Columns/Tables API, so items of each column can be batched together in a same draw call. struct ImDrawListSplitter { int _Current; // Current channel number (0) int _Count; // Number of active channels (1+) ImVector _Channels; // Draw channels (not resized down so _Count might be < Channels.Size) - inline ImDrawListSplitter() { Clear(); } + inline ImDrawListSplitter() { memset(this, 0, sizeof(*this)); } inline ~ImDrawListSplitter() { ClearFreeMemory(); } inline void Clear() { _Current = 0; _Count = 1; } // Do not clear Channels[] so our allocations are reused next frame IMGUI_API void ClearFreeMemory(); @@ -2176,19 +2413,19 @@ struct ImDrawList ImDrawListFlags Flags; // Flags, you may poke into these to adjust anti-aliasing settings per-primitive. // [Internal, used while building lists] + unsigned int _VtxCurrentIdx; // [Internal] generally == VtxBuffer.Size unless we are past 64K vertices, in which case this gets reset to 0. const ImDrawListSharedData* _Data; // Pointer to shared draw data (you can use ImGui::GetDrawListSharedData() to get the one from current ImGui context) const char* _OwnerName; // Pointer to owner window's name for debugging - unsigned int _VtxCurrentIdx; // [Internal] Generally == VtxBuffer.Size unless we are past 64K vertices, in which case this gets reset to 0. ImDrawVert* _VtxWritePtr; // [Internal] point within VtxBuffer.Data after each add command (to avoid using the ImVector<> operators too much) ImDrawIdx* _IdxWritePtr; // [Internal] point within IdxBuffer.Data after each add command (to avoid using the ImVector<> operators too much) ImVector _ClipRectStack; // [Internal] ImVector _TextureIdStack; // [Internal] ImVector _Path; // [Internal] current path building - ImDrawCmd _CmdHeader; // [Internal] Template of active commands. Fields should match those of CmdBuffer.back(). + ImDrawCmdHeader _CmdHeader; // [Internal] template of active commands. Fields should match those of CmdBuffer.back(). ImDrawListSplitter _Splitter; // [Internal] for channels api (note: prefer using your own persistent instance of ImDrawListSplitter!) // If you want to create ImDrawList instances, pass them ImGui::GetDrawListSharedData() or create and use your own ImDrawListSharedData (so you can use ImDrawList without ImGui) - ImDrawList(const ImDrawListSharedData* shared_data) { _Data = shared_data; Flags = ImDrawListFlags_None; _VtxCurrentIdx = 0; _VtxWritePtr = NULL; _IdxWritePtr = NULL; _OwnerName = NULL; } + ImDrawList(const ImDrawListSharedData* shared_data) { memset(this, 0, sizeof(*this)); _Data = shared_data; } ~ImDrawList() { _ClearFreeMemory(); } IMGUI_API void PushClipRect(ImVec2 clip_rect_min, ImVec2 clip_rect_max, bool intersect_with_current_clip_rect = false); // Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling) diff --git a/libs/ImGui/include/imgui_internal.h b/libs/ImGui/include/imgui_internal.h index 5f46a7d06..7c269244c 100644 --- a/libs/ImGui/include/imgui_internal.h +++ b/libs/ImGui/include/imgui_internal.h @@ -23,6 +23,7 @@ Index of this file: // [SECTION] Docking support // [SECTION] Viewport support // [SECTION] Settings support +// [SECTION] Metrics, Debug // [SECTION] Generic context hooks // [SECTION] ImGuiContext (main imgui context) // [SECTION] ImGuiWindowTempData, ImGuiWindow @@ -110,9 +111,14 @@ struct ImGuiNextWindowData; // Storage for SetNextWindow** functions struct ImGuiNextItemData; // Storage for SetNextItem** functions struct ImGuiPopupData; // Storage for current popup stack struct ImGuiSettingsHandler; // Storage for one type registered in the .ini file +struct ImGuiStackSizes; // Storage of stack sizes for debugging/asserting struct ImGuiStyleMod; // Stacked style modifier, backup of modified data so we can restore it struct ImGuiTabBar; // Storage for a tab bar struct ImGuiTabItem; // Storage for a tab item (within a tab bar) +struct ImGuiTable; // Storage for a table +struct ImGuiTableColumn; // Storage for one column of a table +struct ImGuiTableSettings; // Storage for a table .ini settings +struct ImGuiTableColumnsSettings; // Storage for a column .ini settings struct ImGuiWindow; // Storage for one window struct ImGuiWindowTempData; // Temporary storage for one window (that's the data which in theory we could ditch at the end of the frame) struct ImGuiWindowSettings; // Storage for a window .ini settings (we keep one of those even if the actual window wasn't instanced during this session) @@ -254,6 +260,7 @@ namespace ImStb // - Helper: ImRect // - Helper: ImBitArray // - Helper: ImBitVector +// - Helper: ImSpan<>, ImSpanAllocator<> // - Helper: ImPool<> // - Helper: ImChunkStream<> //----------------------------------------------------------------------------- @@ -273,6 +280,7 @@ IMGUI_API ImU32 ImAlphaBlendColors(ImU32 col_a, ImU32 col_b); // Helpers: Bit manipulation static inline bool ImIsPowerOfTwo(int v) { return v != 0 && (v & (v - 1)) == 0; } +static inline bool ImIsPowerOfTwo(ImU64 v) { return v != 0 && (v & (v - 1)) == 0; } static inline int ImUpperPowerOfTwo(int v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; } // Helpers: String, Formatting @@ -465,21 +473,35 @@ struct IMGUI_API ImRect }; // Helper: ImBitArray -inline bool ImBitArrayTestBit(const ImU32* arr, int n) { ImU32 mask = (ImU32)1 << (n & 31); return (arr[n >> 5] & mask) != 0; } -inline void ImBitArrayClearBit(ImU32* arr, int n) { ImU32 mask = (ImU32)1 << (n & 31); arr[n >> 5] &= ~mask; } -inline void ImBitArraySetBit(ImU32* arr, int n) { ImU32 mask = (ImU32)1 << (n & 31); arr[n >> 5] |= mask; } -inline void ImBitArraySetBitRange(ImU32* arr, int n, int n2) +inline bool ImBitArrayTestBit(const ImU32* arr, int n) { ImU32 mask = (ImU32)1 << (n & 31); return (arr[n >> 5] & mask) != 0; } +inline void ImBitArrayClearBit(ImU32* arr, int n) { ImU32 mask = (ImU32)1 << (n & 31); arr[n >> 5] &= ~mask; } +inline void ImBitArraySetBit(ImU32* arr, int n) { ImU32 mask = (ImU32)1 << (n & 31); arr[n >> 5] |= mask; } +inline void ImBitArraySetBitRange(ImU32* arr, int n, int n2) { while (n <= n2) { int a_mod = (n & 31); - int b_mod = ((n2 >= n + 31) ? 31 : (n2 & 31)) + 1; + int b_mod = (n2 > (n | 31) ? 31 : (n2 & 31)) + 1; ImU32 mask = (ImU32)(((ImU64)1 << b_mod) - 1) & ~(ImU32)(((ImU64)1 << a_mod) - 1); arr[n >> 5] |= mask; n = (n + 32) & ~31; } } +// Helper: ImBitArray class (wrapper over ImBitArray functions) +// Store 1-bit per value. NOT CLEARED by constructor. +template +struct IMGUI_API ImBitArray +{ + ImU32 Storage[(BITCOUNT + 31) >> 5]; + ImBitArray() { } + void ClearBits() { memset(Storage, 0, sizeof(Storage)); } + bool TestBit(int n) const { IM_ASSERT(n < BITCOUNT); return ImBitArrayTestBit(Storage, n); } + void SetBit(int n) { IM_ASSERT(n < BITCOUNT); ImBitArraySetBit(Storage, n); } + void ClearBit(int n) { IM_ASSERT(n < BITCOUNT); ImBitArrayClearBit(Storage, n); } + void SetBitRange(int n1, int n2) { ImBitArraySetBitRange(Storage, n1, n2); } +}; + // Helper: ImBitVector // Store 1-bit per value. struct IMGUI_API ImBitVector @@ -492,6 +514,54 @@ struct IMGUI_API ImBitVector void ClearBit(int n) { IM_ASSERT(n < (Storage.Size << 5)); ImBitArrayClearBit(Storage.Data, n); } }; +// Helper: ImSpan<> +// Pointing to a span of data we don't own. +template +struct ImSpan +{ + T* Data; + T* DataEnd; + + // Constructors, destructor + inline ImSpan() { Data = DataEnd = NULL; } + inline ImSpan(T* data, int size) { Data = data; DataEnd = data + size; } + inline ImSpan(T* data, T* data_end) { Data = data; DataEnd = data_end; } + + inline void set(T* data, int size) { Data = data; DataEnd = data + size; } + inline void set(T* data, T* data_end) { Data = data; DataEnd = data_end; } + inline int size() const { return (int)(ptrdiff_t)(DataEnd - Data); } + inline T& operator[](int i) { T* p = Data + i; IM_ASSERT(p >= Data && p < DataEnd); return *p; } + inline const T& operator[](int i) const { const T* p = Data + i; IM_ASSERT(p >= Data && p < DataEnd); return *p; } + + inline T* begin() { return Data; } + inline const T* begin() const { return Data; } + inline T* end() { return DataEnd; } + inline const T* end() const { return DataEnd; } + + // Utilities + inline int index_from_ptr(const T* it) const { IM_ASSERT(it >= Data && it < DataEnd); const ptrdiff_t off = it - Data; return (int)off; } +}; + +// Helper: ImSpanAllocator<> +// Facilitate storing multiple chunks into a single large block (the "arena") +template +struct ImSpanAllocator +{ + char* BasePtr; + int TotalSize; + int CurrSpan; + int Offsets[CHUNKS]; + + ImSpanAllocator() { memset(this, 0, sizeof(*this)); } + inline void ReserveBytes(int n, size_t sz) { IM_ASSERT(n == CurrSpan && n < CHUNKS); IM_UNUSED(n); Offsets[CurrSpan++] = TotalSize; TotalSize += (int)sz; } + inline int GetArenaSizeInBytes() { return TotalSize; } + inline void SetArenaBasePtr(void* base_ptr) { BasePtr = (char*)base_ptr; } + inline void* GetSpanPtrBegin(int n) { IM_ASSERT(n >= 0 && n < CHUNKS && CurrSpan == CHUNKS); return (void*)(BasePtr + Offsets[n]); } + inline void* GetSpanPtrEnd(int n) { IM_ASSERT(n >= 0 && n < CHUNKS && CurrSpan == CHUNKS); return (n + 1 < CHUNKS) ? BasePtr + Offsets[n + 1] : (void*)(BasePtr + TotalSize); } + template + inline void GetSpan(int n, ImSpan* span) { span->set((T*)GetSpanPtrBegin(n), (T*)GetSpanPtrEnd(n)); } +}; + // Helper: ImPool<> // Basic keyed storage for contiguous instances, slow/amortized insertion, O(1) indexable, O(Log N) queries by ID over a dense/hot buffer, // Honor constructor/destructor. Add/remove invalidate all pointers. Indexes have the same lifetime as the associated object. @@ -538,6 +608,8 @@ struct IMGUI_API ImChunkStream T* end() { return (T*)(void*)(Buf.Data + Buf.Size); } int offset_from_ptr(const T* p) { IM_ASSERT(p >= begin() && p < end()); const ptrdiff_t off = (const char*)p - Buf.Data; return (int)off; } T* ptr_from_offset(int off) { IM_ASSERT(off >= 4 && off < Buf.Size); return (T*)(void*)(Buf.Data + off); } + void swap(ImChunkStream& rhs) { rhs.Buf.swap(Buf); } + }; //----------------------------------------------------------------------------- @@ -838,6 +910,7 @@ struct ImGuiStyleMod // Stacked storage data for BeginGroup()/EndGroup() struct ImGuiGroupData { + ImGuiID WindowID; ImVec2 BackupCursorPos; ImVec2 BackupCursorMaxPos; ImVec1 BackupIndent; @@ -856,7 +929,7 @@ struct IMGUI_API ImGuiMenuColumns float Width, NextWidth; float Pos[3], NextWidths[3]; - ImGuiMenuColumns(); + ImGuiMenuColumns() { memset(this, 0, sizeof(*this)); } void Update(int count, float spacing, bool clear); float DeclColumns(float w0, float w1, float w2); float CalcExtraSpace(float avail_w) const; @@ -909,7 +982,7 @@ struct ImGuiPopupData ImVec2 OpenPopupPos; // Set on OpenPopup(), preferred popup position (typically == OpenMousePos when using mouse) ImVec2 OpenMousePos; // Set on OpenPopup(), copy of mouse position at the time of opening popup - ImGuiPopupData() { PopupId = 0; Window = SourceWindow = NULL; OpenFrameCount = -1; OpenParentId = 0; } + ImGuiPopupData() { memset(this, 0, sizeof(*this)); OpenFrameCount = -1; } }; struct ImGuiNavMoveResult @@ -1026,7 +1099,7 @@ struct ImGuiColumnData ImGuiColumnsFlags Flags; // Not exposed ImRect ClipRect; - ImGuiColumnData() { OffsetNorm = OffsetNormBeforeResize = 0.0f; Flags = ImGuiColumnsFlags_None; } + ImGuiColumnData() { memset(this, 0, sizeof(*this)); } }; struct ImGuiColumns @@ -1047,21 +1120,7 @@ struct ImGuiColumns ImVector Columns; ImDrawListSplitter Splitter; - ImGuiColumns() { Clear(); } - void Clear() - { - ID = 0; - Flags = ImGuiColumnsFlags_None; - IsFirstFrame = false; - IsBeingResized = false; - Current = 0; - Count = 1; - OffMinX = OffMaxX = 0.0f; - LineMinY = LineMaxY = 0.0f; - HostCursorPosY = 0.0f; - HostCursorMaxPosX = 0.0f; - Columns.clear(); - } + ImGuiColumns() { memset(this, 0, sizeof(*this)); } }; //----------------------------------------------------------------------------- @@ -1124,6 +1183,7 @@ struct ImGuiDockNode ImGuiID ID; ImGuiDockNodeFlags SharedFlags; // Flags shared by all nodes of a same dockspace hierarchy (inherited from the root node) ImGuiDockNodeFlags LocalFlags; // Flags specific to this node + ImGuiDockNodeState State; ImGuiDockNode* ParentNode; ImGuiDockNode* ChildNodes[2]; // [Split node only] Child nodes (left/right or top/bottom). Consider switching to an array. ImVector Windows; // Note: unordered list! Iterate TabBar->Tabs for user-order. @@ -1134,7 +1194,6 @@ struct ImGuiDockNode ImGuiAxis SplitAxis; // [Split node only] Split axis (X or Y) ImGuiWindowClass WindowClass; // [Root node only] - ImGuiDockNodeState State; ImGuiWindow* HostWindow; ImGuiWindow* VisibleWindow; // Generally point to window which is ID is == SelectedTabID, but when CTRL+Tabbing this can be a different window. ImGuiDockNode* CentralNode; // [Root node only] Pointer to central node. @@ -1245,7 +1304,7 @@ struct ImGuiWindowSettings bool Collapsed; bool WantApply; // Set when loaded from .ini data (to enable merging/loading .ini data into an already running context) - ImGuiWindowSettings() { ID = 0; Pos = Size = ViewportPos = ImVec2ih(0, 0); ViewportId = DockId = ClassId = 0; DockOrder = -1; Collapsed = WantApply = false; } + ImGuiWindowSettings() { memset(this, 0, sizeof(*this)); DockOrder = -1; } char* GetName() { return (char*)(this + 1); } }; @@ -1264,6 +1323,10 @@ struct ImGuiSettingsHandler ImGuiSettingsHandler() { memset(this, 0, sizeof(*this)); } }; +//----------------------------------------------------------------------------- +// [SECTION] Metrics, Debug +//----------------------------------------------------------------------------- + struct ImGuiMetricsConfig { bool ShowWindowsRects; @@ -1288,6 +1351,21 @@ struct ImGuiMetricsConfig } }; +struct IMGUI_API ImGuiStackSizes +{ + short SizeOfIDStack; + short SizeOfColorStack; + short SizeOfStyleVarStack; + short SizeOfFontStack; + short SizeOfFocusScopeStack; + short SizeOfGroupStack; + short SizeOfBeginPopupStack; + + ImGuiStackSizes() { memset(this, 0, sizeof(*this)); } + IMGUI_API void SetToCurrentState(); + IMGUI_API void CompareWithCurrentState(); +}; + //----------------------------------------------------------------------------- // [SECTION] Generic context hooks //----------------------------------------------------------------------------- @@ -1330,6 +1408,7 @@ struct ImGuiContext bool WithinFrameScope; // Set by NewFrame(), cleared by EndFrame() bool WithinFrameScopeWithImplicitWindow; // Set by NewFrame(), cleared by EndFrame() when the implicit debug window has been pushed bool WithinEndChild; // Set within EndChild() + bool GcCompactAll; // Request full GC bool TestEngineHookItems; // Will call test engine hooks: ImGuiTestEngineHook_ItemAdd(), ImGuiTestEngineHook_ItemInfo(), ImGuiTestEngineHook_Log() ImGuiID TestEngineHookIdInfo; // Will call test engine hooks: ImGuiTestEngineHook_IdInfo() from GetID() void* TestEngine; // Test engine user data @@ -1386,9 +1465,12 @@ struct ImGuiContext ImGuiNextItemData NextItemData; // Storage for SetNextItem** functions // Shared stacks - ImVector ColorModifiers; // Stack for PushStyleColor()/PopStyleColor() - ImVector StyleModifiers; // Stack for PushStyleVar()/PopStyleVar() - ImVector FontStack; // Stack for PushFont()/PopFont() + ImVector ColorStack; // Stack for PushStyleColor()/PopStyleColor() - inherited by Begin() + ImVector StyleVarStack; // Stack for PushStyleVar()/PopStyleVar() - inherited by Begin() + ImVector FontStack; // Stack for PushFont()/PopFont() - inherited by Begin() + ImVector FocusScopeStack; // Stack for PushFocusScope()/PopFocusScope() - not inherited by Begin(), unless child window + ImVectorItemFlagsStack; // Stack for PushItemFlag()/PopItemFlag() - inherited by Begin() + ImVectorGroupStack; // Stack for BeginGroup()/EndGroup() - not inherited by Begin() ImVectorOpenPopupStack; // Which popups are open (persistent) ImVectorBeginPopupStack; // Which level of BeginPopup() we are in (reset every frame) @@ -1479,6 +1561,13 @@ struct ImGuiContext ImVector DragDropPayloadBufHeap; // We don't expose the ImVector<> directly, ImGuiPayload only holds pointer+size unsigned char DragDropPayloadBufLocal[16]; // Local buffer for small payloads + // Table + ImGuiTable* CurrentTable; + ImPool Tables; + ImVector CurrentTableStack; + ImVector TablesLastTimeActive; // Last used timestamp of each tables (SOA, for efficient GC) + ImVector DrawChannelsTempMergeBuffer; + // Tab bars ImGuiTabBar* CurrentTabBar; ImPool TabBars; @@ -1521,6 +1610,7 @@ struct ImGuiContext ImGuiTextBuffer SettingsIniData; // In memory .ini settings ImVector SettingsHandlers; // List of .ini settings handlers ImChunkStream SettingsWindows; // ImGuiWindow .ini settings entries + ImChunkStream SettingsTables; // ImGuiTable .ini settings entries ImVector Hooks; // Hooks for extensions (e.g. test engine) // Capture/Logging @@ -1560,6 +1650,7 @@ struct ImGuiContext FrameCount = 0; FrameCountEnded = FrameCountPlatformEnded = FrameCountRendered = -1; WithinFrameScope = WithinFrameScopeWithImplicitWindow = WithinEndChild = false; + GcCompactAll = false; TestEngineHookItems = false; TestEngineHookIdInfo = 0; TestEngine = NULL; @@ -1655,6 +1746,7 @@ struct ImGuiContext DragDropHoldJustPressedId = 0; memset(DragDropPayloadBufLocal, 0, sizeof(DragDropPayloadBufLocal)); + CurrentTable = NULL; CurrentTabBar = NULL; LastValidMousePos = ImVec2(0.0f, 0.0f); @@ -1701,7 +1793,8 @@ struct ImGuiContext //----------------------------------------------------------------------------- // Transient per-window data, reset at the beginning of the frame. This used to be called ImGuiDrawContext, hence the DC variable name in ImGuiWindow. -// FIXME: That's theory, in practice the delimitation between ImGuiWindow and ImGuiWindowTempData is quite tenuous and could be reconsidered. +// (That's theory, in practice the delimitation between ImGuiWindow and ImGuiWindowTempData is quite tenuous and could be reconsidered..) +// (This doesn't need a constructor because we zero-clear it as part of ImGuiWindow and all frame-temporary data are setup on Begin) struct IMGUI_API ImGuiWindowTempData { // Layout @@ -1740,6 +1833,7 @@ struct IMGUI_API ImGuiWindowTempData ImVector ChildWindows; ImGuiStorage* StateStorage; // Current persistent per-window storage (store e.g. tree node open/close state) ImGuiColumns* CurrentColumns; // Current columns set + int CurrentTableIdx; // Current table index (into g.Tables) ImGuiLayoutType LayoutType; ImGuiLayoutType ParentLayoutType; // Layout type of parent window at the time of Begin() int FocusCounterRegular; // (Legacy Focus/Tabbing system) Sequential counter, start at -1 and increase as assigned via FocusableItemRegister() (FIXME-NAV: Needs redesign) @@ -1747,48 +1841,12 @@ struct IMGUI_API ImGuiWindowTempData // Local parameters stacks // 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. - ImGuiItemFlags ItemFlags; // == ItemFlagsStack.back() [empty == ImGuiItemFlags_Default] + ImGuiItemFlags ItemFlags; // == g.ItemFlagsStack.back() 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] - ImVectorItemFlagsStack; ImVector ItemWidthStack; ImVector TextWrapPosStack; - ImVectorGroupStack; - short StackSizesBackup[6]; // Store size of various stacks for asserting - - ImGuiWindowTempData() - { - CursorPos = CursorPosPrevLine = CursorStartPos = CursorMaxPos = ImVec2(0.0f, 0.0f); - CurrLineSize = PrevLineSize = ImVec2(0.0f, 0.0f); - CurrLineTextBaseOffset = PrevLineTextBaseOffset = 0.0f; - Indent = ImVec1(0.0f); - ColumnsOffset = ImVec1(0.0f); - GroupOffset = ImVec1(0.0f); - - LastItemId = 0; - LastItemStatusFlags = ImGuiItemStatusFlags_None; - LastItemRect = LastItemDisplayRect = ImRect(); - - NavLayerActiveMask = NavLayerActiveMaskNext = 0x00; - NavLayerCurrent = ImGuiNavLayer_Main; - NavFocusScopeIdCurrent = 0; - NavHideHighlightOneFrame = false; - NavHasScroll = false; - - MenuBarAppending = false; - MenuBarOffset = ImVec2(0.0f, 0.0f); - TreeDepth = 0; - TreeJumpToParentOnPopMask = 0x00; - StateStorage = NULL; - CurrentColumns = NULL; - LayoutType = ParentLayoutType = ImGuiLayoutType_Vertical; - FocusCounterRegular = FocusCounterTabStop = -1; - - ItemFlags = ImGuiItemFlags_Default_; - ItemWidth = 0.0f; - TextWrapPos = -1.0f; - memset(StackSizesBackup, 0, sizeof(StackSizesBackup)); - } + ImGuiStackSizes StackSizesOnBegin; // Store size of various stacks for asserting }; // Storage for one window @@ -1852,7 +1910,7 @@ struct IMGUI_API ImGuiWindow ImVector IDStack; // ID stack. ID are hashes seeded with the value at the top of the stack. (In theory this should be in the TempData structure) ImGuiWindowTempData DC; // Temporary per-window data, reset at the beginning of the frame. This used to be called ImGuiDrawContext, hence the "DC" variable name. - // The best way to understand what those rectangles are is to use the 'Metrics -> Tools -> Show windows rectangles' viewer. + // The best way to understand what those rectangles are is to use the 'Metrics->Tools->Show Windows Rectangles' viewer. // The main 'OuterRect', omitted as a field, is window->Rect(). ImRect OuterRectClipped; // == Window->Rect() just after setup in Begin(). == window->Rect() for root window. ImRect InnerRect; // Inner rectangle (omit title bar, menu bar, scroll bar) @@ -1886,9 +1944,9 @@ struct IMGUI_API ImGuiWindow ImGuiID NavLastIds[ImGuiNavLayer_COUNT]; // Last known NavId for this window, per layer (0/1) ImRect NavRectRel[ImGuiNavLayer_COUNT]; // Reference rectangle, in window relative space - bool MemoryCompacted; // Set when window extraneous data have been garbage collected int MemoryDrawListIdxCapacity; // Backup of last idx/vtx count, so when waking up the window we can preallocate and avoid iterative alloc/copy int MemoryDrawListVtxCapacity; + bool MemoryCompacted; // Set when window extraneous data have been garbage collected // Docking ImGuiDockNode* DockNode; // Which node are we docked into. Important: Prefer testing DockIsActive in many cases as this will still be set when the dock node is hidden. @@ -2026,7 +2084,212 @@ struct ImGuiTabBar //----------------------------------------------------------------------------- #ifdef IMGUI_HAS_TABLE -// + +#define IM_COL32_DISABLE IM_COL32(0,0,0,1) // Special sentinel code which cannot be used as a regular color. +#define IMGUI_TABLE_MAX_COLUMNS 64 // sizeof(ImU64) * 8. This is solely because we frequently encode columns set in a ImU64. +#define IMGUI_TABLE_MAX_DRAW_CHANNELS (4 + 64 * 2) // See TableUpdateDrawChannels() + +// [Internal] sizeof() ~ 100 +// We use the terminology "Visible" to refer to a column that is not Hidden by user or settings. However it may still be out of view and clipped (see IsClipped). +struct ImGuiTableColumn +{ + ImRect ClipRect; // Clipping rectangle for the column + ImGuiID UserID; // Optional, value passed to TableSetupColumn() + ImGuiTableColumnFlags FlagsIn; // Flags as they were provided by user. See ImGuiTableColumnFlags_ + ImGuiTableColumnFlags Flags; // Effective flags. See ImGuiTableColumnFlags_ + float MinX; // Absolute positions + float MaxX; + float InitStretchWeightOrWidth; // Value passed to TableSetupColumn(). For Width it is a content width (_without padding_). + float StretchWeight; // Master width weight when (Flags & _WidthStretch). Often around ~1.0f initially. + float WidthAuto; // Automatic width + float WidthRequest; // Master width absolute value when !(Flags & _WidthStretch). When Stretch this is derived every frame from StretchWeight in TableUpdateLayout() + float WidthGiven; // Final/actual width visible == (MaxX - MinX), locked in TableUpdateLayout(). May be > WidthRequest to honor minimum width, may be < WidthRequest to honor shrinking columns down in tight space. + float WorkMinX; // Start position for the frame, currently ~(MinX + CellPaddingX) + float WorkMaxX; + float ContentMaxXFrozen; // Contents maximum position for frozen rows (apart from headers), from which we can infer content width. + float ContentMaxXUnfrozen; + float ContentMaxXHeadersUsed; // Contents maximum position for headers rows (regardless of freezing). TableHeader() automatically softclip itself + report ideal desired size, to avoid creating extraneous draw calls + float ContentMaxXHeadersIdeal; + ImS16 NameOffset; // Offset into parent ColumnsNames[] + bool IsVisible; // Is the column not marked Hidden by the user? (even if off view, e.g. clipped by scrolling). + bool IsVisibleNextFrame; + bool IsClipped; // Is not actually in view (e.g. not overlapping the host window clipping rectangle). + bool IsSkipItems; // Do we want item submissions to this column to be ignored early on. + ImS8 NavLayerCurrent; // ImGuiNavLayer in 1 byte + ImS8 DisplayOrder; // Index within Table's IndexToDisplayOrder[] (column may be reordered by users) + ImS8 IndexWithinVisibleSet; // Index within visible set (<= IndexToDisplayOrder) + ImS8 PrevVisibleColumn; // Index of prev visible column within Columns[], -1 if first visible column + ImS8 NextVisibleColumn; // Index of next visible column within Columns[], -1 if last visible column + ImS8 SortOrder; // Index of this column within sort specs, -1 if not sorting on this column, 0 for single-sort, may be >0 on multi-sort + ImS8 SortDirection; // ImGuiSortDirection_Ascending or ImGuiSortDirection_Descending + ImU8 AutoFitQueue; // Queue of 8 values for the next 8 frames to request auto-fit + ImU8 CannotSkipItemsQueue; // Queue of 8 values for the next 8 frames to disable Clipped/SkipItem + ImU8 DrawChannelCurrent; // Index within DrawSplitter.Channels[] + ImU8 DrawChannelFrozen; + ImU8 DrawChannelUnfrozen; + + ImGuiTableColumn() + { + memset(this, 0, sizeof(*this)); + StretchWeight = WidthRequest = -1.0f; + NameOffset = -1; + IsVisible = IsVisibleNextFrame = true; + DisplayOrder = IndexWithinVisibleSet = -1; + PrevVisibleColumn = NextVisibleColumn = -1; + SortOrder = -1; + SortDirection = ImGuiSortDirection_None; + AutoFitQueue = CannotSkipItemsQueue = (1 << 3) - 1; // Skip for three frames + DrawChannelCurrent = DrawChannelFrozen = DrawChannelUnfrozen = (ImU8)-1; + } +}; + +// Transient cell data stored per row. +// sizeof() ~ 6 +struct ImGuiTableCellData +{ + ImU32 BgColor; // Actual color + ImS8 Column; // Column number +}; + +struct ImGuiTable +{ + ImGuiID ID; + ImGuiTableFlags Flags; + void* RawData; // Single allocation to hold Columns[], DisplayOrderToIndex[] and RowCellData[] + ImSpan Columns; // Point within RawData[] + ImSpan DisplayOrderToIndex; // Point within RawData[]. Store display order of columns (when not reordered, the values are 0...Count-1) + ImSpan RowCellData; // Point within RawData[]. Store cells background requests for current row. + ImU64 VisibleMaskByIndex; // Column Index -> IsVisible map (== not hidden by user/api) in a format adequate for iterating column without touching cold data + ImU64 VisibleMaskByDisplayOrder; // Column DisplayOrder -> IsVisible map + ImU64 VisibleUnclippedMaskByIndex;// Visible and not Clipped, aka "actually visible" "not hidden by some scrolling" + ImGuiTableFlags SettingsLoadedFlags; // Which data were loaded from the .ini file (e.g. when order is not altered we won't save order) + int SettingsOffset; // Offset in g.SettingsTables + int LastFrameActive; + int ColumnsCount; // Number of columns declared in BeginTable() + int ColumnsVisibleCount; // Number of non-hidden columns (<= ColumnsCount) + int CurrentRow; + int CurrentColumn; + ImS16 InstanceCurrent; // Count of BeginTable() calls with same ID in the same frame (generally 0). This is a little bit similar to BeginCount for a window, but multiple table with same ID look are multiple tables, they are just synched. + ImS16 InstanceInteracted; // Mark which instance (generally 0) of the same ID is being interacted with + float RowPosY1; + float RowPosY2; + float RowMinHeight; // Height submitted to TableNextRow() + float RowTextBaseline; + float RowIndentOffsetX; + ImGuiTableRowFlags RowFlags : 16; // Current row flags, see ImGuiTableRowFlags_ + ImGuiTableRowFlags LastRowFlags : 16; + int RowBgColorCounter; // Counter for alternating background colors (can be fast-forwarded by e.g clipper), not same as CurrentRow because header rows typically don't increase this. + ImU32 RowBgColor[2]; // Background color override for current row. + ImU32 BorderColorStrong; + ImU32 BorderColorLight; + float BorderX1; + float BorderX2; + float HostIndentX; + float OuterPaddingX; + float CellPaddingX; // Padding from each borders + float CellPaddingY; + float CellSpacingX1; // Spacing between non-bordered cells + float CellSpacingX2; + float LastOuterHeight; // Outer height from last frame + float LastFirstRowHeight; // Height of first row from last frame + float InnerWidth; // User value passed to BeginTable(), see comments at the top of BeginTable() for details. + float ColumnsTotalWidth; // Sum of current column width + float ColumnsAutoFitWidth; // Sum of ideal column width in order nothing to be clipped, used for auto-fitting and content width submission in outer window + float ResizedColumnNextWidth; + float RefScale; // Reference scale to be able to rescale columns on font/dpi changes. + ImRect OuterRect; // Note: OuterRect.Max.y is often FLT_MAX until EndTable(), unless a height has been specified in BeginTable(). + ImRect WorkRect; + ImRect InnerClipRect; + ImRect BgClipRect; // We use this to cpu-clip cell background color fill + ImRect BgClipRectForDrawCmd; + ImRect HostClipRect; // This is used to check if we can eventually merge our columns draw calls into the current draw call of the current window. + ImRect HostBackupWorkRect; // Backup of InnerWindow->WorkRect at the end of BeginTable() + ImRect HostBackupParentWorkRect; // Backup of InnerWindow->ParentWorkRect at the end of BeginTable() + ImRect HostBackupClipRect; // Backup of InnerWindow->ClipRect during PushTableBackground()/PopTableBackground() + ImVec2 HostBackupCursorMaxPos; // Backup of InnerWindow->DC.CursorMaxPos at the end of BeginTable() + ImVec1 HostBackupColumnsOffset; // Backup of OuterWindow->ColumnsOffset at the end of BeginTable() + ImGuiWindow* OuterWindow; // Parent window for the table + ImGuiWindow* InnerWindow; // Window holding the table data (== OuterWindow or a child window) + ImGuiTextBuffer ColumnsNames; // Contiguous buffer holding columns names + ImDrawListSplitter DrawSplitter; // We carry our own ImDrawList splitter to allow recursion (FIXME: could be stored outside, worst case we need 1 splitter per recursing table) + ImVector SortSpecsData; // FIXME-OPT: Fixed-size array / small-vector pattern, optimize for single sort spec + ImGuiTableSortSpecs SortSpecs; // Public facing sorts specs, this is what we return in TableGetSortSpecs() + ImS8 SortSpecsCount; + ImS8 DeclColumnsCount; // Count calls to TableSetupColumn() + ImS8 HoveredColumnBody; // Index of column whose visible region is being hovered. Important: == ColumnsCount when hovering empty region after the right-most column! + ImS8 HoveredColumnBorder; // Index of column whose right-border is being hovered (for resizing). + ImS8 ResizedColumn; // Index of column being resized. Reset when InstanceCurrent==0. + ImS8 LastResizedColumn; // Index of column being resized from previous frame. + ImS8 HeldHeaderColumn; // Index of column header being held. + ImS8 ReorderColumn; // Index of column being reordered. (not cleared) + ImS8 ReorderColumnDir; // -1 or +1 + ImS8 RightMostVisibleColumn; // Index of right-most non-hidden column. + ImS8 LeftMostStretchedColumnDisplayOrder; // Display order of left-most stretched column. + ImS8 ContextPopupColumn; // Column right-clicked on, of -1 if opening context menu from a neutral/empty spot + ImS8 FreezeRowsRequest; // Requested frozen rows count + ImS8 FreezeRowsCount; // Actual frozen row count (== FreezeRowsRequest, or == 0 when no scrolling offset) + ImS8 FreezeColumnsRequest; // Requested frozen columns count + ImS8 FreezeColumnsCount; // Actual frozen columns count (== FreezeColumnsRequest, or == 0 when no scrolling offset) + ImS8 RowCellDataCurrent; // Index of current RowCellData[] entry in current row + ImU8 DummyDrawChannel; // Redirect non-visible columns here. + ImU8 Bg1DrawChannelCurrent; // For Selectable() and other widgets drawing accross columns after the freezing line. Index within DrawSplitter.Channels[] + ImU8 Bg1DrawChannelUnfrozen; + bool IsLayoutLocked; // Set by TableUpdateLayout() which is called when beginning the first row. + bool IsInsideRow; // Set when inside TableBeginRow()/TableEndRow(). + bool IsInitializing; + bool IsSortSpecsDirty; + bool IsUsingHeaders; // Set when the first row had the ImGuiTableRowFlags_Headers flag. + bool IsContextPopupOpen; // Set when default context menu is open (also see: ContextPopupColumn, InstanceInteracted). + bool IsSettingsRequestLoad; + bool IsSettingsDirty; // Set when table settings have changed and needs to be reported into ImGuiTableSetttings data. + bool IsDefaultDisplayOrder; // Set when display order is unchanged from default (DisplayOrder contains 0...Count-1) + bool IsResetDisplayOrderRequest; + bool IsUnfrozen; // Set when we got past the frozen row. + bool MemoryCompacted; + bool HostSkipItems; // Backup of InnerWindow->SkipItem at the end of BeginTable(), because we will overwrite InnerWindow->SkipItem on a per-column basis + + IMGUI_API ImGuiTable(); + IMGUI_API ~ImGuiTable(); +}; + +// sizeof() ~ 12 +struct ImGuiTableColumnSettings +{ + float WidthOrWeight; + ImGuiID UserID; + ImS8 Index; + ImS8 DisplayOrder; + ImS8 SortOrder; + ImU8 SortDirection : 2; + ImU8 IsVisible : 1; + ImU8 IsStretch : 1; + + ImGuiTableColumnSettings() + { + WidthOrWeight = 0.0f; + UserID = 0; + Index = -1; + DisplayOrder = SortOrder = -1; + SortDirection = ImGuiSortDirection_None; + IsVisible = 1; + IsStretch = 0; + } +}; + +// This is designed to be stored in a single ImChunkStream (1 header followed by N ImGuiTableColumnSettings, etc.) +struct ImGuiTableSettings +{ + ImGuiID ID; // Set to 0 to invalidate/delete the setting + ImGuiTableFlags SaveFlags; // Indicate data we want to save using the Resizable/Reorderable/Sortable/Hideable flags (could be using its own flags..) + float RefScale; // Reference scale to be able to rescale columns on font/dpi changes. + ImS8 ColumnsCount; + ImS8 ColumnsCountMax; // Maximum number of columns this settings instance can store, we can recycle a settings instance with lower number of columns but not higher + bool WantApply; // Set when loaded from .ini data (to enable merging/loading .ini data into an already running context) + + ImGuiTableSettings() { memset(this, 0, sizeof(*this)); } + ImGuiTableColumnSettings* GetColumnSettings() { return (ImGuiTableColumnSettings*)(this + 1); } +}; + #endif // #ifdef IMGUI_HAS_TABLE //----------------------------------------------------------------------------- @@ -2109,6 +2372,7 @@ namespace ImGui inline ImGuiItemStatusFlags GetItemStatusFlags() { ImGuiContext& g = *GImGui; return g.CurrentWindow->DC.LastItemStatusFlags; } inline ImGuiID GetActiveID() { ImGuiContext& g = *GImGui; return g.ActiveId; } inline ImGuiID GetFocusID() { ImGuiContext& g = *GImGui; return g.NavId; } + inline ImGuiItemFlags GetItemsFlags() { ImGuiContext& g = *GImGui; return g.CurrentWindow->DC.ItemFlags; } IMGUI_API void SetActiveID(ImGuiID id, ImGuiWindow* window); IMGUI_API void SetFocusID(ImGuiID id, ImGuiWindow* window); IMGUI_API void ClearActiveID(); @@ -2171,7 +2435,8 @@ namespace ImGui // patterns generally need to react (e.g. clear selection) when landing on an item of the set. IMGUI_API void PushFocusScope(ImGuiID id); IMGUI_API void PopFocusScope(); - inline ImGuiID GetFocusScopeID() { ImGuiContext& g = *GImGui; return g.NavFocusScopeId; } + inline ImGuiID GetFocusedFocusScope() { ImGuiContext& g = *GImGui; return g.NavFocusScopeId; } // Focus scope which is actually active + inline ImGuiID GetFocusScope() { ImGuiContext& g = *GImGui; return g.CurrentWindow->DC.NavFocusScopeIdCurrent; } // Focus scope we are outputting into, set by PushFocusScope() // Inputs // FIXME: Eventually we should aim to move e.g. IsActiveIdUsingKey() into IsKeyXXX functions. @@ -2249,6 +2514,45 @@ namespace ImGui IMGUI_API float GetColumnOffsetFromNorm(const ImGuiColumns* columns, float offset_norm); IMGUI_API float GetColumnNormFromOffset(const ImGuiColumns* columns, float offset); + // Tables + IMGUI_API ImGuiTable* TableFindByID(ImGuiID id); + IMGUI_API bool BeginTableEx(const char* name, ImGuiID id, int columns_count, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0, 0), float inner_width = 0.0f); + IMGUI_API void TableBeginUpdateColumns(ImGuiTable* table); + IMGUI_API void TableUpdateDrawChannels(ImGuiTable* table); + IMGUI_API void TableUpdateLayout(ImGuiTable* table); + IMGUI_API void TableUpdateBorders(ImGuiTable* table); + IMGUI_API void TableSetColumnWidth(int column_n, float width); + IMGUI_API void TableSetColumnVisible(int column_n, bool visible); + IMGUI_API void TableDrawBorders(ImGuiTable* table); + IMGUI_API void TableDrawContextMenu(ImGuiTable* table); + IMGUI_API void TableOpenContextMenu(int column_n = -1); + IMGUI_API void TableReorderDrawChannelsForMerge(ImGuiTable* table); + IMGUI_API void TableSetColumnSortDirection(int column_n, ImGuiSortDirection sort_direction, bool append_to_sort_specs); + IMGUI_API void TableSortSpecsSanitize(ImGuiTable* table); + IMGUI_API void TableSortSpecsBuild(ImGuiTable* table); + IMGUI_API void TableBeginRow(ImGuiTable* table); + IMGUI_API void TableEndRow(ImGuiTable* table); + IMGUI_API void TableBeginCell(ImGuiTable* table, int column_n); + IMGUI_API void TableEndCell(ImGuiTable* table); + IMGUI_API ImRect TableGetCellBgRect(const ImGuiTable* table, int column_n); + IMGUI_API const char* TableGetColumnName(const ImGuiTable* table, int column_n); + IMGUI_API ImGuiID TableGetColumnResizeID(const ImGuiTable* table, int column_n, int instance_no = 0); + IMGUI_API void TableSetColumnAutofit(ImGuiTable* table, int column_n); + IMGUI_API void PushTableBackground(); + IMGUI_API void PopTableBackground(); + IMGUI_API void TableRemove(ImGuiTable* table); + IMGUI_API void TableGcCompactTransientBuffers(ImGuiTable* table); + IMGUI_API void TableGcCompactSettings(); + + // Tables: Settings + IMGUI_API void TableLoadSettings(ImGuiTable* table); + IMGUI_API void TableSaveSettings(ImGuiTable* table); + IMGUI_API ImGuiTableSettings* TableGetBoundSettings(ImGuiTable* table); + IMGUI_API void TableSettingsInstallHandler(ImGuiContext* context); + IMGUI_API ImGuiTableSettings* TableSettingsCreate(ImGuiID id, int columns_count); + IMGUI_API ImGuiTableSettings* TableSettingsFindByID(ImGuiID id); + IMGUI_API void TableSettingsClearByID(ImGuiID id); + // Tab Bars IMGUI_API bool BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& bb, ImGuiTabBarFlags flags, ImGuiDockNode* dock_node); IMGUI_API ImGuiTabItem* TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id); @@ -2325,6 +2629,7 @@ namespace ImGui template IMGUI_API bool DragBehaviorT(ImGuiDataType data_type, T* v, float v_speed, T v_min, T v_max, const char* format, ImGuiSliderFlags flags); template IMGUI_API bool SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, T* v, T v_min, T v_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb); template IMGUI_API T RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, T v); + template IMGUI_API bool CheckboxFlagsT(const char* label, T* flags, T flags_value); // Data type helpers IMGUI_API const ImGuiDataTypeInfo* DataTypeGetInfo(ImGuiDataType data_type); @@ -2354,6 +2659,7 @@ namespace ImGui IMGUI_API void ShadeVertsLinearUV(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, bool clamp); // Garbage collection + IMGUI_API void GcCompactTransientMiscBuffers(); IMGUI_API void GcCompactTransientWindowBuffers(ImGuiWindow* window); IMGUI_API void GcAwakeTransientWindowBuffers(ImGuiWindow* window); @@ -2367,6 +2673,8 @@ namespace ImGui IMGUI_API void DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, const ImDrawList* draw_list, const ImDrawCmd* draw_cmd, bool show_mesh, bool show_aabb); IMGUI_API void DebugNodeStorage(ImGuiStorage* storage, const char* label); IMGUI_API void DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label); + IMGUI_API void DebugNodeTable(ImGuiTable* table); + IMGUI_API void DebugNodeTableSettings(ImGuiTableSettings* settings); IMGUI_API void DebugNodeWindow(ImGuiWindow* window, const char* label); IMGUI_API void DebugNodeWindowSettings(ImGuiWindowSettings* settings); IMGUI_API void DebugNodeWindowsList(ImVector* windows, const char* label); diff --git a/libs/ImGui/source/imgui.cpp b/libs/ImGui/source/imgui.cpp index e38b4d8dd..11355609e 100644 --- a/libs/ImGui/source/imgui.cpp +++ b/libs/ImGui/source/imgui.cpp @@ -382,6 +382,7 @@ CODE - 2020/XX/XX (1.XX) - Moved IME support functions from io.ImeSetInputScreenPosFn, io.ImeWindowHandle to the PlatformIO api. + - 2020/11/03 (1.80) - renamed io.ConfigWindowsMemoryCompactTimer to io.ConfigMemoryCompactTimer as the feature will apply to other data structures - 2020/10/14 (1.80) - backends: moved all backends files (imgui_impl_XXXX.cpp, imgui_impl_XXXX.h) from examples/ to backends/. - 2020/10/12 (1.80) - removed redirecting functions/enums that were marked obsolete in 1.60 (April 2018): - io.RenderDrawListsFn pointer -> use ImGui::GetDrawData() value and call the render function of your backend @@ -873,6 +874,7 @@ static float NavUpdatePageUpPageDown(); static inline void NavUpdateAnyRequestFlag(); static void NavEndFrame(); static bool NavScoreItem(ImGuiNavMoveResult* result, ImRect cand); +static void NavApplyItemToResult(ImGuiNavMoveResult* result, ImGuiWindow* window, ImGuiID id, const ImRect& nav_bb_rel); static void NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, ImGuiID id); static ImVec2 NavCalcPreferredRefPos(); static void NavSaveLastChildNavWindowIntoParent(ImGuiWindow* nav_window); @@ -882,7 +884,6 @@ static int FindWindowFocusIndex(ImGuiWindow* window); // Error Checking static void ErrorCheckNewFrameSanityChecks(); static void ErrorCheckEndFrameSanityChecks(); -static void ErrorCheckBeginEndCompareStacksSize(ImGuiWindow* window, bool write); // Misc static void UpdateSettings(); @@ -971,6 +972,7 @@ ImGuiStyle::ImGuiStyle() FrameBorderSize = 0.0f; // Thickness of border around frames. Generally set to 0.0f or 1.0f. Other values not well tested. ItemSpacing = ImVec2(8,4); // Horizontal and vertical spacing between widgets/lines ItemInnerSpacing = ImVec2(4,4); // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label) + CellPadding = ImVec2(4,2); // Padding within a table cell TouchExtraPadding = ImVec2(0,0); // Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately we don't sort widgets so priority on overlap will always be given to the first widget. So don't grow this too much! IndentSpacing = 21.0f; // Horizontal spacing when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2). ColumnsMinSpacing = 6.0f; // Minimum horizontal spacing between two columns. Preferably > (FramePadding.x + 1). @@ -1011,6 +1013,7 @@ void ImGuiStyle::ScaleAllSizes(float scale_factor) FrameRounding = ImFloor(FrameRounding * scale_factor); ItemSpacing = ImFloor(ItemSpacing * scale_factor); ItemInnerSpacing = ImFloor(ItemInnerSpacing * scale_factor); + CellPadding = ImFloor(CellPadding * scale_factor); TouchExtraPadding = ImFloor(TouchExtraPadding * scale_factor); IndentSpacing = ImFloor(IndentSpacing * scale_factor); ColumnsMinSpacing = ImFloor(ColumnsMinSpacing * scale_factor); @@ -1076,7 +1079,7 @@ ImGuiIO::ImGuiIO() ConfigInputTextCursorBlink = true; ConfigWindowsResizeFromEdges = true; ConfigWindowsMoveFromTitleBarOnly = false; - ConfigWindowsMemoryCompactTimer = 60.0f; + ConfigMemoryCompactTimer = 60.0f; // Platform Functions BackendPlatformName = BackendRendererName = NULL; @@ -1590,66 +1593,58 @@ void* ImFileLoadToMemory(const char* filename, const char* mode, size_t* out_f //----------------------------------------------------------------------------- // Convert UTF-8 to 32-bit character, process single character input. -// Based on stb_from_utf8() from github.com/nothings/stb/ +// A nearly-branchless UTF-8 decoder, based on work of Christopher Wellons (https://github.com/skeeto/branchless-utf8). // We handle UTF-8 decoding error by skipping forward. int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end) { - unsigned int c = (unsigned int)-1; - const unsigned char* str = (const unsigned char*)in_text; - if (!(*str & 0x80)) + static const char lengths[32] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 0 }; + static const int masks[] = { 0x00, 0x7f, 0x1f, 0x0f, 0x07 }; + static const uint32_t mins[] = { 0x400000, 0, 0x80, 0x800, 0x10000 }; + static const int shiftc[] = { 0, 18, 12, 6, 0 }; + static const int shifte[] = { 0, 6, 4, 2, 0 }; + int len = lengths[*(const unsigned char*)in_text >> 3]; + int wanted = len + !len; + + if (in_text_end == NULL) + in_text_end = in_text + wanted; // Max length, nulls will be taken into account. + + // Copy at most 'len' bytes, stop copying at 0 or past in_text_end. Branch predictor does a good job here, + // so it is fast even with excessive branching. + unsigned char s[4]; + s[0] = in_text + 0 < in_text_end ? in_text[0] : 0; + s[1] = in_text + 1 < in_text_end ? in_text[1] : 0; + s[2] = in_text + 2 < in_text_end ? in_text[2] : 0; + s[3] = in_text + 3 < in_text_end ? in_text[3] : 0; + + // Assume a four-byte character and load four bytes. Unused bits are shifted out. + *out_char = (uint32_t)(s[0] & masks[len]) << 18; + *out_char |= (uint32_t)(s[1] & 0x3f) << 12; + *out_char |= (uint32_t)(s[2] & 0x3f) << 6; + *out_char |= (uint32_t)(s[3] & 0x3f) << 0; + *out_char >>= shiftc[len]; + + // Accumulate the various error conditions. + int e = 0; + e = (*out_char < mins[len]) << 6; // non-canonical encoding + e |= ((*out_char >> 11) == 0x1b) << 7; // surrogate half? + e |= (*out_char > IM_UNICODE_CODEPOINT_MAX) << 8; // out of range? + e |= (s[1] & 0xc0) >> 2; + e |= (s[2] & 0xc0) >> 4; + e |= (s[3] ) >> 6; + e ^= 0x2a; // top two bits of each tail byte correct? + e >>= shifte[len]; + + if (e) { - c = (unsigned int)(*str++); - *out_char = c; - return 1; + // No bytes are consumed when *in_text == 0 || in_text == in_text_end. + // One byte is consumed in case of invalid first byte of in_text. + // All available bytes (at most `len` bytes) are consumed on incomplete/invalid second to last bytes. + // Invalid or incomplete input may consume less bytes than wanted, therefore every byte has to be inspected in s. + wanted = ImMin(wanted, !!s[0] + !!s[1] + !!s[2] + !!s[3]); + *out_char = IM_UNICODE_CODEPOINT_INVALID; } - if ((*str & 0xe0) == 0xc0) - { - *out_char = IM_UNICODE_CODEPOINT_INVALID; // will be invalid but not end of string - if (in_text_end && in_text_end - (const char*)str < 2) return 1; - if (*str < 0xc2) return 2; - c = (unsigned int)((*str++ & 0x1f) << 6); - if ((*str & 0xc0) != 0x80) return 2; - c += (*str++ & 0x3f); - *out_char = c; - return 2; - } - if ((*str & 0xf0) == 0xe0) - { - *out_char = IM_UNICODE_CODEPOINT_INVALID; // will be invalid but not end of string - if (in_text_end && in_text_end - (const char*)str < 3) return 1; - if (*str == 0xe0 && (str[1] < 0xa0 || str[1] > 0xbf)) return 3; - if (*str == 0xed && str[1] > 0x9f) return 3; // str[1] < 0x80 is checked below - c = (unsigned int)((*str++ & 0x0f) << 12); - if ((*str & 0xc0) != 0x80) return 3; - c += (unsigned int)((*str++ & 0x3f) << 6); - if ((*str & 0xc0) != 0x80) return 3; - c += (*str++ & 0x3f); - *out_char = c; - return 3; - } - if ((*str & 0xf8) == 0xf0) - { - *out_char = IM_UNICODE_CODEPOINT_INVALID; // will be invalid but not end of string - if (in_text_end && in_text_end - (const char*)str < 4) return 1; - if (*str > 0xf4) return 4; - if (*str == 0xf0 && (str[1] < 0x90 || str[1] > 0xbf)) return 4; - if (*str == 0xf4 && str[1] > 0x8f) return 4; // str[1] < 0x80 is checked below - c = (unsigned int)((*str++ & 0x07) << 18); - if ((*str & 0xc0) != 0x80) return 4; - c += (unsigned int)((*str++ & 0x3f) << 12); - if ((*str & 0xc0) != 0x80) return 4; - c += (unsigned int)((*str++ & 0x3f) << 6); - if ((*str & 0xc0) != 0x80) return 4; - c += (*str++ & 0x3f); - // utf-8 encodings of values used in surrogate pairs are invalid - if ((c & 0xFFFFF800) == 0xD800) return 4; - // If codepoint does not fit in ImWchar, use replacement character U+FFFD instead - if (c > IM_UNICODE_CODEPOINT_MAX) c = IM_UNICODE_CODEPOINT_INVALID; - *out_char = c; - return 4; - } - *out_char = 0; - return 0; + + return wanted; } int ImTextStrFromUtf8(ImWchar* buf, int buf_size, const char* in_text, const char* in_text_end, const char** in_text_remaining) @@ -2177,6 +2172,14 @@ void ImGuiTextBuffer::appendfv(const char* fmt, va_list args) // the API mid-way through development and support two ways to using the clipper, needs some rework (see TODO) //----------------------------------------------------------------------------- +// FIXME-TABLE: This prevents us from using ImGuiListClipper _inside_ a table cell. +// The problem we have is that without a Begin/End scheme for rows using the clipper is ambiguous. +static bool GetSkipItemForListClipping() +{ + ImGuiContext& g = *GImGui; + return (g.CurrentTable ? g.CurrentTable->HostSkipItems : g.CurrentWindow->SkipItems); +} + // Helper to calculate coarse clipping of large list of evenly sized items. // NB: Prefer using the ImGuiListClipper higher-level helper if you can! Read comments and instructions there on how those use this sort of pattern. // NB: 'items_count' is only used to clamp the result, if you don't know your count you can use INT_MAX @@ -2191,7 +2194,7 @@ void ImGui::CalcListClipping(int items_count, float items_height, int* out_items *out_items_display_end = items_count; return; } - if (window->SkipItems) + if (GetSkipItemForListClipping()) { *out_items_display_start = *out_items_display_end = 0; return; @@ -2227,12 +2230,20 @@ static void SetCursorPosYAndSetupForPrevLine(float pos_y, float line_height) // The clipper should probably have a 4th step to display the last item in a regular manner. ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + float off_y = pos_y - window->DC.CursorPos.y; window->DC.CursorPos.y = pos_y; window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, pos_y); window->DC.CursorPosPrevLine.y = window->DC.CursorPos.y - line_height; // Setting those fields so that SetScrollHereY() can properly function after the end of our clipper usage. window->DC.PrevLineSize.y = (line_height - g.Style.ItemSpacing.y); // If we end up needing more accurate data (to e.g. use SameLine) we may as well make the clipper have a fourth step to let user process and display the last item in their list. if (ImGuiColumns* columns = window->DC.CurrentColumns) columns->LineMinY = window->DC.CursorPos.y; // Setting this so that cell Y position are set properly + if (ImGuiTable* table = g.CurrentTable) + { + if (table->IsInsideRow) + ImGui::TableEndRow(table); + table->RowPosY2 = window->DC.CursorPos.y; + table->RowBgColorCounter += (int)((off_y / line_height) + 0.5f); + } } ImGuiListClipper::ImGuiListClipper() @@ -2254,9 +2265,14 @@ void ImGuiListClipper::Begin(int items_count, float items_height) ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + if (ImGuiTable* table = g.CurrentTable) + if (table->IsInsideRow) + ImGui::TableEndRow(table); + StartPosY = window->DC.CursorPos.y; ItemsHeight = items_height; ItemsCount = items_count; + ItemsFrozen = 0; StepNo = 0; DisplayStart = -1; DisplayEnd = 0; @@ -2269,7 +2285,7 @@ void ImGuiListClipper::End() // In theory here we should assert that ImGui::GetCursorPosY() == StartPosY + DisplayEnd * ItemsHeight, but it feels saner to just seek at the end and not assert/crash the user. if (ItemsCount < INT_MAX && DisplayStart >= 0) - SetCursorPosYAndSetupForPrevLine(StartPosY + ItemsCount * ItemsHeight, ItemsHeight); + SetCursorPosYAndSetupForPrevLine(StartPosY + (ItemsCount - ItemsFrozen) * ItemsHeight, ItemsHeight); ItemsCount = -1; StepNo = 3; } @@ -2279,8 +2295,12 @@ bool ImGuiListClipper::Step() ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + ImGuiTable* table = g.CurrentTable; + if (table && table->IsInsideRow) + ImGui::TableEndRow(table); + // Reached end of list - if (DisplayEnd >= ItemsCount || window->SkipItems) + if (DisplayEnd >= ItemsCount || GetSkipItemForListClipping()) { End(); return false; @@ -2289,12 +2309,22 @@ bool ImGuiListClipper::Step() // Step 0: Let you process the first element (regardless of it being visible or not, so we can measure the element height) if (StepNo == 0) { + // While we are in frozen row state, keep displaying items one by one, unclipped + // FIXME: Could be stored as a table-agnostic state. + if (table != NULL && !table->IsUnfrozen) + { + DisplayStart = ItemsFrozen; + DisplayEnd = ItemsFrozen + 1; + ItemsFrozen++; + return true; + } + StartPosY = window->DC.CursorPos.y; if (ItemsHeight <= 0.0f) { // Submit the first item so we can measure its height (generally it is 0..1) - DisplayStart = 0; - DisplayEnd = 1; + DisplayStart = ItemsFrozen; + DisplayEnd = ItemsFrozen + 1; StepNo = 1; return true; } @@ -2308,7 +2338,17 @@ bool ImGuiListClipper::Step() if (StepNo == 1) { IM_ASSERT(ItemsHeight <= 0.0f); - ItemsHeight = window->DC.CursorPos.y - StartPosY; + if (table) + { + const float pos_y1 = table->RowPosY1; // Using this instead of StartPosY to handle clipper straddling the frozen row + const float pos_y2 = table->RowPosY2; // Using this instead of CursorPos.y to take account of tallest cell. + ItemsHeight = pos_y2 - pos_y1; + window->DC.CursorPos.y = pos_y2; + } + else + { + ItemsHeight = window->DC.CursorPos.y - StartPosY; + } IM_ASSERT(ItemsHeight > 0.0f && "Unable to calculate item height! First item hasn't moved the cursor vertically!"); StepNo = 2; } @@ -2325,7 +2365,7 @@ bool ImGuiListClipper::Step() // Seek cursor if (DisplayStart > already_submitted) - SetCursorPosYAndSetupForPrevLine(StartPosY + DisplayStart * ItemsHeight, ItemsHeight); + SetCursorPosYAndSetupForPrevLine(StartPosY + (DisplayStart - ItemsFrozen) * ItemsHeight, ItemsHeight); StepNo = 3; return true; @@ -2337,7 +2377,7 @@ bool ImGuiListClipper::Step() { // Seek cursor if (ItemsCount < INT_MAX) - SetCursorPosYAndSetupForPrevLine(StartPosY + ItemsCount * ItemsHeight, ItemsHeight); // advance cursor + SetCursorPosYAndSetupForPrevLine(StartPosY + (ItemsCount - ItemsFrozen) * ItemsHeight, ItemsHeight); // advance cursor ItemsCount = -1; return false; } @@ -2395,7 +2435,7 @@ void ImGui::PushStyleColor(ImGuiCol idx, ImU32 col) ImGuiColorMod backup; backup.Col = idx; backup.BackupValue = g.Style.Colors[idx]; - g.ColorModifiers.push_back(backup); + g.ColorStack.push_back(backup); g.Style.Colors[idx] = ColorConvertU32ToFloat4(col); } @@ -2405,7 +2445,7 @@ void ImGui::PushStyleColor(ImGuiCol idx, const ImVec4& col) ImGuiColorMod backup; backup.Col = idx; backup.BackupValue = g.Style.Colors[idx]; - g.ColorModifiers.push_back(backup); + g.ColorStack.push_back(backup); g.Style.Colors[idx] = col; } @@ -2414,9 +2454,9 @@ void ImGui::PopStyleColor(int count) ImGuiContext& g = *GImGui; while (count > 0) { - ImGuiColorMod& backup = g.ColorModifiers.back(); + ImGuiColorMod& backup = g.ColorStack.back(); g.Style.Colors[backup.Col] = backup.BackupValue; - g.ColorModifiers.pop_back(); + g.ColorStack.pop_back(); count--; } } @@ -2447,6 +2487,7 @@ static const ImGuiStyleVarInfo GStyleVarInfo[] = { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemSpacing) }, // ImGuiStyleVar_ItemSpacing { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemInnerSpacing) }, // ImGuiStyleVar_ItemInnerSpacing { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, IndentSpacing) }, // ImGuiStyleVar_IndentSpacing + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, CellPadding) }, // ImGuiStyleVar_CellPadding { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarSize) }, // ImGuiStyleVar_ScrollbarSize { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarRounding) }, // ImGuiStyleVar_ScrollbarRounding { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabMinSize) }, // ImGuiStyleVar_GrabMinSize @@ -2470,7 +2511,7 @@ void ImGui::PushStyleVar(ImGuiStyleVar idx, float val) { ImGuiContext& g = *GImGui; float* pvar = (float*)var_info->GetVarPtr(&g.Style); - g.StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar)); + g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar)); *pvar = val; return; } @@ -2484,7 +2525,7 @@ void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val) { ImGuiContext& g = *GImGui; ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&g.Style); - g.StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar)); + g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar)); *pvar = val; return; } @@ -2497,12 +2538,12 @@ void ImGui::PopStyleVar(int count) while (count > 0) { // We avoid a generic memcpy(data, &backup.Backup.., GDataTypeSize[info->Type] * info->Count), the overhead in Debug is not worth it. - ImGuiStyleMod& backup = g.StyleModifiers.back(); + ImGuiStyleMod& backup = g.StyleVarStack.back(); const ImGuiStyleVarInfo* info = GetStyleVarInfo(backup.VarIdx); void* data = info->GetVarPtr(&g.Style); if (info->Type == ImGuiDataType_Float && info->Count == 1) { ((float*)data)[0] = backup.BackupFloat[0]; } else if (info->Type == ImGuiDataType_Float && info->Count == 2) { ((float*)data)[0] = backup.BackupFloat[0]; ((float*)data)[1] = backup.BackupFloat[1]; } - g.StyleModifiers.pop_back(); + g.StyleVarStack.pop_back(); count--; } } @@ -2556,6 +2597,11 @@ const char* ImGui::GetStyleColorName(ImGuiCol idx) case ImGuiCol_PlotLinesHovered: return "PlotLinesHovered"; case ImGuiCol_PlotHistogram: return "PlotHistogram"; case ImGuiCol_PlotHistogramHovered: return "PlotHistogramHovered"; + case ImGuiCol_TableHeaderBg: return "TableHeaderBg"; + case ImGuiCol_TableBorderStrong: return "TableBorderStrong"; + case ImGuiCol_TableBorderLight: return "TableBorderLight"; + case ImGuiCol_TableRowBg: return "TableRowBg"; + case ImGuiCol_TableRowBgAlt: return "TableRowBgAlt"; case ImGuiCol_TextSelectedBg: return "TextSelectedBg"; case ImGuiCol_DragDropTarget: return "DragDropTarget"; case ImGuiCol_NavHighlight: return "NavHighlight"; @@ -2819,83 +2865,31 @@ void ImGui::RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFl //----------------------------------------------------------------------------- // ImGuiWindow is mostly a dumb struct. It merely has a constructor and a few helper methods -ImGuiWindow::ImGuiWindow(ImGuiContext* context, const char* name) - : DrawListInst(&context->DrawListSharedData) +ImGuiWindow::ImGuiWindow(ImGuiContext* context, const char* name) : DrawListInst(NULL) { + memset(this, 0, sizeof(*this)); Name = ImStrdup(name); + NameBufLen = (int)strlen(name) + 1; ID = ImHashStr(name); IDStack.push_back(ID); - Flags = FlagsPreviousFrame = ImGuiWindowFlags_None; - Viewport = NULL; - ViewportId = 0; ViewportAllowPlatformMonitorExtend = -1; ViewportPos = ImVec2(FLT_MAX, FLT_MAX); - Pos = ImVec2(0.0f, 0.0f); - Size = SizeFull = ImVec2(0.0f, 0.0f); - ContentSize = ContentSizeExplicit = ImVec2(0.0f, 0.0f); - WindowPadding = ImVec2(0.0f, 0.0f); - WindowRounding = 0.0f; - WindowBorderSize = 0.0f; - NameBufLen = (int)strlen(name) + 1; MoveId = GetID("#MOVE"); - ChildId = 0; - Scroll = ImVec2(0.0f, 0.0f); ScrollTarget = ImVec2(FLT_MAX, FLT_MAX); ScrollTargetCenterRatio = ImVec2(0.5f, 0.5f); - ScrollbarSizes = ImVec2(0.0f, 0.0f); - ScrollbarX = ScrollbarY = false; - ViewportOwned = false; - Active = WasActive = false; - WriteAccessed = false; - Collapsed = false; - WantCollapseToggle = false; - SkipItems = false; - Appearing = false; - Hidden = false; - IsFallbackWindow = false; - HasCloseButton = false; - ResizeBorderHeld = -1; - BeginCount = 0; - BeginOrderWithinParent = -1; - BeginOrderWithinContext = -1; - PopupId = 0; AutoFitFramesX = AutoFitFramesY = -1; - AutoFitChildAxises = 0x00; - AutoFitOnlyGrows = false; AutoPosLastDirection = ImGuiDir_None; - HiddenFramesCanSkipItems = HiddenFramesCannotSkipItems = 0; SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = SetWindowDockAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing; SetWindowPosVal = SetWindowPosPivot = ImVec2(FLT_MAX, FLT_MAX); - - InnerRect = ImRect(0.0f, 0.0f, 0.0f, 0.0f); // Clear so the InnerRect.GetSize() code in Begin() doesn't lead to overflow even if the result isn't used. - LastFrameActive = -1; LastFrameJustFocused = -1; LastTimeActive = -1.0f; - ItemWidthDefault = 0.0f; FontWindowScale = FontDpiScale = 1.0f; SettingsOffset = -1; - - DrawList = &DrawListInst; - DrawList->_OwnerName = Name; - ParentWindow = NULL; - RootWindow = NULL; - RootWindowDockStop = NULL; - RootWindowForTitleBarHighlight = NULL; - RootWindowForNav = NULL; - - NavLastIds[0] = NavLastIds[1] = 0; - NavRectRel[0] = NavRectRel[1] = ImRect(); - NavLastChildNavWindow = NULL; - - MemoryCompacted = false; - MemoryDrawListIdxCapacity = MemoryDrawListVtxCapacity = 0; - - DockNode = DockNodeAsHost = NULL; - DockId = 0; - DockTabItemStatusFlags = ImGuiItemStatusFlags_None; DockOrder = -1; - DockIsActive = DockTabIsVisible = DockTabWantClose = false; + DrawList = &DrawListInst; + DrawList->_Data = &context->DrawListSharedData; + DrawList->_OwnerName = Name; } ImGuiWindow::~ImGuiWindow() @@ -2989,15 +2983,22 @@ static void SetCurrentWindow(ImGuiWindow* window) { ImGuiContext& g = *GImGui; g.CurrentWindow = window; + g.CurrentTable = window && window->DC.CurrentTableIdx != -1 ? g.Tables.GetByIndex(window->DC.CurrentTableIdx) : NULL; if (window) g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); } +void ImGui::GcCompactTransientMiscBuffers() +{ + ImGuiContext& g = *GImGui; + g.ItemFlagsStack.clear(); + g.GroupStack.clear(); + TableGcCompactSettings(); +} + // Free up/compact internal window buffers, we can use this when a window becomes unused. -// This is currently unused by the library, but you may call this yourself for easy GC. // Not freed: -// - ImGuiWindow, ImGuiWindowSettings, Name -// - StateStorage, ColumnsStorage (may hold useful data) +// - ImGuiWindow, ImGuiWindowSettings, Name, StateStorage, ColumnsStorage (may hold useful data) // This should have no noticeable visual effect. When the window reappear however, expect new allocation/buffer growth/copy cost. void ImGui::GcCompactTransientWindowBuffers(ImGuiWindow* window) { @@ -3007,10 +3008,8 @@ void ImGui::GcCompactTransientWindowBuffers(ImGuiWindow* window) window->IDStack.clear(); window->DrawList->_ClearFreeMemory(); window->DC.ChildWindows.clear(); - window->DC.ItemFlagsStack.clear(); window->DC.ItemWidthStack.clear(); window->DC.TextWrapPosStack.clear(); - window->DC.GroupStack.clear(); } void ImGui::GcAwakeTransientWindowBuffers(ImGuiWindow* window) @@ -4072,7 +4071,7 @@ void ImGui::NewFrame() // Mark all windows as not visible and compact unused memory. IM_ASSERT(g.WindowsFocusOrder.Size == g.Windows.Size); - const float memory_compact_start_time = (g.IO.ConfigWindowsMemoryCompactTimer >= 0.0f) ? (float)g.Time - g.IO.ConfigWindowsMemoryCompactTimer : FLT_MAX; + const float memory_compact_start_time = (g.GcCompactAll || g.IO.ConfigMemoryCompactTimer < 0.0f) ? FLT_MAX : (float)g.Time - g.IO.ConfigMemoryCompactTimer; for (int i = 0; i != g.Windows.Size; i++) { ImGuiWindow* window = g.Windows[i]; @@ -4086,6 +4085,14 @@ void ImGui::NewFrame() GcCompactTransientWindowBuffers(window); } + // Garbage collect transient buffers of recently unused tables + for (int i = 0; i < g.TablesLastTimeActive.Size; i++) + if (g.TablesLastTimeActive[i] >= 0.0f && g.TablesLastTimeActive[i] < memory_compact_start_time) + TableGcCompactTransientBuffers(g.Tables.GetByIndex(i)); + if (g.GcCompactAll) + GcCompactTransientMiscBuffers(); + g.GcCompactAll = false; + // Closing the focused window restore focus to the first active root window in descending z-order if (g.NavWindow && !g.NavWindow->WasActive) FocusTopMostWindowUnderOne(NULL, NULL); @@ -4094,6 +4101,9 @@ void ImGui::NewFrame() // 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.BeginPopupStack.resize(0); + g.ItemFlagsStack.resize(0); + g.ItemFlagsStack.push_back(ImGuiItemFlags_Default_); + g.GroupStack.resize(0); ClosePopupsOverWindow(g.NavWindow, false); // Docking @@ -4158,15 +4168,7 @@ void ImGui::Initialize(ImGuiContext* context) #ifdef IMGUI_HAS_TABLE // Add .ini handle for ImGuiTable type - { - ImGuiSettingsHandler ini_handler; - ini_handler.TypeName = "Table"; - ini_handler.TypeHash = ImHashStr("Table"); - ini_handler.ReadOpenFn = TableSettingsHandler_ReadOpen; - ini_handler.ReadLineFn = TableSettingsHandler_ReadLine; - ini_handler.WriteAllFn = TableSettingsHandler_WriteAll; - g.SettingsHandlers.push_back(ini_handler); - } + TableSettingsInstallHandler(context); #endif // #ifdef IMGUI_HAS_TABLE #ifdef IMGUI_HAS_DOCK @@ -4235,8 +4237,8 @@ void ImGui::Shutdown(ImGuiContext* context) g.HoveredWindow = g.HoveredRootWindow = g.HoveredWindowUnderMovingWindow = NULL; g.ActiveIdWindow = g.ActiveIdPreviousFrameWindow = NULL; g.MovingWindow = NULL; - g.ColorModifiers.clear(); - g.StyleModifiers.clear(); + g.ColorStack.clear(); + g.StyleVarStack.clear(); g.FontStack.clear(); g.OpenPopupStack.clear(); g.BeginPopupStack.clear(); @@ -4250,6 +4252,10 @@ void ImGui::Shutdown(ImGuiContext* context) g.CurrentTabBarStack.clear(); g.ShrinkWidthBuffer.clear(); + g.Tables.Clear(); + g.CurrentTableStack.clear(); + g.DrawChannelsTempMergeBuffer.clear(); + g.ClipboardHandlerData.clear(); g.MenusIdSubmittedThisFrame.clear(); g.InputTextState.ClearFreeMemory(); @@ -4302,7 +4308,7 @@ static void AddWindowToSortBuffer(ImVector* out_sorted_windows, Im static void AddDrawListToDrawData(ImVector* out_list, ImDrawList* draw_list) { // Remove trailing command if unused. - // Technically we could return directly instead of popping, but this make things looks neat in Metrics window as well. + // Technically we could return directly instead of popping, but this make things looks neat in Metrics/Debugger window as well. draw_list->_PopUnusedDrawCmd(); if (draw_list->CmdBuffer.Size == 0) return; @@ -4317,7 +4323,7 @@ static void AddDrawListToDrawData(ImVector* out_list, ImDrawList* d // Check that draw_list doesn't use more vertices than indexable (default ImDrawIdx = unsigned short = 2 bytes = 64K vertices per ImDrawList = per window) // If this assert triggers because you are drawing lots of stuff manually: // - First, make sure you are coarse clipping yourself and not trying to draw many things outside visible bounds. - // Be mindful that the ImDrawList API doesn't filter vertices. Use the Metrics window to inspect draw list contents. + // Be mindful that the ImDrawList API doesn't filter vertices. Use the Metrics/Debugger window to inspect draw list contents. // - If you want large meshes with more than 64K vertices, you can either: // (A) Handle the ImDrawCmd::VtxOffset value in your renderer backend, and set 'io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset'. // Most example backends already support this from 1.71. Pre-1.71 backends won't. @@ -4995,12 +5001,13 @@ bool ImGui::IsItemDeactivatedAfterEdit() return IsItemDeactivated() && (g.ActiveIdPreviousFrameHasBeenEditedBefore || (g.ActiveId == 0 && g.ActiveIdHasBeenEditedBefore)); } +// == GetItemID() == GetFocusID() bool ImGui::IsItemFocused() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - if (g.NavId == 0 || g.NavDisableHighlight || g.NavId != window->DC.LastItemId) + if (g.NavId != window->DC.LastItemId || g.NavId == 0) return false; // Special handling for the dummy item after Begin() which represent the title bar or tab. @@ -5977,8 +5984,10 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Add to stack // We intentionally set g.CurrentWindow to NULL to prevent usage until when the viewport is set, then will call SetCurrentWindow() g.CurrentWindowStack.push_back(window); + g.CurrentWindow = window; + window->DC.StackSizesOnBegin.SetToCurrentState(); g.CurrentWindow = NULL; - ErrorCheckBeginEndCompareStacksSize(window, true); + if (flags & ImGuiWindowFlags_Popup) { ImGuiPopupData& popup_ref = g.OpenPopupStack[g.BeginPopupStack.Size]; @@ -6056,6 +6065,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->ClipRect = ImVec4(-FLT_MAX, -FLT_MAX, +FLT_MAX, +FLT_MAX); window->IDStack.resize(1); window->DrawList->_ResetForNewFrame(); + window->DC.CurrentTableIdx = -1; // Restore buffer capacity when woken from a compacted state, to avoid if (window->MemoryCompacted) @@ -6575,7 +6585,6 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->DC.NavLayerCurrent = ImGuiNavLayer_Main; window->DC.NavLayerActiveMask = window->DC.NavLayerActiveMaskNext; window->DC.NavLayerActiveMaskNext = 0x00; - window->DC.NavFocusScopeIdCurrent = (flags & ImGuiWindowFlags_ChildWindow) ? parent_window->DC.NavFocusScopeIdCurrent : 0; // -V595 window->DC.NavHideHighlightOneFrame = false; window->DC.NavHasScroll = (window->ScrollMax.y > 0.0f); @@ -6592,13 +6601,8 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->DC.ItemWidth = window->ItemWidthDefault; window->DC.TextWrapPos = -1.0f; // disabled - window->DC.ItemFlagsStack.resize(0); window->DC.ItemWidthStack.resize(0); window->DC.TextWrapPosStack.resize(0); - window->DC.GroupStack.resize(0); - window->DC.ItemFlags = parent_window ? parent_window->DC.ItemFlags : ImGuiItemFlags_Default_; - if (parent_window) - window->DC.ItemFlagsStack.push_back(window->DC.ItemFlags); if (window->AutoFitFramesX > 0) window->AutoFitFramesX--; @@ -6674,13 +6678,15 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) SetCurrentWindow(window); } + // Pull/inherit current state + window->DC.ItemFlags = g.ItemFlagsStack.back(); // Inherit from shared stack + window->DC.NavFocusScopeIdCurrent = (flags & ImGuiWindowFlags_ChildWindow) ? parent_window->DC.NavFocusScopeIdCurrent : 0; // Inherit from parent only // -V595 + if (!(flags & ImGuiWindowFlags_DockNodeHost)) PushClipRect(window->InnerClipRect.Min, window->InnerClipRect.Max, true); // Clear 'accessed' flag last thing (After PushClipRect which will set the flag. We want the flag to stay false when the default "Debug" window is unused) - if (first_begin_of_the_frame) - window->WriteAccessed = false; - + window->WriteAccessed = false; window->BeginCount++; g.NextWindowData.ClearFlags(); @@ -6769,7 +6775,7 @@ void ImGui::End() g.CurrentWindowStack.pop_back(); if (window->Flags & ImGuiWindowFlags_Popup) g.BeginPopupStack.pop_back(); - ErrorCheckBeginEndCompareStacksSize(window, false); + window->DC.StackSizesOnBegin.CompareWithCurrentState(); SetCurrentWindow(g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back()); if (g.CurrentWindow) SetCurrentViewport(g.CurrentWindow, g.CurrentWindow->Viewport); @@ -6935,19 +6941,25 @@ void ImGui::PopFont() void ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled) { - ImGuiWindow* window = GetCurrentWindow(); + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiItemFlags item_flags = window->DC.ItemFlags; + IM_ASSERT(item_flags == g.ItemFlagsStack.back()); if (enabled) - window->DC.ItemFlags |= option; + item_flags |= option; else - window->DC.ItemFlags &= ~option; - window->DC.ItemFlagsStack.push_back(window->DC.ItemFlags); + item_flags &= ~option; + window->DC.ItemFlags = item_flags; + g.ItemFlagsStack.push_back(item_flags); } void ImGui::PopItemFlag() { - ImGuiWindow* window = GetCurrentWindow(); - window->DC.ItemFlagsStack.pop_back(); - window->DC.ItemFlags = window->DC.ItemFlagsStack.empty() ? ImGuiItemFlags_Default_ : window->DC.ItemFlagsStack.back(); + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + IM_ASSERT(g.ItemFlagsStack.Size > 1); // Too many calls to PopItemFlag() - we always leave a 0 at the bottom of the stack. + g.ItemFlagsStack.pop_back(); + window->DC.ItemFlags = g.ItemFlagsStack.back(); } // FIXME: Look into renaming this once we have settled the new Focus/Activation/TabStop system. @@ -7377,12 +7389,11 @@ void ImGui::ActivateItem(ImGuiID id) g.NavNextActivateId = id; } -// Note: this is storing in same stack as IDStack, so Push/Pop mismatch will be reported there. void ImGui::PushFocusScope(ImGuiID id) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - window->IDStack.push_back(window->DC.NavFocusScopeIdCurrent); + g.FocusScopeStack.push_back(window->DC.NavFocusScopeIdCurrent); window->DC.NavFocusScopeIdCurrent = id; } @@ -7390,8 +7401,9 @@ void ImGui::PopFocusScope() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - window->DC.NavFocusScopeIdCurrent = window->IDStack.back(); - window->IDStack.pop_back(); + IM_ASSERT(g.FocusScopeStack.Size > 0); // Too many PopFocusScope() ? + window->DC.NavFocusScopeIdCurrent = g.FocusScopeStack.back(); + g.FocusScopeStack.pop_back(); } void ImGui::SetKeyboardFocusHere(int offset) @@ -7490,6 +7502,7 @@ ImGuiID ImGui::GetIDWithSeed(const char* str, const char* str_end, ImGuiID seed) void ImGui::PopID() { ImGuiWindow* window = GImGui->CurrentWindow; + IM_ASSERT(window->IDStack.Size > 1); // Too many PopID(), or could be popping in a wrong/different window? window->IDStack.pop_back(); } @@ -7628,9 +7641,13 @@ static void ImGui::ErrorCheckEndFrameSanityChecks() // Verify that io.KeyXXX fields haven't been tampered with. Key mods should not be modified between NewFrame() and EndFrame() // One possible reason leading to this assert is that your backends update inputs _AFTER_ NewFrame(). - const ImGuiKeyModFlags expected_key_mod_flags = GetMergedKeyModFlags(); - IM_ASSERT(g.IO.KeyMods == expected_key_mod_flags && "Mismatching io.KeyCtrl/io.KeyShift/io.KeyAlt/io.KeySuper vs io.KeyMods"); - IM_UNUSED(expected_key_mod_flags); + // It is known that when some modal native windows called mid-frame takes focus away, some backends such as GLFW will + // send key release events mid-frame. This would normally trigger this assertion and lead to sheared inputs. + // We silently accommodate for this case by ignoring/ the case where all io.KeyXXX modifiers were released (aka key_mod_flags == 0), + // while still correctly asserting on mid-frame key press events. + const ImGuiKeyModFlags key_mod_flags = GetMergedKeyModFlags(); + IM_ASSERT((key_mod_flags == 0 || g.IO.KeyMods == key_mod_flags) && "Mismatching io.KeyCtrl/io.KeyShift/io.KeyAlt/io.KeySuper vs io.KeyMods"); + IM_UNUSED(key_mod_flags); // Report when there is a mismatch of Begin/BeginChild vs End/EndChild calls. Important: Remember that the Begin/BeginChild API requires you // to always call End/EndChild even if Begin/BeginChild returns false! (this is unfortunately inconsistent with most other Begin* API). @@ -7647,28 +7664,42 @@ static void ImGui::ErrorCheckEndFrameSanityChecks() IM_ASSERT_USER_ERROR(g.CurrentWindowStack.Size == 1, "Mismatched Begin/BeginChild vs End/EndChild calls: did you call End/EndChild too much?"); } } + + IM_ASSERT_USER_ERROR(g.GroupStack.Size == 0, "Missing EndGroup call!"); } -// Save and compare stack sizes on Begin()/End() to detect usage errors -// Begin() calls this with write=true -// End() calls this with write=false -static void ImGui::ErrorCheckBeginEndCompareStacksSize(ImGuiWindow* window, bool write) +// Save current stack sizes for later compare +void ImGuiStackSizes::SetToCurrentState() { ImGuiContext& g = *GImGui; - short* p = &window->DC.StackSizesBackup[0]; + ImGuiWindow* window = g.CurrentWindow; + SizeOfIDStack = (short)window->IDStack.Size; + SizeOfColorStack = (short)g.ColorStack.Size; + SizeOfStyleVarStack = (short)g.StyleVarStack.Size; + SizeOfFontStack = (short)g.FontStack.Size; + SizeOfFocusScopeStack = (short)g.FocusScopeStack.Size; + SizeOfGroupStack = (short)g.GroupStack.Size; + SizeOfBeginPopupStack = (short)g.BeginPopupStack.Size; +} + +// Compare to detect usage errors +void ImGuiStackSizes::CompareWithCurrentState() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; // Window stacks - // 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) - { int n = window->IDStack.Size; if (write) *p = (short)n; else IM_ASSERT(*p == n && "PushID/PopID or TreeNode/TreePop Mismatch!"); p++; } // Too few or too many PopID()/TreePop() - { int n = window->DC.GroupStack.Size; if (write) *p = (short)n; else IM_ASSERT(*p == n && "BeginGroup/EndGroup Mismatch!"); p++; } // Too few or too many EndGroup() + // NOT checking: DC.ItemWidth, DC.TextWrapPos (per window) to allow user to conveniently push once and not pop (they are cleared on Begin) + IM_ASSERT(SizeOfIDStack == window->IDStack.Size && "PushID/PopID or TreeNode/TreePop Mismatch!"); // Global stacks // For color, style and font stacks there is an incentive to use Push/Begin/Pop/.../End patterns, so we relax our checks a little to allow them. - { int n = g.BeginPopupStack.Size; if (write) *p = (short)n; else IM_ASSERT(*p == n && "BeginMenu/EndMenu or BeginPopup/EndPopup Mismatch!"); p++; }// Too few or too many EndMenu()/EndPopup() - { int n = g.ColorModifiers.Size; if (write) *p = (short)n; else IM_ASSERT(*p >= n && "PushStyleColor/PopStyleColor Mismatch!"); p++; } // Too few or too many PopStyleColor() - { int n = g.StyleModifiers.Size; if (write) *p = (short)n; else IM_ASSERT(*p >= n && "PushStyleVar/PopStyleVar Mismatch!"); p++; } // Too few or too many PopStyleVar() - { int n = g.FontStack.Size; if (write) *p = (short)n; else IM_ASSERT(*p >= n && "PushFont/PopFont Mismatch!"); p++; } // Too few or too many PopFont() - IM_ASSERT(p == window->DC.StackSizesBackup + IM_ARRAYSIZE(window->DC.StackSizesBackup)); + IM_ASSERT(SizeOfGroupStack == g.GroupStack.Size && "BeginGroup/EndGroup Mismatch!"); + IM_ASSERT(SizeOfBeginPopupStack == g.BeginPopupStack.Size && "BeginPopup/EndPopup or BeginMenu/EndMenu Mismatch!"); + IM_ASSERT(SizeOfColorStack >= g.ColorStack.Size && "PushStyleColor/PopStyleColor Mismatch!"); + IM_ASSERT(SizeOfStyleVarStack >= g.StyleVarStack.Size && "PushStyleVar/PopStyleVar Mismatch!"); + IM_ASSERT(SizeOfFontStack >= g.FontStack.Size && "PushFont/PopFont Mismatch!"); + IM_ASSERT(SizeOfFocusScopeStack == g.FocusScopeStack.Size && "PushFocusScope/PopFocusScope Mismatch!"); } @@ -8023,7 +8054,7 @@ ImVec2 ImGui::GetContentRegionMax() ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImVec2 mx = window->ContentRegionRect.Max - window->Pos; - if (window->DC.CurrentColumns) + if (window->DC.CurrentColumns || g.CurrentTable) mx.x = window->WorkRect.Max.x - window->Pos.x; return mx; } @@ -8034,7 +8065,7 @@ ImVec2 ImGui::GetContentRegionMaxAbs() ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImVec2 mx = window->ContentRegionRect.Max; - if (window->DC.CurrentColumns) + if (window->DC.CurrentColumns || g.CurrentTable) mx.x = window->WorkRect.Max.x; return mx; } @@ -8071,8 +8102,9 @@ void ImGui::BeginGroup() ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - window->DC.GroupStack.resize(window->DC.GroupStack.Size + 1); - ImGuiGroupData& group_data = window->DC.GroupStack.back(); + g.GroupStack.resize(g.GroupStack.Size + 1); + ImGuiGroupData& group_data = g.GroupStack.back(); + group_data.WindowID = window->ID; group_data.BackupCursorPos = window->DC.CursorPos; group_data.BackupCursorMaxPos = window->DC.CursorMaxPos; group_data.BackupIndent = window->DC.Indent; @@ -8095,9 +8127,10 @@ void ImGui::EndGroup() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - IM_ASSERT(window->DC.GroupStack.Size > 0); // Mismatched BeginGroup()/EndGroup() calls + IM_ASSERT(g.GroupStack.Size > 0); // Mismatched BeginGroup()/EndGroup() calls - ImGuiGroupData& group_data = window->DC.GroupStack.back(); + ImGuiGroupData& group_data = g.GroupStack.back(); + IM_ASSERT(group_data.WindowID == window->ID); // EndGroup() in wrong window? ImRect group_bb(group_data.BackupCursorPos, ImMax(window->DC.CursorMaxPos, group_data.BackupCursorPos)); @@ -8112,7 +8145,7 @@ void ImGui::EndGroup() if (!group_data.EmitItem) { - window->DC.GroupStack.pop_back(); + g.GroupStack.pop_back(); return; } @@ -8141,7 +8174,7 @@ void ImGui::EndGroup() if (group_contains_prev_active_id && g.ActiveId != g.ActiveIdPreviousFrame) window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_Deactivated; - window->DC.GroupStack.pop_back(); + g.GroupStack.pop_back(); //window->DrawList->AddRect(group_bb.Min, group_bb.Max, IM_COL32(255,0,255,255)); // [Debug] } @@ -9121,6 +9154,14 @@ static bool ImGui::NavScoreItem(ImGuiNavMoveResult* result, ImRect cand) return new_best; } +static void ImGui::NavApplyItemToResult(ImGuiNavMoveResult* result, ImGuiWindow* window, ImGuiID id, const ImRect& nav_bb_rel) +{ + result->Window = window; + result->ID = id; + result->FocusScopeId = window->DC.NavFocusScopeIdCurrent; + result->RectRel = nav_bb_rel; +} + // We get there when either NavId == id, or when g.NavAnyRequest is set (which is updated by NavUpdateAnyRequestFlag above) static void ImGui::NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, const ImGuiID id) { @@ -9161,25 +9202,14 @@ static void ImGui::NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, con bool new_best = g.NavMoveRequest && NavScoreItem(result, nav_bb); #endif if (new_best) - { - result->Window = window; - result->ID = id; - result->FocusScopeId = window->DC.NavFocusScopeIdCurrent; - result->RectRel = nav_bb_rel; - } + NavApplyItemToResult(result, window, id, nav_bb_rel); // Features like PageUp/PageDown need to maintain a separate score for the visible set of items. const float VISIBLE_RATIO = 0.70f; if ((g.NavMoveRequestFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet) && window->ClipRect.Overlaps(nav_bb)) if (ImClamp(nav_bb.Max.y, window->ClipRect.Min.y, window->ClipRect.Max.y) - ImClamp(nav_bb.Min.y, window->ClipRect.Min.y, window->ClipRect.Max.y) >= (nav_bb.Max.y - nav_bb.Min.y) * VISIBLE_RATIO) if (NavScoreItem(&g.NavMoveResultLocalVisibleSet, nav_bb)) - { - result = &g.NavMoveResultLocalVisibleSet; - result->Window = window; - result->ID = id; - result->FocusScopeId = window->DC.NavFocusScopeIdCurrent; - result->RectRel = nav_bb_rel; - } + NavApplyItemToResult(&g.NavMoveResultLocalVisibleSet, window, id, nav_bb_rel); } // Update window-relative bounding box of navigated item @@ -15552,7 +15582,7 @@ static void MetricsHelpMarker(const char* desc) void ImGui::ShowMetricsWindow(bool* p_open) { - if (!Begin("Dear ImGui Metrics", p_open)) + if (!Begin("Dear ImGui Metrics/Debugger", p_open)) { End(); return; @@ -15568,20 +15598,39 @@ void ImGui::ShowMetricsWindow(bool* p_open) Text("%d vertices, %d indices (%d triangles)", io.MetricsRenderVertices, io.MetricsRenderIndices, io.MetricsRenderIndices / 3); Text("%d active windows (%d visible)", io.MetricsActiveWindows, io.MetricsRenderWindows); Text("%d active allocations", io.MetricsActiveAllocations); + //SameLine(); if (SmallButton("GC")) { g.GcCompactAll = true; } + Separator(); // Debugging enums enum { WRT_OuterRect, WRT_OuterRectClipped, WRT_InnerRect, WRT_InnerClipRect, WRT_WorkRect, WRT_Content, WRT_ContentRegionRect, WRT_Count }; // Windows Rect Type const char* wrt_rects_names[WRT_Count] = { "OuterRect", "OuterRectClipped", "InnerRect", "InnerClipRect", "WorkRect", "Content", "ContentRegionRect" }; - enum { TRT_OuterRect, TRT_WorkRect, TRT_HostClipRect, TRT_InnerClipRect, TRT_BackgroundClipRect, TRT_ColumnsRect, TRT_ColumnsClipRect, TRT_ColumnsContentHeadersUsed, TRT_ColumnsContentHeadersIdeal, TRT_ColumnsContentRowsFrozen, TRT_ColumnsContentRowsUnfrozen, TRT_Count }; // Tables Rect Type - const char* trt_rects_names[TRT_Count] = { "OuterRect", "WorkRect", "HostClipRect", "InnerClipRect", "BackgroundClipRect", "ColumnsRect", "ColumnsClipRect", "ColumnsContentHeadersUsed", "ColumnsContentHeadersIdeal", "ColumnsContentRowsFrozen", "ColumnsContentRowsUnfrozen" }; + enum { TRT_OuterRect, TRT_WorkRect, TRT_HostClipRect, TRT_InnerClipRect, TRT_BackgroundClipRect, TRT_ColumnsRect, TRT_ColumnsClipRect, TRT_ColumnsContentHeadersUsed, TRT_ColumnsContentHeadersIdeal, TRT_ColumnsContentFrozen, TRT_ColumnsContentUnfrozen, TRT_Count }; // Tables Rect Type + const char* trt_rects_names[TRT_Count] = { "OuterRect", "WorkRect", "HostClipRect", "InnerClipRect", "BackgroundClipRect", "ColumnsRect", "ColumnsClipRect", "ColumnsContentHeadersUsed", "ColumnsContentHeadersIdeal", "ColumnsContentFrozen", "ColumnsContentUnfrozen" }; if (cfg->ShowWindowsRectsType < 0) cfg->ShowWindowsRectsType = WRT_WorkRect; if (cfg->ShowTablesRectsType < 0) - cfg->ShowWindowsRectsType = TRT_WorkRect; + cfg->ShowTablesRectsType = TRT_WorkRect; struct Funcs { + static ImRect GetTableRect(ImGuiTable* table, int rect_type, int n) + { + if (rect_type == TRT_OuterRect) { return table->OuterRect; } + else if (rect_type == TRT_WorkRect) { return table->WorkRect; } + else if (rect_type == TRT_HostClipRect) { return table->HostClipRect; } + else if (rect_type == TRT_InnerClipRect) { return table->InnerClipRect; } + else if (rect_type == TRT_BackgroundClipRect) { return table->BgClipRect; } + else if (rect_type == TRT_ColumnsRect) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MaxX, table->InnerClipRect.Min.y + table->LastOuterHeight); } + else if (rect_type == TRT_ColumnsClipRect) { ImGuiTableColumn* c = &table->Columns[n]; return c->ClipRect; } + else if (rect_type == TRT_ColumnsContentHeadersUsed){ ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->WorkMinX, table->InnerClipRect.Min.y, c->ContentMaxXHeadersUsed, table->InnerClipRect.Min.y + table->LastFirstRowHeight); } // Note: y1/y2 not always accurate + else if (rect_type == TRT_ColumnsContentHeadersIdeal){ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->WorkMinX, table->InnerClipRect.Min.y, c->ContentMaxXHeadersIdeal, table->InnerClipRect.Min.y + table->LastFirstRowHeight); } + else if (rect_type == TRT_ColumnsContentFrozen) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->WorkMinX, table->InnerClipRect.Min.y, c->ContentMaxXFrozen, table->InnerClipRect.Min.y + table->LastFirstRowHeight); } + else if (rect_type == TRT_ColumnsContentUnfrozen) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->WorkMinX, table->InnerClipRect.Min.y + table->LastFirstRowHeight, c->ContentMaxXUnfrozen, table->InnerClipRect.Max.y); } + IM_ASSERT(0); + return ImRect(); + } + static ImRect GetWindowRect(ImGuiWindow* window, int rect_type) { if (rect_type == WRT_OuterRect) { return window->Rect(); } @@ -15606,7 +15655,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) MetricsHelpMarker("Will call the IM_DEBUG_BREAK() macro to break in debugger.\nWarning: If you don't have a debugger attached, this will probably crash."); Checkbox("Show windows begin order", &cfg->ShowWindowsBeginOrder); - ImGui::Checkbox("Show windows rectangles", &cfg->ShowWindowsRects); + Checkbox("Show windows rectangles", &cfg->ShowWindowsRects); SameLine(); SetNextItemWidth(GetFontSize() * 12); cfg->ShowWindowsRects |= Combo("##show_windows_rect_type", &cfg->ShowWindowsRectsType, wrt_rects_names, WRT_Count, WRT_Count); @@ -15623,13 +15672,59 @@ void ImGui::ShowMetricsWindow(bool* p_open) } Checkbox("Show ImDrawCmd mesh when hovering", &cfg->ShowDrawCmdMesh); Checkbox("Show ImDrawCmd bounding boxes when hovering", &cfg->ShowDrawCmdBoundingBoxes); + + Checkbox("Show tables rectangles", &cfg->ShowTablesRects); + SameLine(); + SetNextItemWidth(GetFontSize() * 12); + cfg->ShowTablesRects |= Combo("##show_table_rects_type", &cfg->ShowTablesRectsType, trt_rects_names, TRT_Count, TRT_Count); + if (cfg->ShowTablesRects && g.NavWindow != NULL) + { + for (int table_n = 0; table_n < g.Tables.GetSize(); table_n++) + { + ImGuiTable* table = g.Tables.GetByIndex(table_n); + if (table->LastFrameActive < g.FrameCount - 1 || (table->OuterWindow != g.NavWindow && table->InnerWindow != g.NavWindow)) + continue; + + BulletText("Table 0x%08X (%d columns, in '%s')", table->ID, table->ColumnsCount, table->OuterWindow->Name); + if (IsItemHovered()) + GetForegroundDrawList()->AddRect(table->OuterRect.Min - ImVec2(1, 1), table->OuterRect.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, ~0, 2.0f); + Indent(); + char buf[128]; + for (int rect_n = 0; rect_n < TRT_Count; rect_n++) + { + if (rect_n >= TRT_ColumnsRect) + { + if (rect_n != TRT_ColumnsRect && rect_n != TRT_ColumnsClipRect) + continue; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImRect r = Funcs::GetTableRect(table, rect_n, column_n); + ImFormatString(buf, IM_ARRAYSIZE(buf), "(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) Col %d %s", r.Min.x, r.Min.y, r.Max.x, r.Max.y, r.GetWidth(), r.GetHeight(), column_n, trt_rects_names[rect_n]); + Selectable(buf); + if (IsItemHovered()) + GetForegroundDrawList()->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, ~0, 2.0f); + } + } + else + { + ImRect r = Funcs::GetTableRect(table, rect_n, -1); + ImFormatString(buf, IM_ARRAYSIZE(buf), "(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) %s", r.Min.x, r.Min.y, r.Max.x, r.Max.y, r.GetWidth(), r.GetHeight(), trt_rects_names[rect_n]); + Selectable(buf); + if (IsItemHovered()) + GetForegroundDrawList()->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, ~0, 2.0f); + } + } + Unindent(); + } + } + TreePop(); } // Contents DebugNodeWindowsList(&g.Windows, "Windows"); //DebugNodeWindowsList(&g.WindowsFocusOrder, "WindowsFocusOrder"); - if (TreeNode("Viewport", "Viewports (%d)", g.Viewports.Size)) + if (TreeNode("Viewports", "Viewports (%d)", g.Viewports.Size)) { Indent(GetTreeNodeToLabelSpacing()); RenderViewportsThumbnails(); @@ -15674,7 +15769,6 @@ void ImGui::ShowMetricsWindow(bool* p_open) } // Details for Tables - IM_UNUSED(trt_rects_names); #ifdef IMGUI_HAS_TABLE if (TreeNode("Tables", "Tables (%d)", g.Tables.GetSize())) { @@ -15841,11 +15935,29 @@ void ImGui::ShowMetricsWindow(bool* p_open) #ifdef IMGUI_HAS_TABLE // Overlay: Display Tables Rectangles - if (show_tables_rects) + if (cfg->ShowTablesRects) { for (int table_n = 0; table_n < g.Tables.GetSize(); table_n++) { ImGuiTable* table = g.Tables.GetByIndex(table_n); + if (table->LastFrameActive < g.FrameCount - 1) + continue; + ImDrawList* draw_list = GetForegroundDrawList(table->OuterWindow); + if (cfg->ShowTablesRectsType >= TRT_ColumnsRect) + { + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImRect r = Funcs::GetTableRect(table, cfg->ShowTablesRectsType, column_n); + ImU32 col = (table->HoveredColumnBody == column_n) ? IM_COL32(255, 255, 128, 255) : IM_COL32(255, 0, 128, 255); + float thickness = (table->HoveredColumnBody == column_n) ? 3.0f : 1.0f; + draw_list->AddRect(r.Min, r.Max, col, 0.0f, ~0, thickness); + } + } + else + { + ImRect r = Funcs::GetTableRect(table, cfg->ShowTablesRectsType, -1); + draw_list->AddRect(r.Min, r.Max, IM_COL32(255, 0, 128, 255)); + } } } #endif // #ifdef IMGUI_HAS_TABLE @@ -15870,7 +15982,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) } #endif // #ifdef IMGUI_HAS_DOCK - ImGui::End(); + End(); } // [DEBUG] Display contents of Columns @@ -15916,15 +16028,15 @@ void ImGui::DebugNodeDockNode(ImGuiDockNode* node, const char* label) node->WantLockSizeOnce ? " WantLockSizeOnce" : ""); if (TreeNode("flags", "LocalFlags: 0x%04X SharedFlags: 0x%04X", node->LocalFlags, node->SharedFlags)) { - CheckboxFlags("LocalFlags: NoDocking", (ImU32*)&node->LocalFlags, ImGuiDockNodeFlags_NoDocking); - CheckboxFlags("LocalFlags: NoSplit", (ImU32*)&node->LocalFlags, ImGuiDockNodeFlags_NoSplit); - CheckboxFlags("LocalFlags: NoResize", (ImU32*)&node->LocalFlags, ImGuiDockNodeFlags_NoResize); - CheckboxFlags("LocalFlags: NoResizeX", (ImU32*)&node->LocalFlags, ImGuiDockNodeFlags_NoResizeX); - CheckboxFlags("LocalFlags: NoResizeY", (ImU32*)&node->LocalFlags, ImGuiDockNodeFlags_NoResizeY); - CheckboxFlags("LocalFlags: NoTabBar", (ImU32*)&node->LocalFlags, ImGuiDockNodeFlags_NoTabBar); - CheckboxFlags("LocalFlags: HiddenTabBar", (ImU32*)&node->LocalFlags, ImGuiDockNodeFlags_HiddenTabBar); - CheckboxFlags("LocalFlags: NoWindowMenuButton", (ImU32*)&node->LocalFlags, ImGuiDockNodeFlags_NoWindowMenuButton); - CheckboxFlags("LocalFlags: NoCloseButton", (ImU32*)&node->LocalFlags, ImGuiDockNodeFlags_NoCloseButton); + CheckboxFlags("LocalFlags: NoDocking", &node->LocalFlags, ImGuiDockNodeFlags_NoDocking); + CheckboxFlags("LocalFlags: NoSplit", &node->LocalFlags, ImGuiDockNodeFlags_NoSplit); + CheckboxFlags("LocalFlags: NoResize", &node->LocalFlags, ImGuiDockNodeFlags_NoResize); + CheckboxFlags("LocalFlags: NoResizeX", &node->LocalFlags, ImGuiDockNodeFlags_NoResizeX); + CheckboxFlags("LocalFlags: NoResizeY", &node->LocalFlags, ImGuiDockNodeFlags_NoResizeY); + CheckboxFlags("LocalFlags: NoTabBar", &node->LocalFlags, ImGuiDockNodeFlags_NoTabBar); + CheckboxFlags("LocalFlags: HiddenTabBar", &node->LocalFlags, ImGuiDockNodeFlags_HiddenTabBar); + CheckboxFlags("LocalFlags: NoWindowMenuButton", &node->LocalFlags, ImGuiDockNodeFlags_NoWindowMenuButton); + CheckboxFlags("LocalFlags: NoCloseButton", &node->LocalFlags, ImGuiDockNodeFlags_NoCloseButton); TreePop(); } if (node->ParentNode) @@ -16224,10 +16336,12 @@ void ImGui::DebugNodeWindowsList(ImVector* windows, const char* la void ImGui::ShowMetricsWindow(bool*) {} void ImGui::DebugNodeColumns(ImGuiColumns*) {} -void ImGui::DebugNodeDrawList(ImGuiWindow*, ImGuiViewportP* viewport, const ImDrawList*, const char*) {} +void ImGui::DebugNodeDrawList(ImGuiWindow*, ImGuiViewportP*, const ImDrawList*, const char*) {} void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList*, const ImDrawList*, const ImDrawCmd*, bool, bool) {} void ImGui::DebugNodeStorage(ImGuiStorage*, const char*) {} void ImGui::DebugNodeTabBar(ImGuiTabBar*, const char*) {} +void ImGui::DebugNodeTable(ImGuiTable*) {} +void ImGui::DebugNodeTableSettings(ImGuiTableSettings*) {} void ImGui::DebugNodeWindow(ImGuiWindow*, const char*) {} void ImGui::DebugNodeWindowSettings(ImGuiWindowSettings*) {} void ImGui::DebugNodeWindowsList(ImVector*, const char*) {} diff --git a/libs/ImGui/source/imgui_demo.cpp b/libs/ImGui/source/imgui_demo.cpp deleted file mode 100644 index 2775815ed..000000000 --- a/libs/ImGui/source/imgui_demo.cpp +++ /dev/null @@ -1,5867 +0,0 @@ -// dear imgui, v1.80 WIP -// (demo code) - -// Help: -// - Read FAQ at http://dearimgui.org/faq -// - Newcomers, read 'Programmer guide' in imgui.cpp for notes on how to setup Dear ImGui in your codebase. -// - Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp. All applications in examples/ are doing that. -// Read imgui.cpp for more details, documentation and comments. -// Get latest version at https://github.com/ocornut/imgui - -// Message to the person tempted to delete this file when integrating Dear ImGui into their code base: -// Do NOT remove this file from your project! Think again! It is the most useful reference code that you and other -// coders will want to refer to and call. Have the ImGui::ShowDemoWindow() function wired in an always-available -// debug menu of your game/app! Removing this file from your project is hindering access to documentation for everyone -// in your team, likely leading you to poorer usage of the library. -// Everything in this file will be stripped out by the linker if you don't call ImGui::ShowDemoWindow(). -// If you want to link core Dear ImGui in your shipped builds but want a thorough guarantee that the demo will not be -// linked, you can setup your imconfig.h with #define IMGUI_DISABLE_DEMO_WINDOWS and those functions will be empty. -// In other situation, whenever you have Dear ImGui available you probably want this to be available for reference. -// Thank you, -// -Your beloved friend, imgui_demo.cpp (which you won't delete) - -// Message to beginner C/C++ programmers about the meaning of the 'static' keyword: -// In this demo code, we frequently we use 'static' variables inside functions. A static variable persist across calls, -// so it is essentially like a global variable but declared inside the scope of the function. We do this as a way to -// gather code and data in the same place, to make the demo source code faster to read, faster to write, and smaller -// in size. It also happens to be a convenient way of storing simple UI related information as long as your function -// doesn't need to be reentrant or used in multiple threads. This might be a pattern you will want to use in your code, -// but most of the real data you would be editing is likely going to be stored outside your functions. - -// The Demo code in this file is designed to be easy to copy-and-paste in into your application! -// Because of this: -// - We never omit the ImGui:: prefix when calling functions, even though most code here is in the same namespace. -// - We try to declare static variables in the local scope, as close as possible to the code using them. -// - We never use any of the helpers/facilities used internally by Dear ImGui, unless available in the public API. -// - We never use maths operators on ImVec2/ImVec4. For our other sources files we use them, and they are provided -// by imgui_internal.h using the IMGUI_DEFINE_MATH_OPERATORS define. For your own sources file they are optional -// and require you either enable those, either provide your own via IM_VEC2_CLASS_EXTRA in imconfig.h. -// Because we can't assume anything about your support of maths operators, we cannot use them in imgui_demo.cpp. - -/* - -Index of this file: - -// [SECTION] Forward Declarations, Helpers -// [SECTION] Demo Window / ShowDemoWindow() -// [SECTION] About Window / ShowAboutWindow() -// [SECTION] Style Editor / ShowStyleEditor() -// [SECTION] Example App: Main Menu Bar / ShowExampleAppMainMenuBar() -// [SECTION] Example App: Debug Console / ShowExampleAppConsole() -// [SECTION] Example App: Debug Log / ShowExampleAppLog() -// [SECTION] Example App: Simple Layout / ShowExampleAppLayout() -// [SECTION] Example App: Property Editor / ShowExampleAppPropertyEditor() -// [SECTION] Example App: Long Text / ShowExampleAppLongText() -// [SECTION] Example App: Auto Resize / ShowExampleAppAutoResize() -// [SECTION] Example App: Constrained Resize / ShowExampleAppConstrainedResize() -// [SECTION] Example App: Simple Overlay / ShowExampleAppSimpleOverlay() -// [SECTION] Example App: Manipulating Window Titles / ShowExampleAppWindowTitles() -// [SECTION] Example App: Custom Rendering using ImDrawList API / ShowExampleAppCustomRendering() -// [SECTION] Example App: Docking, DockSpace / ShowExampleAppDockSpace() -// [SECTION] Example App: Documents Handling / ShowExampleAppDocuments() - -*/ - -#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) -#define _CRT_SECURE_NO_WARNINGS -#endif - -#include "imgui.h" -#ifndef IMGUI_DISABLE - -#include // toupper -#include // INT_MIN, INT_MAX -#include // sqrtf, powf, cosf, sinf, floorf, ceilf -#include // vsnprintf, sscanf, printf -#include // NULL, malloc, free, atoi -#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier -#include // intptr_t -#else -#include // intptr_t -#endif - -// Visual Studio warnings -#ifdef _MSC_VER -#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen -#endif - -// Clang/GCC warnings with -Weverything -#if defined(__clang__) -#if __has_warning("-Wunknown-warning-option") -#pragma clang diagnostic ignored "-Wunknown-warning-option" // warning: unknown warning group 'xxx' // not all warnings are known by all Clang versions and they tend to be rename-happy.. so ignoring warnings triggers new warnings on some configuration. Great! -#endif -#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' -#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse. -#pragma clang diagnostic ignored "-Wdeprecated-declarations" // warning: 'xx' is deprecated: The POSIX name for this.. // for strdup used in demo code (so user can copy & paste the code) -#pragma clang diagnostic ignored "-Wint-to-void-pointer-cast" // warning: cast to 'void *' from smaller integer type -#pragma clang diagnostic ignored "-Wformat-security" // warning: format string is not a string literal -#pragma clang diagnostic ignored "-Wexit-time-destructors" // warning: declaration requires an exit-time destructor // exit-time destruction order is undefined. if MemFree() leads to users code that has been disabled before exit it might cause problems. ImGui coding style welcomes static/globals. -#pragma clang diagnostic ignored "-Wunused-macros" // warning: macro is not used // we define snprintf/vsnprintf on Windows so they are available, but not always used. -#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some standard header variations use #define NULL 0 -#pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using printf() is a misery with this as C++ va_arg ellipsis changes float to double. -#pragma clang diagnostic ignored "-Wreserved-id-macro" // warning: macro name is a reserved identifier -#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision -#elif defined(__GNUC__) -#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind -#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size -#pragma GCC diagnostic ignored "-Wformat-security" // warning: format string is not a string literal (potentially insecure) -#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function -#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value -#pragma GCC diagnostic ignored "-Wmisleading-indentation" // [__GNUC__ >= 6] warning: this 'if' clause does not guard this statement // GCC 6.0+ only. See #883 on GitHub. -#endif - -// Play it nice with Windows users (Update: May 2018, Notepad now supports Unix-style carriage returns!) -#ifdef _WIN32 -#define IM_NEWLINE "\r\n" -#else -#define IM_NEWLINE "\n" -#endif - -// Helpers -#if defined(_MSC_VER) && !defined(snprintf) -#define snprintf _snprintf -#endif -#if defined(_MSC_VER) && !defined(vsnprintf) -#define vsnprintf _vsnprintf -#endif - -// Helpers macros -// We normally try to not use many helpers in imgui_demo.cpp in order to make code easier to copy and paste, -// but making an exception here as those are largely simplifying code... -// In other imgui sources we can use nicer internal functions from imgui_internal.h (ImMin/ImMax) but not in the demo. -#define IM_MIN(A, B) (((A) < (B)) ? (A) : (B)) -#define IM_MAX(A, B) (((A) >= (B)) ? (A) : (B)) -#define IM_CLAMP(V, MN, MX) ((V) < (MN) ? (MN) : (V) > (MX) ? (MX) : (V)) - -//----------------------------------------------------------------------------- -// [SECTION] Forward Declarations, Helpers -//----------------------------------------------------------------------------- - -#if !defined(IMGUI_DISABLE_DEMO_WINDOWS) - -// Forward Declarations -static void ShowExampleAppDockSpace(bool* p_open); -static void ShowExampleAppDocuments(bool* p_open); -static void ShowExampleAppMainMenuBar(); -static void ShowExampleAppConsole(bool* p_open); -static void ShowExampleAppLog(bool* p_open); -static void ShowExampleAppLayout(bool* p_open); -static void ShowExampleAppPropertyEditor(bool* p_open); -static void ShowExampleAppLongText(bool* p_open); -static void ShowExampleAppAutoResize(bool* p_open); -static void ShowExampleAppConstrainedResize(bool* p_open); -static void ShowExampleAppSimpleOverlay(bool* p_open); -static void ShowExampleAppWindowTitles(bool* p_open); -static void ShowExampleAppCustomRendering(bool* p_open); -static void ShowExampleMenuFile(); - -// Helper to display a little (?) mark which shows a tooltip when hovered. -// In your own code you may want to display an actual icon if you are using a merged icon fonts (see docs/FONTS.md) -static void HelpMarker(const char* desc) -{ - ImGui::TextDisabled("(?)"); - if (ImGui::IsItemHovered()) - { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); - ImGui::TextUnformatted(desc); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } -} - -static void ShowDockingDisabledMessage() -{ - ImGuiIO& io = ImGui::GetIO(); - ImGui::Text("ERROR: Docking is not enabled! See Demo > Configuration."); - ImGui::Text("Set io.ConfigFlags |= ImGuiConfigFlags_DockingEnable in your code, or "); - ImGui::SameLine(0.0f, 0.0f); - if (ImGui::SmallButton("click here")) - io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; -} - -// Helper to display basic user controls. -void ImGui::ShowUserGuide() -{ - ImGuiIO& io = ImGui::GetIO(); - ImGui::BulletText("Double-click on title bar to collapse window."); - ImGui::BulletText( - "Click and drag on lower corner to resize window\n" - "(double-click to auto fit window to its contents)."); - ImGui::BulletText("CTRL+Click on a slider or drag box to input value as text."); - ImGui::BulletText("TAB/SHIFT+TAB to cycle through keyboard editable fields."); - if (io.FontAllowUserScaling) - ImGui::BulletText("CTRL+Mouse Wheel to zoom window contents."); - ImGui::BulletText("While inputing text:\n"); - ImGui::Indent(); - ImGui::BulletText("CTRL+Left/Right to word jump."); - ImGui::BulletText("CTRL+A or double-click to select all."); - ImGui::BulletText("CTRL+X/C/V to use clipboard cut/copy/paste."); - ImGui::BulletText("CTRL+Z,CTRL+Y to undo/redo."); - ImGui::BulletText("ESCAPE to revert."); - ImGui::BulletText("You can apply arithmetic operators +,*,/ on numerical values.\nUse +- to subtract."); - ImGui::Unindent(); - ImGui::BulletText("With keyboard navigation enabled:"); - ImGui::Indent(); - ImGui::BulletText("Arrow keys to navigate."); - ImGui::BulletText("Space to activate a widget."); - ImGui::BulletText("Return to input text into a widget."); - ImGui::BulletText("Escape to deactivate a widget, close popup, exit child window."); - ImGui::BulletText("Alt to jump to the menu layer of a window."); - ImGui::BulletText("CTRL+Tab to select a window."); - ImGui::Unindent(); -} - -//----------------------------------------------------------------------------- -// [SECTION] Demo Window / ShowDemoWindow() -//----------------------------------------------------------------------------- -// - ShowDemoWindowWidgets() -// - ShowDemoWindowLayout() -// - ShowDemoWindowPopups() -// - ShowDemoWindowColumns() -// - ShowDemoWindowMisc() -//----------------------------------------------------------------------------- - -// We split the contents of the big ShowDemoWindow() function into smaller functions -// (because the link time of very large functions grow non-linearly) -static void ShowDemoWindowWidgets(); -static void ShowDemoWindowLayout(); -static void ShowDemoWindowPopups(); -static void ShowDemoWindowColumns(); -static void ShowDemoWindowMisc(); - -// Demonstrate most Dear ImGui features (this is big function!) -// You may execute this function to experiment with the UI and understand what it does. -// You may then search for keywords in the code when you are interested by a specific feature. -void ImGui::ShowDemoWindow(bool* p_open) -{ - // Exceptionally add an extra assert here for people confused about initial Dear ImGui setup - // Most ImGui functions would normally just crash if the context is missing. - IM_ASSERT(ImGui::GetCurrentContext() != NULL && "Missing dear imgui context. Refer to examples app!"); - - // Examples Apps (accessible from the "Examples" menu) - static bool show_app_main_menu_bar = false; - static bool show_app_dockspace = false; - static bool show_app_documents = false; - - static bool show_app_console = false; - static bool show_app_log = false; - static bool show_app_layout = false; - static bool show_app_property_editor = false; - static bool show_app_long_text = false; - static bool show_app_auto_resize = false; - static bool show_app_constrained_resize = false; - static bool show_app_simple_overlay = false; - static bool show_app_window_titles = false; - static bool show_app_custom_rendering = false; - - if (show_app_main_menu_bar) ShowExampleAppMainMenuBar(); - if (show_app_dockspace) ShowExampleAppDockSpace(&show_app_dockspace); // Process the Docking app first, as explicit DockSpace() nodes needs to be submitted early (read comments near the DockSpace function) - if (show_app_documents) ShowExampleAppDocuments(&show_app_documents); // Process the Document app next, as it may also use a DockSpace() - - if (show_app_console) ShowExampleAppConsole(&show_app_console); - if (show_app_log) ShowExampleAppLog(&show_app_log); - if (show_app_layout) ShowExampleAppLayout(&show_app_layout); - if (show_app_property_editor) ShowExampleAppPropertyEditor(&show_app_property_editor); - if (show_app_long_text) ShowExampleAppLongText(&show_app_long_text); - if (show_app_auto_resize) ShowExampleAppAutoResize(&show_app_auto_resize); - if (show_app_constrained_resize) ShowExampleAppConstrainedResize(&show_app_constrained_resize); - if (show_app_simple_overlay) ShowExampleAppSimpleOverlay(&show_app_simple_overlay); - if (show_app_window_titles) ShowExampleAppWindowTitles(&show_app_window_titles); - if (show_app_custom_rendering) ShowExampleAppCustomRendering(&show_app_custom_rendering); - - // Dear ImGui Apps (accessible from the "Tools" menu) - static bool show_app_metrics = false; - static bool show_app_style_editor = false; - static bool show_app_about = false; - - if (show_app_metrics) { ImGui::ShowMetricsWindow(&show_app_metrics); } - if (show_app_about) { ImGui::ShowAboutWindow(&show_app_about); } - if (show_app_style_editor) - { - ImGui::Begin("Dear ImGui Style Editor", &show_app_style_editor); - ImGui::ShowStyleEditor(); - ImGui::End(); - } - - // Demonstrate the various window flags. Typically you would just use the default! - static bool no_titlebar = false; - static bool no_scrollbar = false; - static bool no_menu = false; - static bool no_move = false; - static bool no_resize = false; - static bool no_collapse = false; - static bool no_close = false; - static bool no_nav = false; - static bool no_background = false; - static bool no_bring_to_front = false; - static bool no_docking = false; - - ImGuiWindowFlags window_flags = 0; - if (no_titlebar) window_flags |= ImGuiWindowFlags_NoTitleBar; - if (no_scrollbar) window_flags |= ImGuiWindowFlags_NoScrollbar; - if (!no_menu) window_flags |= ImGuiWindowFlags_MenuBar; - if (no_move) window_flags |= ImGuiWindowFlags_NoMove; - if (no_resize) window_flags |= ImGuiWindowFlags_NoResize; - if (no_collapse) window_flags |= ImGuiWindowFlags_NoCollapse; - if (no_nav) window_flags |= ImGuiWindowFlags_NoNav; - if (no_background) window_flags |= ImGuiWindowFlags_NoBackground; - if (no_bring_to_front) window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus; - if (no_docking) window_flags |= ImGuiWindowFlags_NoDocking; - if (no_close) p_open = NULL; // Don't pass our bool* to Begin - - // We specify a default position/size in case there's no data in the .ini file. - // We only do it to make the demo applications a little more welcoming, but typically this isn't required. - ImGuiViewport* main_viewport = ImGui::GetMainViewport(); - ImGui::SetNextWindowPos(ImVec2(main_viewport->GetWorkPos().x + 650, main_viewport->GetWorkPos().y + 20), ImGuiCond_FirstUseEver); - ImGui::SetNextWindowSize(ImVec2(550, 680), ImGuiCond_FirstUseEver); - - // Main body of the Demo window starts here. - if (!ImGui::Begin("Dear ImGui Demo", p_open, window_flags)) - { - // Early out if the window is collapsed, as an optimization. - ImGui::End(); - return; - } - - // Most "big" widgets share a common width settings by default. See 'Demo->Layout->Widgets Width' for details. - - // e.g. Use 2/3 of the space for widgets and 1/3 for labels (default) - //ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.65f); - - // e.g. Leave a fixed amount of width for labels (by passing a negative value), the rest goes to widgets. - ImGui::PushItemWidth(ImGui::GetFontSize() * -12); - - // Menu Bar - if (ImGui::BeginMenuBar()) - { - if (ImGui::BeginMenu("Menu")) - { - ShowExampleMenuFile(); - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("Examples")) - { - ImGui::MenuItem("Main menu bar", NULL, &show_app_main_menu_bar); - ImGui::MenuItem("Console", NULL, &show_app_console); - ImGui::MenuItem("Log", NULL, &show_app_log); - ImGui::MenuItem("Simple layout", NULL, &show_app_layout); - ImGui::MenuItem("Property editor", NULL, &show_app_property_editor); - ImGui::MenuItem("Long text display", NULL, &show_app_long_text); - ImGui::MenuItem("Auto-resizing window", NULL, &show_app_auto_resize); - ImGui::MenuItem("Constrained-resizing window", NULL, &show_app_constrained_resize); - ImGui::MenuItem("Simple overlay", NULL, &show_app_simple_overlay); - ImGui::MenuItem("Manipulating window titles", NULL, &show_app_window_titles); - ImGui::MenuItem("Custom rendering", NULL, &show_app_custom_rendering); - ImGui::MenuItem("Dockspace", NULL, &show_app_dockspace); - ImGui::MenuItem("Documents", NULL, &show_app_documents); - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("Tools")) - { - ImGui::MenuItem("Metrics", NULL, &show_app_metrics); - ImGui::MenuItem("Style Editor", NULL, &show_app_style_editor); - ImGui::MenuItem("About Dear ImGui", NULL, &show_app_about); - ImGui::EndMenu(); - } - ImGui::EndMenuBar(); - } - - ImGui::Text("dear imgui says hello. (%s)", IMGUI_VERSION); - ImGui::Spacing(); - - if (ImGui::CollapsingHeader("Help")) - { - ImGui::Text("ABOUT THIS DEMO:"); - ImGui::BulletText("Sections below are demonstrating many aspects of the library."); - ImGui::BulletText("The \"Examples\" menu above leads to more demo contents."); - ImGui::BulletText("The \"Tools\" menu above gives access to: About Box, Style Editor,\n" - "and Metrics (general purpose Dear ImGui debugging tool)."); - ImGui::Separator(); - - ImGui::Text("PROGRAMMER GUIDE:"); - ImGui::BulletText("See the ShowDemoWindow() code in imgui_demo.cpp. <- you are here!"); - ImGui::BulletText("See comments in imgui.cpp."); - ImGui::BulletText("See example applications in the examples/ folder."); - ImGui::BulletText("Read the FAQ at http://www.dearimgui.org/faq/"); - ImGui::BulletText("Set 'io.ConfigFlags |= NavEnableKeyboard' for keyboard controls."); - ImGui::BulletText("Set 'io.ConfigFlags |= NavEnableGamepad' for gamepad controls."); - ImGui::Separator(); - - ImGui::Text("USER GUIDE:"); - ImGui::ShowUserGuide(); - } - - if (ImGui::CollapsingHeader("Configuration")) - { - ImGuiIO& io = ImGui::GetIO(); - - if (ImGui::TreeNode("Configuration##2")) - { - ImGui::CheckboxFlags("io.ConfigFlags: NavEnableKeyboard", (unsigned int*)&io.ConfigFlags, ImGuiConfigFlags_NavEnableKeyboard); - ImGui::CheckboxFlags("io.ConfigFlags: NavEnableGamepad", (unsigned int*)&io.ConfigFlags, ImGuiConfigFlags_NavEnableGamepad); - ImGui::SameLine(); HelpMarker("Required backend to feed in gamepad inputs in io.NavInputs[] and set io.BackendFlags |= ImGuiBackendFlags_HasGamepad.\n\nRead instructions in imgui.cpp for details."); - ImGui::CheckboxFlags("io.ConfigFlags: NavEnableSetMousePos", (unsigned int*)&io.ConfigFlags, ImGuiConfigFlags_NavEnableSetMousePos); - ImGui::SameLine(); HelpMarker("Instruct navigation to move the mouse cursor. See comment for ImGuiConfigFlags_NavEnableSetMousePos."); - ImGui::CheckboxFlags("io.ConfigFlags: NoMouse", (unsigned int*)&io.ConfigFlags, ImGuiConfigFlags_NoMouse); - - // The "NoMouse" option above can get us stuck with a disable mouse! Provide an alternative way to fix it: - if (io.ConfigFlags & ImGuiConfigFlags_NoMouse) - { - if (fmodf((float)ImGui::GetTime(), 0.40f) < 0.20f) - { - ImGui::SameLine(); - ImGui::Text("<>"); - } - if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Space))) - io.ConfigFlags &= ~ImGuiConfigFlags_NoMouse; - } - ImGui::CheckboxFlags("io.ConfigFlags: NoMouseCursorChange", (unsigned int*)&io.ConfigFlags, ImGuiConfigFlags_NoMouseCursorChange); - ImGui::SameLine(); HelpMarker("Instruct backend to not alter mouse cursor shape and visibility."); - - ImGui::CheckboxFlags("io.ConfigFlags: DockingEnable", (unsigned int *)&io.ConfigFlags, ImGuiConfigFlags_DockingEnable); - ImGui::SameLine(); HelpMarker(io.ConfigDockingWithShift ? "[beta] Use SHIFT to dock window into each others." : "[beta] Drag from title bar to dock windows into each others."); - if (io.ConfigFlags & ImGuiConfigFlags_DockingEnable) - { - ImGui::Indent(); - ImGui::Checkbox("io.ConfigDockingNoSplit", &io.ConfigDockingNoSplit); - ImGui::SameLine(); HelpMarker("Simplified docking mode: disable window splitting, so docking is limited to merging multiple windows together into tab-bars."); - ImGui::Checkbox("io.ConfigDockingWithShift", &io.ConfigDockingWithShift); - ImGui::SameLine(); HelpMarker("Enable docking when holding Shift only (allows to drop in wider space, reduce visual noise)"); - ImGui::Checkbox("io.ConfigDockingAlwaysTabBar", &io.ConfigDockingAlwaysTabBar); - ImGui::SameLine(); HelpMarker("Create a docking node and tab-bar on single floating windows."); - ImGui::Checkbox("io.ConfigDockingTransparentPayload", &io.ConfigDockingTransparentPayload); - ImGui::SameLine(); HelpMarker("Make window or viewport transparent when docking and only display docking boxes on the target viewport. Useful if rendering of multiple viewport cannot be synced. Best used with ConfigViewportsNoAutoMerge."); - ImGui::Unindent(); - } - - ImGui::CheckboxFlags("io.ConfigFlags: ViewportsEnable", (unsigned int *)&io.ConfigFlags, ImGuiConfigFlags_ViewportsEnable); - ImGui::SameLine(); HelpMarker("[beta] Enable beta multi-viewports support. See ImGuiPlatformIO for details."); - if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) - { - ImGui::Indent(); - ImGui::Checkbox("io.ConfigViewportsNoAutoMerge", &io.ConfigViewportsNoAutoMerge); - ImGui::SameLine(); HelpMarker("Set to make all floating imgui windows always create their own viewport. Otherwise, they are merged into the main host viewports when overlapping it."); - ImGui::Checkbox("io.ConfigViewportsNoTaskBarIcon", &io.ConfigViewportsNoTaskBarIcon); - ImGui::SameLine(); HelpMarker("Toggling this at runtime is normally unsupported (most platform backends won't refresh the task bar icon state right away)."); - ImGui::Checkbox("io.ConfigViewportsNoDecoration", &io.ConfigViewportsNoDecoration); - ImGui::SameLine(); HelpMarker("Toggling this at runtime is normally unsupported (most platform backends won't refresh the decoration right away)."); - ImGui::Checkbox("io.ConfigViewportsNoDefaultParent", &io.ConfigViewportsNoDefaultParent); - ImGui::SameLine(); HelpMarker("Toggling this at runtime is normally unsupported (most platform backends won't refresh the parenting right away)."); - ImGui::Unindent(); - } - - ImGui::Checkbox("io.ConfigInputTextCursorBlink", &io.ConfigInputTextCursorBlink); - ImGui::SameLine(); HelpMarker("Set to false to disable blinking cursor, for users who consider it distracting"); - ImGui::Checkbox("io.ConfigWindowsResizeFromEdges", &io.ConfigWindowsResizeFromEdges); - ImGui::SameLine(); HelpMarker("Enable resizing of windows from their edges and from the lower-left corner.\nThis requires (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) because it needs mouse cursor feedback."); - ImGui::Checkbox("io.ConfigWindowsMoveFromTitleBarOnly", &io.ConfigWindowsMoveFromTitleBarOnly); - ImGui::Checkbox("io.MouseDrawCursor", &io.MouseDrawCursor); - ImGui::SameLine(); HelpMarker("Instruct Dear ImGui to render a mouse cursor itself. Note that a mouse cursor rendered via your application GPU rendering path will feel more laggy than hardware cursor, but will be more in sync with your other visuals.\n\nSome desktop applications may use both kinds of cursors (e.g. enable software cursor only when resizing/dragging something)."); - ImGui::Text("Also see Style->Rendering for rendering options."); - ImGui::TreePop(); - ImGui::Separator(); - } - - if (ImGui::TreeNode("Backend Flags")) - { - HelpMarker( - "Those flags are set by the backends (imgui_impl_xxx files) to specify their capabilities.\n" - "Here we expose then as read-only fields to avoid breaking interactions with your backend."); - - // Make a local copy to avoid modifying actual backend flags. - ImGuiBackendFlags backend_flags = io.BackendFlags; - ImGui::CheckboxFlags("io.BackendFlags: HasGamepad", (unsigned int*)&backend_flags, ImGuiBackendFlags_HasGamepad); - ImGui::CheckboxFlags("io.BackendFlags: HasMouseCursors", (unsigned int*)&backend_flags, ImGuiBackendFlags_HasMouseCursors); - ImGui::CheckboxFlags("io.BackendFlags: HasSetMousePos", (unsigned int*)&backend_flags, ImGuiBackendFlags_HasSetMousePos); - ImGui::CheckboxFlags("io.BackendFlags: PlatformHasViewports", (unsigned int*)&backend_flags, ImGuiBackendFlags_PlatformHasViewports); - ImGui::CheckboxFlags("io.BackendFlags: HasMouseHoveredViewport",(unsigned int*)&backend_flags, ImGuiBackendFlags_HasMouseHoveredViewport); - ImGui::CheckboxFlags("io.BackendFlags: RendererHasVtxOffset", (unsigned int*)&backend_flags, ImGuiBackendFlags_RendererHasVtxOffset); - ImGui::CheckboxFlags("io.BackendFlags: RendererHasViewports", (unsigned int*)&backend_flags, ImGuiBackendFlags_RendererHasViewports); - ImGui::TreePop(); - ImGui::Separator(); - } - - if (ImGui::TreeNode("Style")) - { - HelpMarker("The same contents can be accessed in 'Tools->Style Editor' or by calling the ShowStyleEditor() function."); - ImGui::ShowStyleEditor(); - ImGui::TreePop(); - ImGui::Separator(); - } - - if (ImGui::TreeNode("Capture/Logging")) - { - HelpMarker( - "The logging API redirects all text output so you can easily capture the content of " - "a window or a block. Tree nodes can be automatically expanded.\n" - "Try opening any of the contents below in this window and then click one of the \"Log To\" button."); - ImGui::LogButtons(); - - HelpMarker("You can also call ImGui::LogText() to output directly to the log without a visual output."); - if (ImGui::Button("Copy \"Hello, world!\" to clipboard")) - { - ImGui::LogToClipboard(); - ImGui::LogText("Hello, world!"); - ImGui::LogFinish(); - } - ImGui::TreePop(); - } - } - - if (ImGui::CollapsingHeader("Window options")) - { - ImGui::Checkbox("No titlebar", &no_titlebar); ImGui::SameLine(150); - ImGui::Checkbox("No scrollbar", &no_scrollbar); ImGui::SameLine(300); - ImGui::Checkbox("No menu", &no_menu); - ImGui::Checkbox("No move", &no_move); ImGui::SameLine(150); - ImGui::Checkbox("No resize", &no_resize); ImGui::SameLine(300); - ImGui::Checkbox("No collapse", &no_collapse); - ImGui::Checkbox("No close", &no_close); ImGui::SameLine(150); - ImGui::Checkbox("No nav", &no_nav); ImGui::SameLine(300); - ImGui::Checkbox("No background", &no_background); - ImGui::Checkbox("No bring to front", &no_bring_to_front); - ImGui::Checkbox("No docking", &no_docking); - } - - // All demo contents - ShowDemoWindowWidgets(); - ShowDemoWindowLayout(); - ShowDemoWindowPopups(); - ShowDemoWindowColumns(); - ShowDemoWindowMisc(); - - // End of ShowDemoWindow() - ImGui::End(); -} - -static void ShowDemoWindowWidgets() -{ - if (!ImGui::CollapsingHeader("Widgets")) - return; - - if (ImGui::TreeNode("Basic")) - { - static int clicked = 0; - if (ImGui::Button("Button")) - clicked++; - if (clicked & 1) - { - ImGui::SameLine(); - ImGui::Text("Thanks for clicking me!"); - } - - static bool check = true; - ImGui::Checkbox("checkbox", &check); - - static int e = 0; - ImGui::RadioButton("radio a", &e, 0); ImGui::SameLine(); - ImGui::RadioButton("radio b", &e, 1); ImGui::SameLine(); - ImGui::RadioButton("radio c", &e, 2); - - // Color buttons, demonstrate using PushID() to add unique identifier in the ID stack, and changing style. - for (int i = 0; i < 7; i++) - { - if (i > 0) - ImGui::SameLine(); - ImGui::PushID(i); - ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(i / 7.0f, 0.6f, 0.6f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(i / 7.0f, 0.7f, 0.7f)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(i / 7.0f, 0.8f, 0.8f)); - ImGui::Button("Click"); - ImGui::PopStyleColor(3); - ImGui::PopID(); - } - - // Use AlignTextToFramePadding() to align text baseline to the baseline of framed widgets elements - // (otherwise a Text+SameLine+Button sequence will have the text a little too high by default!) - // See 'Demo->Layout->Text Baseline Alignment' for details. - ImGui::AlignTextToFramePadding(); - ImGui::Text("Hold to repeat:"); - ImGui::SameLine(); - - // Arrow buttons with Repeater - static int counter = 0; - float spacing = ImGui::GetStyle().ItemInnerSpacing.x; - ImGui::PushButtonRepeat(true); - if (ImGui::ArrowButton("##left", ImGuiDir_Left)) { counter--; } - ImGui::SameLine(0.0f, spacing); - if (ImGui::ArrowButton("##right", ImGuiDir_Right)) { counter++; } - ImGui::PopButtonRepeat(); - ImGui::SameLine(); - ImGui::Text("%d", counter); - - ImGui::Text("Hover over me"); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("I am a tooltip"); - - ImGui::SameLine(); - ImGui::Text("- or me"); - if (ImGui::IsItemHovered()) - { - ImGui::BeginTooltip(); - ImGui::Text("I am a fancy tooltip"); - static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f }; - ImGui::PlotLines("Curve", arr, IM_ARRAYSIZE(arr)); - ImGui::EndTooltip(); - } - - ImGui::Separator(); - - ImGui::LabelText("label", "Value"); - - { - // Using the _simplified_ one-liner Combo() api here - // See "Combo" section for examples of how to use the more complete BeginCombo()/EndCombo() api. - const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIIIIII", "JJJJ", "KKKKKKK" }; - static int item_current = 0; - ImGui::Combo("combo", &item_current, items, IM_ARRAYSIZE(items)); - ImGui::SameLine(); HelpMarker( - "Refer to the \"Combo\" section below for an explanation of the full BeginCombo/EndCombo API, " - "and demonstration of various flags.\n"); - } - - { - // To wire InputText() with std::string or any other custom string type, - // see the "Text Input > Resize Callback" section of this demo, and the misc/cpp/imgui_stdlib.h file. - static char str0[128] = "Hello, world!"; - ImGui::InputText("input text", str0, IM_ARRAYSIZE(str0)); - ImGui::SameLine(); HelpMarker( - "USER:\n" - "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\n" - "PROGRAMMER:\n" - "You can use the ImGuiInputTextFlags_CallbackResize facility if you need to wire InputText() " - "to a dynamic string type. See misc/cpp/imgui_stdlib.h for an example (this is not demonstrated " - "in imgui_demo.cpp)."); - - static char str1[128] = ""; - ImGui::InputTextWithHint("input text (w/ hint)", "enter text here", str1, IM_ARRAYSIZE(str1)); - - static int i0 = 123; - ImGui::InputInt("input int", &i0); - ImGui::SameLine(); HelpMarker( - "You can apply arithmetic operators +,*,/ on numerical values.\n" - " e.g. [ 100 ], input \'*2\', result becomes [ 200 ]\n" - "Use +- to subtract."); - - static float f0 = 0.001f; - ImGui::InputFloat("input float", &f0, 0.01f, 1.0f, "%.3f"); - - static double d0 = 999999.00000001; - ImGui::InputDouble("input double", &d0, 0.01f, 1.0f, "%.8f"); - - static float f1 = 1.e10f; - ImGui::InputFloat("input scientific", &f1, 0.0f, 0.0f, "%e"); - ImGui::SameLine(); HelpMarker( - "You can input value using the scientific notation,\n" - " e.g. \"1e+8\" becomes \"100000000\"."); - - static float vec4a[4] = { 0.10f, 0.20f, 0.30f, 0.44f }; - ImGui::InputFloat3("input float3", vec4a); - } - - { - static int i1 = 50, i2 = 42; - ImGui::DragInt("drag int", &i1, 1); - ImGui::SameLine(); HelpMarker( - "Click and drag to edit value.\n" - "Hold SHIFT/ALT for faster/slower edit.\n" - "Double-click or CTRL+click to input value."); - - ImGui::DragInt("drag int 0..100", &i2, 1, 0, 100, "%d%%", ImGuiSliderFlags_AlwaysClamp); - - static float f1 = 1.00f, f2 = 0.0067f; - ImGui::DragFloat("drag float", &f1, 0.005f); - ImGui::DragFloat("drag small float", &f2, 0.0001f, 0.0f, 0.0f, "%.06f ns"); - } - - { - static int i1 = 0; - ImGui::SliderInt("slider int", &i1, -1, 3); - ImGui::SameLine(); HelpMarker("CTRL+click to input value."); - - static float f1 = 0.123f, f2 = 0.0f; - ImGui::SliderFloat("slider float", &f1, 0.0f, 1.0f, "ratio = %.3f"); - ImGui::SliderFloat("slider float (log)", &f2, -10.0f, 10.0f, "%.4f", ImGuiSliderFlags_Logarithmic); - - static float angle = 0.0f; - ImGui::SliderAngle("slider angle", &angle); - - // Using the format string to display a name instead of an integer. - // Here we completely omit '%d' from the format string, so it'll only display a name. - // This technique can also be used with DragInt(). - enum Element { Element_Fire, Element_Earth, Element_Air, Element_Water, Element_COUNT }; - static int elem = Element_Fire; - const char* elems_names[Element_COUNT] = { "Fire", "Earth", "Air", "Water" }; - const char* elem_name = (elem >= 0 && elem < Element_COUNT) ? elems_names[elem] : "Unknown"; - ImGui::SliderInt("slider enum", &elem, 0, Element_COUNT - 1, elem_name); - ImGui::SameLine(); HelpMarker("Using the format string parameter to display a name instead of the underlying integer."); - } - - { - 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(); HelpMarker( - "Click on the color square to open a color picker.\n" - "Click and hold to use drag and drop.\n" - "Right-click on the color square to show options.\n" - "CTRL+click on individual component to input value.\n"); - - ImGui::ColorEdit4("color 2", col2); - } - - { - // List box - const char* items[] = { "Apple", "Banana", "Cherry", "Kiwi", "Mango", "Orange", "Pineapple", "Strawberry", "Watermelon" }; - static int item_current = 1; - ImGui::ListBox("listbox\n(single select)", &item_current, items, IM_ARRAYSIZE(items), 4); - - //static int listbox_item_current2 = 2; - //ImGui::SetNextItemWidth(-1); - //ImGui::ListBox("##listbox2", &listbox_item_current2, listbox_items, IM_ARRAYSIZE(listbox_items), 4); - } - - ImGui::TreePop(); - } - - // Testing ImGuiOnceUponAFrame helper. - //static ImGuiOnceUponAFrame once; - //for (int i = 0; i < 5; i++) - // if (once) - // ImGui::Text("This will be displayed only once."); - - if (ImGui::TreeNode("Trees")) - { - if (ImGui::TreeNode("Basic trees")) - { - for (int i = 0; i < 5; i++) - { - // Use SetNextItemOpen() so set the default state of a node to be open. We could - // also use TreeNodeEx() with the ImGuiTreeNodeFlags_DefaultOpen flag to achieve the same thing! - if (i == 0) - ImGui::SetNextItemOpen(true, ImGuiCond_Once); - - if (ImGui::TreeNode((void*)(intptr_t)i, "Child %d", i)) - { - ImGui::Text("blah blah"); - ImGui::SameLine(); - if (ImGui::SmallButton("button")) {} - ImGui::TreePop(); - } - } - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Advanced, with Selectable nodes")) - { - HelpMarker( - "This is a more typical looking tree with selectable nodes.\n" - "Click to select, CTRL+Click to toggle, click on arrows or double-click to open."); - static ImGuiTreeNodeFlags base_flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_SpanAvailWidth; - static bool align_label_with_current_x_position = false; - static bool test_drag_and_drop = false; - ImGui::CheckboxFlags("ImGuiTreeNodeFlags_OpenOnArrow", (unsigned int*)&base_flags, ImGuiTreeNodeFlags_OpenOnArrow); - ImGui::CheckboxFlags("ImGuiTreeNodeFlags_OpenOnDoubleClick", (unsigned int*)&base_flags, ImGuiTreeNodeFlags_OpenOnDoubleClick); - ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanAvailWidth", (unsigned int*)&base_flags, ImGuiTreeNodeFlags_SpanAvailWidth); ImGui::SameLine(); HelpMarker("Extend hit area to all available width instead of allowing more items to be laid out after the node."); - ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanFullWidth", (unsigned int*)&base_flags, ImGuiTreeNodeFlags_SpanFullWidth); - ImGui::Checkbox("Align label with current X position", &align_label_with_current_x_position); - ImGui::Checkbox("Test tree node as drag source", &test_drag_and_drop); - ImGui::Text("Hello!"); - if (align_label_with_current_x_position) - ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing()); - - // 'selection_mask' is dumb representation of what may be user-side selection state. - // You may retain selection state inside or outside your objects in whatever format you see fit. - // 'node_clicked' is temporary storage of what node we have clicked to process selection at the end - /// of the loop. May be a pointer to your own node type, etc. - static int selection_mask = (1 << 2); - int node_clicked = -1; - for (int i = 0; i < 6; i++) - { - // Disable the default "open on single-click behavior" + set Selected flag according to our selection. - ImGuiTreeNodeFlags node_flags = base_flags; - const bool is_selected = (selection_mask & (1 << i)) != 0; - if (is_selected) - node_flags |= ImGuiTreeNodeFlags_Selected; - if (i < 3) - { - // Items 0..2 are Tree Node - bool node_open = ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Node %d", i); - if (ImGui::IsItemClicked()) - node_clicked = i; - if (test_drag_and_drop && ImGui::BeginDragDropSource()) - { - ImGui::SetDragDropPayload("_TREENODE", NULL, 0); - ImGui::Text("This is a drag and drop source"); - ImGui::EndDragDropSource(); - } - if (node_open) - { - ImGui::BulletText("Blah blah\nBlah Blah"); - ImGui::TreePop(); - } - } - else - { - // Items 3..5 are Tree Leaves - // The only reason we use TreeNode at all is to allow selection of the leaf. Otherwise we can - // use BulletText() or advance the cursor by GetTreeNodeToLabelSpacing() and call Text(). - node_flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; // ImGuiTreeNodeFlags_Bullet - ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Leaf %d", i); - if (ImGui::IsItemClicked()) - node_clicked = i; - if (test_drag_and_drop && ImGui::BeginDragDropSource()) - { - ImGui::SetDragDropPayload("_TREENODE", NULL, 0); - ImGui::Text("This is a drag and drop source"); - ImGui::EndDragDropSource(); - } - } - } - if (node_clicked != -1) - { - // Update selection state - // (process outside of tree loop to avoid visual inconsistencies during the clicking frame) - if (ImGui::GetIO().KeyCtrl) - selection_mask ^= (1 << node_clicked); // CTRL+click to toggle - else //if (!(selection_mask & (1 << node_clicked))) // Depending on selection behavior you want, may want to preserve selection when clicking on item that is part of the selection - selection_mask = (1 << node_clicked); // Click to single-select - } - if (align_label_with_current_x_position) - ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing()); - ImGui::TreePop(); - } - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Collapsing Headers")) - { - static bool closable_group = true; - ImGui::Checkbox("Show 2nd header", &closable_group); - if (ImGui::CollapsingHeader("Header", ImGuiTreeNodeFlags_None)) - { - ImGui::Text("IsItemHovered: %d", ImGui::IsItemHovered()); - for (int i = 0; i < 5; i++) - ImGui::Text("Some content %d", i); - } - if (ImGui::CollapsingHeader("Header with a close button", &closable_group)) - { - ImGui::Text("IsItemHovered: %d", ImGui::IsItemHovered()); - for (int i = 0; i < 5; i++) - ImGui::Text("More content %d", i); - } - /* - if (ImGui::CollapsingHeader("Header with a bullet", ImGuiTreeNodeFlags_Bullet)) - ImGui::Text("IsItemHovered: %d", ImGui::IsItemHovered()); - */ - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Bullets")) - { - ImGui::BulletText("Bullet point 1"); - ImGui::BulletText("Bullet point 2\nOn multiple lines"); - if (ImGui::TreeNode("Tree node")) - { - ImGui::BulletText("Another bullet point"); - ImGui::TreePop(); - } - ImGui::Bullet(); ImGui::Text("Bullet point 3 (two calls)"); - ImGui::Bullet(); ImGui::SmallButton("Button"); - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Text")) - { - if (ImGui::TreeNode("Colorful Text")) - { - // 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::SameLine(); HelpMarker("The TextDisabled color is stored in ImGuiStyle."); - 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 suitable for English and possibly other languages."); - ImGui::Spacing(); - - static float wrap_width = 200.0f; - ImGui::SliderFloat("Wrap width", &wrap_width, -20, 600, "%.0f"); - - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - for (int n = 0; n < 2; n++) - { - ImGui::Text("Test paragraph %d:", n); - ImVec2 pos = ImGui::GetCursorScreenPos(); - ImVec2 marker_min = ImVec2(pos.x + wrap_width, pos.y); - ImVec2 marker_max = ImVec2(pos.x + wrap_width + 10, pos.y + ImGui::GetTextLineHeight()); - ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width); - if (n == 0) - ImGui::Text("The lazy dog is a good dog. This paragraph should fit within %.0f pixels. Testing a 1 character word. The quick brown fox jumps over the lazy dog.", wrap_width); - else - ImGui::Text("aaaaaaaa bbbbbbbb, c cccccccc,dddddddd. d eeeeeeee ffffffff. gggggggg!hhhhhhhh"); - - // Draw actual text bounding box, following by marker of our expected limit (should not overlap!) - draw_list->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(255, 255, 0, 255)); - draw_list->AddRectFilled(marker_min, marker_max, IM_COL32(255, 0, 255, 255)); - ImGui::PopTextWrapPos(); - } - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("UTF-8 Text")) - { - // UTF-8 test with Japanese characters - // (Needs a suitable font? Try "Google Noto" or "Arial Unicode". See docs/FONTS.md for details.) - // - From C++11 you can use the u8"my text" syntax to encode literal strings as UTF-8 - // - For earlier compiler, you may be able to encode your sources as UTF-8 (e.g. in Visual Studio, you - // can save your source files as 'UTF-8 without signature'). - // - FOR THIS DEMO FILE ONLY, BECAUSE WE WANT TO SUPPORT OLD COMPILERS, WE ARE *NOT* INCLUDING RAW UTF-8 - // CHARACTERS IN THIS SOURCE FILE. Instead we are encoding a few strings with hexadecimal constants. - // Don't do this in your application! Please use u8"text in any language" in your application! - // Note that characters values are preserved even by InputText() if the font cannot be displayed, - // so you can safely copy & paste garbled characters into another application. - ImGui::TextWrapped( - "CJK text will only appears if the font was loaded with the appropriate CJK character ranges. " - "Call io.Font->AddFontFromFileTTF() manually to load extra character ranges. " - "Read docs/FONTS.md for details."); - ImGui::Text("Hiragana: \xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91\xe3\x81\x93 (kakikukeko)"); // Normally we would use u8"blah blah" with the proper characters directly in the string. - ImGui::Text("Kanjis: \xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e (nihongo)"); - static char buf[32] = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"; - //static char buf[32] = u8"NIHONGO"; // <- this is how you would write it with C++11, using real kanjis - ImGui::InputText("UTF-8 input", buf, IM_ARRAYSIZE(buf)); - ImGui::TreePop(); - } - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Images")) - { - ImGuiIO& io = ImGui::GetIO(); - ImGui::TextWrapped( - "Below we are displaying the font texture (which is the only texture we have access to in this demo). " - "Use the 'ImTextureID' type as storage to pass pointers or identifier to your own texture data. " - "Hover the texture for a zoomed view!"); - - // Below we are displaying the font texture because it is the only texture we have access to inside the demo! - // Remember that ImTextureID is just storage for whatever you want it to be. It is essentially a value that - // will be passed to the rendering backend via the ImDrawCmd structure. - // If you use one of the default imgui_impl_XXXX.cpp rendering backend, they all have comments at the top - // of their respective source file to specify what they expect to be stored in ImTextureID, for example: - // - The imgui_impl_dx11.cpp renderer expect a 'ID3D11ShaderResourceView*' pointer - // - The imgui_impl_opengl3.cpp renderer expect a GLuint OpenGL texture identifier, etc. - // More: - // - If you decided that ImTextureID = MyEngineTexture*, then you can pass your MyEngineTexture* pointers - // to ImGui::Image(), and gather width/height through your own functions, etc. - // - You can use ShowMetricsWindow() to inspect the draw data that are being passed to your renderer, - // it will help you debug issues if you are confused about it. - // - Consider using the lower-level ImDrawList::AddImage() API, via ImGui::GetWindowDrawList()->AddImage(). - // - Read https://github.com/ocornut/imgui/blob/master/docs/FAQ.md - // - Read https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples - ImTextureID my_tex_id = io.Fonts->TexID; - float my_tex_w = (float)io.Fonts->TexWidth; - float my_tex_h = (float)io.Fonts->TexHeight; - { - ImGui::Text("%.0fx%.0f", my_tex_w, my_tex_h); - ImVec2 pos = ImGui::GetCursorScreenPos(); - ImVec2 uv_min = ImVec2(0.0f, 0.0f); // Top-left - ImVec2 uv_max = ImVec2(1.0f, 1.0f); // Lower-right - ImVec4 tint_col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // No tint - ImVec4 border_col = ImVec4(1.0f, 1.0f, 1.0f, 0.5f); // 50% opaque white - ImGui::Image(my_tex_id, ImVec2(my_tex_w, my_tex_h), uv_min, uv_max, tint_col, border_col); - if (ImGui::IsItemHovered()) - { - ImGui::BeginTooltip(); - float region_sz = 32.0f; - float region_x = io.MousePos.x - pos.x - region_sz * 0.5f; - float region_y = io.MousePos.y - pos.y - region_sz * 0.5f; - float zoom = 4.0f; - if (region_x < 0.0f) { region_x = 0.0f; } - else if (region_x > my_tex_w - region_sz) { region_x = my_tex_w - region_sz; } - if (region_y < 0.0f) { region_y = 0.0f; } - else if (region_y > my_tex_h - region_sz) { region_y = my_tex_h - region_sz; } - ImGui::Text("Min: (%.2f, %.2f)", region_x, region_y); - ImGui::Text("Max: (%.2f, %.2f)", region_x + region_sz, region_y + region_sz); - ImVec2 uv0 = ImVec2((region_x) / my_tex_w, (region_y) / my_tex_h); - ImVec2 uv1 = ImVec2((region_x + region_sz) / my_tex_w, (region_y + region_sz) / my_tex_h); - ImGui::Image(my_tex_id, ImVec2(region_sz * zoom, region_sz * zoom), uv0, uv1, tint_col, border_col); - ImGui::EndTooltip(); - } - } - ImGui::TextWrapped("And now some textured buttons.."); - static int pressed_count = 0; - for (int i = 0; i < 8; i++) - { - ImGui::PushID(i); - int frame_padding = -1 + i; // -1 == uses default padding (style.FramePadding) - ImVec2 size = ImVec2(32.0f, 32.0f); // Size of the image we want to make visible - ImVec2 uv0 = ImVec2(0.0f, 0.0f); // UV coordinates for lower-left - ImVec2 uv1 = ImVec2(32.0f / my_tex_w, 32.0f / my_tex_h);// UV coordinates for (32,32) in our texture - ImVec4 bg_col = ImVec4(0.0f, 0.0f, 0.0f, 1.0f); // Black background - ImVec4 tint_col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // No tint - if (ImGui::ImageButton(my_tex_id, size, uv0, uv1, frame_padding, bg_col, tint_col)) - pressed_count += 1; - ImGui::PopID(); - ImGui::SameLine(); - } - ImGui::NewLine(); - ImGui::Text("Pressed %d times.", pressed_count); - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Combo")) - { - // Expose flags as checkbox for the demo - static ImGuiComboFlags flags = 0; - ImGui::CheckboxFlags("ImGuiComboFlags_PopupAlignLeft", (unsigned int*)&flags, ImGuiComboFlags_PopupAlignLeft); - ImGui::SameLine(); HelpMarker("Only makes a difference if the popup is larger than the combo"); - if (ImGui::CheckboxFlags("ImGuiComboFlags_NoArrowButton", (unsigned int*)&flags, ImGuiComboFlags_NoArrowButton)) - flags &= ~ImGuiComboFlags_NoPreview; // Clear the other flag, as we cannot combine both - if (ImGui::CheckboxFlags("ImGuiComboFlags_NoPreview", (unsigned int*)&flags, ImGuiComboFlags_NoPreview)) - flags &= ~ImGuiComboFlags_NoArrowButton; // Clear the other flag, as we cannot combine both - - // Using the generic BeginCombo() API, you have full control over how to display the combo contents. - // (your selection data could be an index, a pointer to the object, an id for the object, a flag intrusively - // stored in the object itself, etc.) - const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO" }; - static int item_current_idx = 0; // Here our selection data is an index. - const char* combo_label = items[item_current_idx]; // Label to preview before opening the combo (technically it could be anything) - if (ImGui::BeginCombo("combo 1", combo_label, flags)) - { - for (int n = 0; n < IM_ARRAYSIZE(items); n++) - { - const bool is_selected = (item_current_idx == n); - if (ImGui::Selectable(items[n], is_selected)) - item_current_idx = n; - - // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) - if (is_selected) - ImGui::SetItemDefaultFocus(); - } - ImGui::EndCombo(); - } - - // Simplified one-liner Combo() API, using values packed in a single constant string - static int item_current_2 = 0; - ImGui::Combo("combo 2 (one-liner)", &item_current_2, "aaaa\0bbbb\0cccc\0dddd\0eeee\0\0"); - - // Simplified one-liner Combo() using an array of const char* - static int item_current_3 = -1; // If the selection isn't within 0..count, Combo won't display a preview - ImGui::Combo("combo 3 (array)", &item_current_3, items, IM_ARRAYSIZE(items)); - - // Simplified one-liner Combo() using an accessor function - struct Funcs { static bool ItemGetter(void* data, int n, const char** out_str) { *out_str = ((const char**)data)[n]; return true; } }; - static int item_current_4 = 0; - ImGui::Combo("combo 4 (function)", &item_current_4, &Funcs::ItemGetter, items, IM_ARRAYSIZE(items)); - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Selectables")) - { - // Selectable() has 2 overloads: - // - The one taking "bool selected" as a read-only selection information. - // When Selectable() has been clicked it returns true and you can alter selection state accordingly. - // - The one taking "bool* p_selected" as a read-write selection information (convenient in some cases) - // The earlier is more flexible, as in real application your selection may be stored in many different ways - // and not necessarily inside a bool value (e.g. in flags within objects, as an external list, etc). - if (ImGui::TreeNode("Basic")) - { - static bool selection[5] = { false, true, false, false, false }; - ImGui::Selectable("1. I am selectable", &selection[0]); - ImGui::Selectable("2. I am selectable", &selection[1]); - ImGui::Text("3. I am not selectable"); - ImGui::Selectable("4. I am selectable", &selection[3]); - if (ImGui::Selectable("5. I am double clickable", selection[4], ImGuiSelectableFlags_AllowDoubleClick)) - if (ImGui::IsMouseDoubleClicked(0)) - selection[4] = !selection[4]; - ImGui::TreePop(); - } - if (ImGui::TreeNode("Selection State: Single Selection")) - { - static int selected = -1; - for (int n = 0; n < 5; n++) - { - char buf[32]; - sprintf(buf, "Object %d", n); - if (ImGui::Selectable(buf, selected == n)) - selected = n; - } - ImGui::TreePop(); - } - if (ImGui::TreeNode("Selection State: Multiple Selection")) - { - HelpMarker("Hold CTRL and click to select multiple items."); - static bool selection[5] = { false, false, false, false, false }; - for (int n = 0; n < 5; n++) - { - char buf[32]; - sprintf(buf, "Object %d", n); - if (ImGui::Selectable(buf, selection[n])) - { - if (!ImGui::GetIO().KeyCtrl) // Clear selection when CTRL is not held - memset(selection, 0, sizeof(selection)); - selection[n] ^= 1; - } - } - ImGui::TreePop(); - } - if (ImGui::TreeNode("Rendering more text into the same line")) - { - // Using the Selectable() override that takes "bool* p_selected" parameter, - // this function toggle your bool value automatically. - static bool selected[3] = { false, false, false }; - ImGui::Selectable("main.c", &selected[0]); ImGui::SameLine(300); ImGui::Text(" 2,345 bytes"); - ImGui::Selectable("Hello.cpp", &selected[1]); ImGui::SameLine(300); ImGui::Text("12,345 bytes"); - ImGui::Selectable("Hello.h", &selected[2]); ImGui::SameLine(300); ImGui::Text(" 2,345 bytes"); - ImGui::TreePop(); - } - if (ImGui::TreeNode("In columns")) - { - ImGui::Columns(3, NULL, false); - static bool selected[16] = {}; - for (int i = 0; i < 16; i++) - { - char label[32]; sprintf(label, "Item %d", i); - if (ImGui::Selectable(label, &selected[i])) {} - ImGui::NextColumn(); - } - ImGui::Columns(1); - ImGui::TreePop(); - } - if (ImGui::TreeNode("Grid")) - { - static char selected[4][4] = { { 1, 0, 0, 0 }, { 0, 1, 0, 0 }, { 0, 0, 1, 0 }, { 0, 0, 0, 1 } }; - - // Add in a bit of silly fun... - const float time = (float)ImGui::GetTime(); - const bool winning_state = memchr(selected, 0, sizeof(selected)) == NULL; // If all cells are selected... - if (winning_state) - ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.5f + 0.5f * cosf(time * 2.0f), 0.5f + 0.5f * sinf(time * 3.0f))); - - for (int y = 0; y < 4; y++) - for (int x = 0; x < 4; x++) - { - if (x > 0) - ImGui::SameLine(); - ImGui::PushID(y * 4 + x); - if (ImGui::Selectable("Sailor", selected[y][x] != 0, 0, ImVec2(50, 50))) - { - // Toggle clicked cell + toggle neighbors - selected[y][x] ^= 1; - if (x > 0) { selected[y][x - 1] ^= 1; } - if (x < 3) { selected[y][x + 1] ^= 1; } - if (y > 0) { selected[y - 1][x] ^= 1; } - if (y < 3) { selected[y + 1][x] ^= 1; } - } - ImGui::PopID(); - } - - if (winning_state) - ImGui::PopStyleVar(); - ImGui::TreePop(); - } - if (ImGui::TreeNode("Alignment")) - { - HelpMarker( - "By default, Selectables uses style.SelectableTextAlign but it can be overridden on a per-item " - "basis using PushStyleVar(). You'll probably want to always keep your default situation to " - "left-align otherwise it becomes difficult to layout multiple items on a same line"); - static bool selected[3 * 3] = { true, false, true, false, true, false, true, false, true }; - for (int y = 0; y < 3; y++) - { - for (int x = 0; x < 3; x++) - { - ImVec2 alignment = ImVec2((float)x / 2.0f, (float)y / 2.0f); - char name[32]; - sprintf(name, "(%.1f,%.1f)", alignment.x, alignment.y); - if (x > 0) ImGui::SameLine(); - ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, alignment); - ImGui::Selectable(name, &selected[3 * y + x], ImGuiSelectableFlags_None, ImVec2(80, 80)); - ImGui::PopStyleVar(); - } - } - ImGui::TreePop(); - } - ImGui::TreePop(); - } - - // To wire InputText() with std::string or any other custom string type, - // see the "Text Input > Resize Callback" section of this demo, and the misc/cpp/imgui_stdlib.h file. - if (ImGui::TreeNode("Text Input")) - { - if (ImGui::TreeNode("Multi-line Text Input")) - { - // Note: we are using a fixed-sized buffer for simplicity here. See ImGuiInputTextFlags_CallbackResize - // and the code in misc/cpp/imgui_stdlib.h for how to setup InputText() for dynamically resizing strings. - static char text[1024 * 16] = - "/*\n" - " The Pentium F00F bug, shorthand for F0 0F C7 C8,\n" - " the hexadecimal encoding of one offending instruction,\n" - " more formally, the invalid operand with locked CMPXCHG8B\n" - " instruction bug, is a design flaw in the majority of\n" - " Intel Pentium, Pentium MMX, and Pentium OverDrive\n" - " processors (all in the P5 microarchitecture).\n" - "*/\n\n" - "label:\n" - "\tlock cmpxchg8b eax\n"; - - static ImGuiInputTextFlags flags = ImGuiInputTextFlags_AllowTabInput; - HelpMarker("You can use the ImGuiInputTextFlags_CallbackResize facility if you need to wire InputTextMultiline() to a dynamic string type. See misc/cpp/imgui_stdlib.h for an example. (This is not demonstrated in imgui_demo.cpp because we don't want to include in here)"); - ImGui::CheckboxFlags("ImGuiInputTextFlags_ReadOnly", (unsigned int*)&flags, ImGuiInputTextFlags_ReadOnly); - ImGui::CheckboxFlags("ImGuiInputTextFlags_AllowTabInput", (unsigned int*)&flags, ImGuiInputTextFlags_AllowTabInput); - ImGui::CheckboxFlags("ImGuiInputTextFlags_CtrlEnterForNewLine", (unsigned int*)&flags, ImGuiInputTextFlags_CtrlEnterForNewLine); - ImGui::InputTextMultiline("##source", text, IM_ARRAYSIZE(text), ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 16), flags); - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Filtered Text Input")) - { - struct TextFilters - { - // Return 0 (pass) if the character is 'i' or 'm' or 'g' or 'u' or 'i' - static int FilterImGuiLetters(ImGuiInputTextCallbackData* data) - { - if (data->EventChar < 256 && strchr("imgui", (char)data->EventChar)) - return 0; - return 1; - } - }; - - static char buf1[64] = ""; ImGui::InputText("default", buf1, 64); - static char buf2[64] = ""; ImGui::InputText("decimal", buf2, 64, ImGuiInputTextFlags_CharsDecimal); - static char buf3[64] = ""; ImGui::InputText("hexadecimal", buf3, 64, ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase); - static char buf4[64] = ""; ImGui::InputText("uppercase", buf4, 64, ImGuiInputTextFlags_CharsUppercase); - static char buf5[64] = ""; ImGui::InputText("no blank", buf5, 64, ImGuiInputTextFlags_CharsNoBlank); - static char buf6[64] = ""; ImGui::InputText("\"imgui\" letters", buf6, 64, ImGuiInputTextFlags_CallbackCharFilter, TextFilters::FilterImGuiLetters); - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Password Input")) - { - static char password[64] = "password123"; - ImGui::InputText("password", password, IM_ARRAYSIZE(password), ImGuiInputTextFlags_Password); - ImGui::SameLine(); HelpMarker("Display all characters as '*'.\nDisable clipboard cut and copy.\nDisable logging.\n"); - ImGui::InputTextWithHint("password (w/ hint)", "", password, IM_ARRAYSIZE(password), ImGuiInputTextFlags_Password); - ImGui::InputText("password (clear)", password, IM_ARRAYSIZE(password)); - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Completion, History, Edit Callbacks")) - { - struct Funcs - { - static int MyCallback(ImGuiInputTextCallbackData* data) - { - if (data->EventFlag == ImGuiInputTextFlags_CallbackCompletion) - { - data->InsertChars(data->CursorPos, ".."); - } - else if (data->EventFlag == ImGuiInputTextFlags_CallbackHistory) - { - if (data->EventKey == ImGuiKey_UpArrow) - { - data->DeleteChars(0, data->BufTextLen); - data->InsertChars(0, "Pressed Up!"); - data->SelectAll(); - } - else if (data->EventKey == ImGuiKey_DownArrow) - { - data->DeleteChars(0, data->BufTextLen); - data->InsertChars(0, "Pressed Down!"); - data->SelectAll(); - } - } - else if (data->EventFlag == ImGuiInputTextFlags_CallbackEdit) - { - // Toggle casing of first character - char c = data->Buf[0]; - if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) data->Buf[0] ^= 32; - data->BufDirty = true; - - // Increment a counter - int* p_int = (int*)data->UserData; - *p_int = *p_int + 1; - } - return 0; - } - }; - static char buf1[64]; - ImGui::InputText("Completion", buf1, 64, ImGuiInputTextFlags_CallbackCompletion, Funcs::MyCallback); - ImGui::SameLine(); HelpMarker("Here we append \"..\" each time Tab is pressed. See 'Examples>Console' for a more meaningful demonstration of using this callback."); - - static char buf2[64]; - ImGui::InputText("History", buf2, 64, ImGuiInputTextFlags_CallbackHistory, Funcs::MyCallback); - ImGui::SameLine(); HelpMarker("Here we replace and select text each time Up/Down are pressed. See 'Examples>Console' for a more meaningful demonstration of using this callback."); - - static char buf3[64]; - static int edit_count = 0; - ImGui::InputText("Edit", buf3, 64, ImGuiInputTextFlags_CallbackEdit, Funcs::MyCallback, (void*)&edit_count); - ImGui::SameLine(); HelpMarker("Here we toggle the casing of the first character on every edits + count edits."); - ImGui::SameLine(); ImGui::Text("(%d)", edit_count); - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Resize Callback")) - { - // To wire InputText() with std::string or any other custom string type, - // you can use the ImGuiInputTextFlags_CallbackResize flag + create a custom ImGui::InputText() wrapper - // using your preferred type. See misc/cpp/imgui_stdlib.h for an implementation of this using std::string. - HelpMarker( - "Using ImGuiInputTextFlags_CallbackResize to wire your custom string type to InputText().\n\n" - "See misc/cpp/imgui_stdlib.h for an implementation of this for std::string."); - struct Funcs - { - static int MyResizeCallback(ImGuiInputTextCallbackData* data) - { - if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) - { - ImVector* my_str = (ImVector*)data->UserData; - IM_ASSERT(my_str->begin() == data->Buf); - my_str->resize(data->BufSize); // NB: On resizing calls, generally data->BufSize == data->BufTextLen + 1 - data->Buf = my_str->begin(); - } - return 0; - } - - // Note: Because ImGui:: is a namespace you would typically add your own function into the namespace. - // For example, you code may declare a function 'ImGui::InputText(const char* label, MyString* my_str)' - static bool MyInputTextMultiline(const char* label, ImVector* my_str, const ImVec2& size = ImVec2(0, 0), ImGuiInputTextFlags flags = 0) - { - IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0); - return ImGui::InputTextMultiline(label, my_str->begin(), (size_t)my_str->size(), size, flags | ImGuiInputTextFlags_CallbackResize, Funcs::MyResizeCallback, (void*)my_str); - } - }; - - // For this demo we are using ImVector as a string container. - // Note that because we need to store a terminating zero character, our size/capacity are 1 more - // than usually reported by a typical string class. - static ImVector my_str; - if (my_str.empty()) - my_str.push_back(0); - Funcs::MyInputTextMultiline("##MyStr", &my_str, ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 16)); - ImGui::Text("Data: %p\nSize: %d\nCapacity: %d", (void*)my_str.begin(), my_str.size(), my_str.capacity()); - ImGui::TreePop(); - } - - ImGui::TreePop(); - } - - // Plot/Graph widgets are currently fairly limited. - // Consider writing your own plotting widget, or using a third-party one - // (for third-party Plot widgets, see 'Wiki->Useful Widgets' or https://github.com/ocornut/imgui/labels/plot%2Fgraph) - if (ImGui::TreeNode("Plots Widgets")) - { - static bool animate = true; - ImGui::Checkbox("Animate", &animate); - - static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f }; - ImGui::PlotLines("Frame Times", arr, IM_ARRAYSIZE(arr)); - - // Fill an array of contiguous float values to plot - // Tip: If your float aren't contiguous but part of a structure, you can pass a pointer to your first float - // and the sizeof() of your structure in the "stride" parameter. - static float values[90] = {}; - static int values_offset = 0; - static double refresh_time = 0.0; - if (!animate || refresh_time == 0.0) - refresh_time = ImGui::GetTime(); - while (refresh_time < ImGui::GetTime()) // Create data at fixed 60 Hz rate for the demo - { - static float phase = 0.0f; - values[values_offset] = cosf(phase); - values_offset = (values_offset + 1) % IM_ARRAYSIZE(values); - phase += 0.10f * values_offset; - refresh_time += 1.0f / 60.0f; - } - - // Plots can display overlay texts - // (in this example, we will display an average value) - { - float average = 0.0f; - for (int n = 0; n < IM_ARRAYSIZE(values); n++) - average += values[n]; - average /= (float)IM_ARRAYSIZE(values); - char overlay[32]; - sprintf(overlay, "avg %f", average); - ImGui::PlotLines("Lines", values, IM_ARRAYSIZE(values), values_offset, overlay, -1.0f, 1.0f, ImVec2(0, 80.0f)); - } - ImGui::PlotHistogram("Histogram", arr, IM_ARRAYSIZE(arr), 0, NULL, 0.0f, 1.0f, ImVec2(0, 80.0f)); - - // Use functions to generate output - // FIXME: This is rather awkward because current plot API only pass in indices. - // We probably want an API passing floats and user provide sample rate/count. - struct Funcs - { - static float Sin(void*, int i) { return sinf(i * 0.1f); } - static float Saw(void*, int i) { return (i & 1) ? 1.0f : -1.0f; } - }; - static int func_type = 0, display_count = 70; - ImGui::Separator(); - ImGui::SetNextItemWidth(100); - ImGui::Combo("func", &func_type, "Sin\0Saw\0"); - ImGui::SameLine(); - ImGui::SliderInt("Sample count", &display_count, 1, 400); - float (*func)(void*, int) = (func_type == 0) ? Funcs::Sin : Funcs::Saw; - ImGui::PlotLines("Lines", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0, 80)); - ImGui::PlotHistogram("Histogram", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0, 80)); - ImGui::Separator(); - - // Animate a simple progress bar - static float progress = 0.0f, progress_dir = 1.0f; - if (animate) - { - progress += progress_dir * 0.4f * ImGui::GetIO().DeltaTime; - if (progress >= +1.1f) { progress = +1.1f; progress_dir *= -1.0f; } - if (progress <= -0.1f) { progress = -0.1f; progress_dir *= -1.0f; } - } - - // Typically we would use ImVec2(-1.0f,0.0f) or ImVec2(-FLT_MIN,0.0f) to use all available width, - // or ImVec2(width,0.0f) for a specified width. ImVec2(0.0f,0.0f) uses ItemWidth. - ImGui::ProgressBar(progress, ImVec2(0.0f, 0.0f)); - ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); - ImGui::Text("Progress Bar"); - - float progress_saturated = IM_CLAMP(progress, 0.0f, 1.0f); - char buf[32]; - sprintf(buf, "%d/%d", (int)(progress_saturated * 1753), 1753); - ImGui::ProgressBar(progress, ImVec2(0.f, 0.f), buf); - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Color/Picker Widgets")) - { - static ImVec4 color = ImVec4(114.0f / 255.0f, 144.0f / 255.0f, 154.0f / 255.0f, 200.0f / 255.0f); - - static bool alpha_preview = true; - static bool alpha_half_preview = false; - static bool drag_and_drop = true; - static bool options_menu = true; - static bool hdr = false; - ImGui::Checkbox("With Alpha Preview", &alpha_preview); - ImGui::Checkbox("With Half Alpha Preview", &alpha_half_preview); - ImGui::Checkbox("With Drag and Drop", &drag_and_drop); - ImGui::Checkbox("With Options Menu", &options_menu); ImGui::SameLine(); HelpMarker("Right-click on the individual color widget to show options."); - ImGui::Checkbox("With HDR", &hdr); ImGui::SameLine(); HelpMarker("Currently all this does is to lift the 0..1 limits on dragging widgets."); - ImGuiColorEditFlags misc_flags = (hdr ? ImGuiColorEditFlags_HDR : 0) | (drag_and_drop ? 0 : ImGuiColorEditFlags_NoDragDrop) | (alpha_half_preview ? ImGuiColorEditFlags_AlphaPreviewHalf : (alpha_preview ? ImGuiColorEditFlags_AlphaPreview : 0)) | (options_menu ? 0 : ImGuiColorEditFlags_NoOptions); - - ImGui::Text("Color widget:"); - ImGui::SameLine(); HelpMarker( - "Click on the color square to open a color picker.\n" - "CTRL+click on individual component to input value.\n"); - ImGui::ColorEdit3("MyColor##1", (float*)&color, misc_flags); - - ImGui::Text("Color widget HSV with Alpha:"); - ImGui::ColorEdit4("MyColor##2", (float*)&color, ImGuiColorEditFlags_DisplayHSV | misc_flags); - - ImGui::Text("Color widget with Float Display:"); - ImGui::ColorEdit4("MyColor##2f", (float*)&color, ImGuiColorEditFlags_Float | misc_flags); - - ImGui::Text("Color button with Picker:"); - ImGui::SameLine(); HelpMarker( - "With the ImGuiColorEditFlags_NoInputs flag you can hide all the slider/text inputs.\n" - "With the ImGuiColorEditFlags_NoLabel flag you can pass a non-empty label which will only " - "be used for the tooltip and picker popup."); - ImGui::ColorEdit4("MyColor##3", (float*)&color, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | misc_flags); - - ImGui::Text("Color button with Custom Picker Popup:"); - - // Generate a default palette. The palette will persist and can be edited. - static bool saved_palette_init = true; - static ImVec4 saved_palette[32] = {}; - if (saved_palette_init) - { - for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++) - { - ImGui::ColorConvertHSVtoRGB(n / 31.0f, 0.8f, 0.8f, - saved_palette[n].x, saved_palette[n].y, saved_palette[n].z); - saved_palette[n].w = 1.0f; // Alpha - } - saved_palette_init = false; - } - - static ImVec4 backup_color; - bool open_popup = ImGui::ColorButton("MyColor##3b", color, misc_flags); - ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x); - open_popup |= ImGui::Button("Palette"); - if (open_popup) - { - ImGui::OpenPopup("mypicker"); - backup_color = color; - } - if (ImGui::BeginPopup("mypicker")) - { - ImGui::Text("MY CUSTOM COLOR PICKER WITH AN AMAZING PALETTE!"); - ImGui::Separator(); - ImGui::ColorPicker4("##picker", (float*)&color, misc_flags | ImGuiColorEditFlags_NoSidePreview | ImGuiColorEditFlags_NoSmallPreview); - ImGui::SameLine(); - - ImGui::BeginGroup(); // Lock X position - ImGui::Text("Current"); - ImGui::ColorButton("##current", color, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf, ImVec2(60, 40)); - ImGui::Text("Previous"); - if (ImGui::ColorButton("##previous", backup_color, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf, ImVec2(60, 40))) - color = backup_color; - ImGui::Separator(); - ImGui::Text("Palette"); - for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++) - { - ImGui::PushID(n); - if ((n % 8) != 0) - ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y); - - ImGuiColorEditFlags palette_button_flags = ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_NoTooltip; - if (ImGui::ColorButton("##palette", saved_palette[n], palette_button_flags, ImVec2(20, 20))) - color = ImVec4(saved_palette[n].x, saved_palette[n].y, saved_palette[n].z, color.w); // Preserve alpha! - - // Allow user to drop colors into each palette entry. Note that ColorButton() is already a - // drag source by default, unless specifying the ImGuiColorEditFlags_NoDragDrop flag. - if (ImGui::BeginDragDropTarget()) - { - if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) - memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 3); - if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F)) - memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 4); - ImGui::EndDragDropTarget(); - } - - ImGui::PopID(); - } - ImGui::EndGroup(); - ImGui::EndPopup(); - } - - ImGui::Text("Color button only:"); - static bool no_border = false; - ImGui::Checkbox("ImGuiColorEditFlags_NoBorder", &no_border); - ImGui::ColorButton("MyColor##3c", *(ImVec4*)&color, misc_flags | (no_border ? ImGuiColorEditFlags_NoBorder : 0), ImVec2(80, 80)); - - ImGui::Text("Color picker:"); - static bool alpha = true; - static bool alpha_bar = true; - static bool side_preview = true; - static bool ref_color = false; - static ImVec4 ref_color_v(1.0f, 0.0f, 1.0f, 0.5f); - static int display_mode = 0; - static int picker_mode = 0; - ImGui::Checkbox("With Alpha", &alpha); - ImGui::Checkbox("With Alpha Bar", &alpha_bar); - ImGui::Checkbox("With Side Preview", &side_preview); - if (side_preview) - { - ImGui::SameLine(); - ImGui::Checkbox("With Ref Color", &ref_color); - if (ref_color) - { - ImGui::SameLine(); - ImGui::ColorEdit4("##RefColor", &ref_color_v.x, ImGuiColorEditFlags_NoInputs | misc_flags); - } - } - ImGui::Combo("Display Mode", &display_mode, "Auto/Current\0None\0RGB Only\0HSV Only\0Hex Only\0"); - ImGui::SameLine(); HelpMarker( - "ColorEdit defaults to displaying RGB inputs if you don't specify a display mode, " - "but the user can change it with a right-click.\n\nColorPicker defaults to displaying RGB+HSV+Hex " - "if you don't specify a display mode.\n\nYou can change the defaults using SetColorEditOptions()."); - ImGui::Combo("Picker Mode", &picker_mode, "Auto/Current\0Hue bar + SV rect\0Hue wheel + SV triangle\0"); - ImGui::SameLine(); HelpMarker("User can right-click the picker to change mode."); - ImGuiColorEditFlags flags = misc_flags; - if (!alpha) flags |= ImGuiColorEditFlags_NoAlpha; // This is by default if you call ColorPicker3() instead of ColorPicker4() - if (alpha_bar) flags |= ImGuiColorEditFlags_AlphaBar; - if (!side_preview) flags |= ImGuiColorEditFlags_NoSidePreview; - if (picker_mode == 1) flags |= ImGuiColorEditFlags_PickerHueBar; - if (picker_mode == 2) flags |= ImGuiColorEditFlags_PickerHueWheel; - if (display_mode == 1) flags |= ImGuiColorEditFlags_NoInputs; // Disable all RGB/HSV/Hex displays - if (display_mode == 2) flags |= ImGuiColorEditFlags_DisplayRGB; // Override display mode - if (display_mode == 3) flags |= ImGuiColorEditFlags_DisplayHSV; - if (display_mode == 4) flags |= ImGuiColorEditFlags_DisplayHex; - ImGui::ColorPicker4("MyColor##4", (float*)&color, flags, ref_color ? &ref_color_v.x : NULL); - - ImGui::Text("Set defaults in code:"); - ImGui::SameLine(); HelpMarker( - "SetColorEditOptions() is designed to allow you to set boot-time default.\n" - "We don't have Push/Pop functions because you can force options on a per-widget basis if needed," - "and the user can change non-forced ones with the options menu.\nWe don't have a getter to avoid" - "encouraging you to persistently save values that aren't forward-compatible."); - if (ImGui::Button("Default: Uint8 + HSV + Hue Bar")) - ImGui::SetColorEditOptions(ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_DisplayHSV | ImGuiColorEditFlags_PickerHueBar); - if (ImGui::Button("Default: Float + HDR + Hue Wheel")) - ImGui::SetColorEditOptions(ImGuiColorEditFlags_Float | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_PickerHueWheel); - - // HSV encoded support (to avoid RGB<>HSV round trips and singularities when S==0 or V==0) - static ImVec4 color_hsv(0.23f, 1.0f, 1.0f, 1.0f); // Stored as HSV! - ImGui::Spacing(); - ImGui::Text("HSV encoded colors"); - ImGui::SameLine(); HelpMarker( - "By default, colors are given to ColorEdit and ColorPicker in RGB, but ImGuiColorEditFlags_InputHSV" - "allows you to store colors as HSV and pass them to ColorEdit and ColorPicker as HSV. This comes with the" - "added benefit that you can manipulate hue values with the picker even when saturation or value are zero."); - ImGui::Text("Color widget with InputHSV:"); - ImGui::ColorEdit4("HSV shown as RGB##1", (float*)&color_hsv, ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_InputHSV | ImGuiColorEditFlags_Float); - ImGui::ColorEdit4("HSV shown as HSV##1", (float*)&color_hsv, ImGuiColorEditFlags_DisplayHSV | ImGuiColorEditFlags_InputHSV | ImGuiColorEditFlags_Float); - ImGui::DragFloat4("Raw HSV values", (float*)&color_hsv, 0.01f, 0.0f, 1.0f); - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Drag/Slider Flags")) - { - // Demonstrate using advanced flags for DragXXX and SliderXXX functions. Note that the flags are the same! - static ImGuiSliderFlags flags = ImGuiSliderFlags_None; - ImGui::CheckboxFlags("ImGuiSliderFlags_AlwaysClamp", (unsigned int*)&flags, ImGuiSliderFlags_AlwaysClamp); - ImGui::SameLine(); HelpMarker("Always clamp value to min/max bounds (if any) when input manually with CTRL+Click."); - ImGui::CheckboxFlags("ImGuiSliderFlags_Logarithmic", (unsigned int*)&flags, ImGuiSliderFlags_Logarithmic); - ImGui::SameLine(); HelpMarker("Enable logarithmic editing (more precision for small values)."); - ImGui::CheckboxFlags("ImGuiSliderFlags_NoRoundToFormat", (unsigned int*)&flags, ImGuiSliderFlags_NoRoundToFormat); - ImGui::SameLine(); HelpMarker("Disable rounding underlying value to match precision of the format string (e.g. %.3f values are rounded to those 3 digits)."); - ImGui::CheckboxFlags("ImGuiSliderFlags_NoInput", (unsigned int*)&flags, ImGuiSliderFlags_NoInput); - ImGui::SameLine(); HelpMarker("Disable CTRL+Click or Enter key allowing to input text directly into the widget."); - - // Drags - static float drag_f = 0.5f; - static int drag_i = 50; - ImGui::Text("Underlying float value: %f", drag_f); - ImGui::DragFloat("DragFloat (0 -> 1)", &drag_f, 0.005f, 0.0f, 1.0f, "%.3f", flags); - ImGui::DragFloat("DragFloat (0 -> +inf)", &drag_f, 0.005f, 0.0f, FLT_MAX, "%.3f", flags); - ImGui::DragFloat("DragFloat (-inf -> 1)", &drag_f, 0.005f, -FLT_MAX, 1.0f, "%.3f", flags); - ImGui::DragFloat("DragFloat (-inf -> +inf)", &drag_f, 0.005f, -FLT_MAX, +FLT_MAX, "%.3f", flags); - ImGui::DragInt("DragInt (0 -> 100)", &drag_i, 0.5f, 0, 100, "%d", flags); - - // Sliders - static float slider_f = 0.5f; - static int slider_i = 50; - ImGui::Text("Underlying float value: %f", slider_f); - ImGui::SliderFloat("SliderFloat (0 -> 1)", &slider_f, 0.0f, 1.0f, "%.3f", flags); - ImGui::SliderInt("SliderInt (0 -> 100)", &slider_i, 0, 100, "%d", flags); - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Range Widgets")) - { - static float begin = 10, end = 90; - static int begin_i = 100, end_i = 1000; - ImGui::DragFloatRange2("range float", &begin, &end, 0.25f, 0.0f, 100.0f, "Min: %.1f %%", "Max: %.1f %%", ImGuiSliderFlags_AlwaysClamp); - ImGui::DragIntRange2("range int", &begin_i, &end_i, 5, 0, 1000, "Min: %d units", "Max: %d units"); - ImGui::DragIntRange2("range int (no bounds)", &begin_i, &end_i, 5, 0, 0, "Min: %d units", "Max: %d units"); - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Data Types")) - { - // DragScalar/InputScalar/SliderScalar functions allow various data types - // - signed/unsigned - // - 8/16/32/64-bits - // - integer/float/double - // To avoid polluting the public API with all possible combinations, we use the ImGuiDataType enum - // to pass the type, and passing all arguments by pointer. - // This is the reason the test code below creates local variables to hold "zero" "one" etc. for each types. - // In practice, if you frequently use a given type that is not covered by the normal API entry points, - // you can wrap it yourself inside a 1 line function which can take typed argument as value instead of void*, - // and then pass their address to the generic function. For example: - // bool MySliderU64(const char *label, u64* value, u64 min = 0, u64 max = 0, const char* format = "%lld") - // { - // return SliderScalar(label, ImGuiDataType_U64, value, &min, &max, format); - // } - - // Setup limits (as helper variables so we can take their address, as explained above) - // Note: SliderScalar() functions have a maximum usable range of half the natural type maximum, hence the /2. - #ifndef LLONG_MIN - ImS64 LLONG_MIN = -9223372036854775807LL - 1; - ImS64 LLONG_MAX = 9223372036854775807LL; - ImU64 ULLONG_MAX = (2ULL * 9223372036854775807LL + 1); - #endif - const char s8_zero = 0, s8_one = 1, s8_fifty = 50, s8_min = -128, s8_max = 127; - const ImU8 u8_zero = 0, u8_one = 1, u8_fifty = 50, u8_min = 0, u8_max = 255; - const short s16_zero = 0, s16_one = 1, s16_fifty = 50, s16_min = -32768, s16_max = 32767; - const ImU16 u16_zero = 0, u16_one = 1, u16_fifty = 50, u16_min = 0, u16_max = 65535; - const ImS32 s32_zero = 0, s32_one = 1, s32_fifty = 50, s32_min = INT_MIN/2, s32_max = INT_MAX/2, s32_hi_a = INT_MAX/2 - 100, s32_hi_b = INT_MAX/2; - const ImU32 u32_zero = 0, u32_one = 1, u32_fifty = 50, u32_min = 0, u32_max = UINT_MAX/2, u32_hi_a = UINT_MAX/2 - 100, u32_hi_b = UINT_MAX/2; - const ImS64 s64_zero = 0, s64_one = 1, s64_fifty = 50, s64_min = LLONG_MIN/2, s64_max = LLONG_MAX/2, s64_hi_a = LLONG_MAX/2 - 100, s64_hi_b = LLONG_MAX/2; - const ImU64 u64_zero = 0, u64_one = 1, u64_fifty = 50, u64_min = 0, u64_max = ULLONG_MAX/2, u64_hi_a = ULLONG_MAX/2 - 100, u64_hi_b = ULLONG_MAX/2; - const float f32_zero = 0.f, f32_one = 1.f, f32_lo_a = -10000000000.0f, f32_hi_a = +10000000000.0f; - const double f64_zero = 0., f64_one = 1., f64_lo_a = -1000000000000000.0, f64_hi_a = +1000000000000000.0; - - // State - static char s8_v = 127; - static ImU8 u8_v = 255; - static short s16_v = 32767; - static ImU16 u16_v = 65535; - static ImS32 s32_v = -1; - static ImU32 u32_v = (ImU32)-1; - static ImS64 s64_v = -1; - static ImU64 u64_v = (ImU64)-1; - static float f32_v = 0.123f; - static double f64_v = 90000.01234567890123456789; - - const float drag_speed = 0.2f; - static bool drag_clamp = false; - ImGui::Text("Drags:"); - ImGui::Checkbox("Clamp integers to 0..50", &drag_clamp); - ImGui::SameLine(); HelpMarker( - "As with every widgets in dear imgui, we never modify values unless there is a user interaction.\n" - "You can override the clamping limits by using CTRL+Click to input a value."); - ImGui::DragScalar("drag s8", ImGuiDataType_S8, &s8_v, drag_speed, drag_clamp ? &s8_zero : NULL, drag_clamp ? &s8_fifty : NULL); - ImGui::DragScalar("drag u8", ImGuiDataType_U8, &u8_v, drag_speed, drag_clamp ? &u8_zero : NULL, drag_clamp ? &u8_fifty : NULL, "%u ms"); - ImGui::DragScalar("drag s16", ImGuiDataType_S16, &s16_v, drag_speed, drag_clamp ? &s16_zero : NULL, drag_clamp ? &s16_fifty : NULL); - ImGui::DragScalar("drag u16", ImGuiDataType_U16, &u16_v, drag_speed, drag_clamp ? &u16_zero : NULL, drag_clamp ? &u16_fifty : NULL, "%u ms"); - ImGui::DragScalar("drag s32", ImGuiDataType_S32, &s32_v, drag_speed, drag_clamp ? &s32_zero : NULL, drag_clamp ? &s32_fifty : NULL); - ImGui::DragScalar("drag u32", ImGuiDataType_U32, &u32_v, drag_speed, drag_clamp ? &u32_zero : NULL, drag_clamp ? &u32_fifty : NULL, "%u ms"); - ImGui::DragScalar("drag s64", ImGuiDataType_S64, &s64_v, drag_speed, drag_clamp ? &s64_zero : NULL, drag_clamp ? &s64_fifty : NULL); - ImGui::DragScalar("drag u64", ImGuiDataType_U64, &u64_v, drag_speed, drag_clamp ? &u64_zero : NULL, drag_clamp ? &u64_fifty : NULL); - ImGui::DragScalar("drag float", ImGuiDataType_Float, &f32_v, 0.005f, &f32_zero, &f32_one, "%f"); - ImGui::DragScalar("drag float log", ImGuiDataType_Float, &f32_v, 0.005f, &f32_zero, &f32_one, "%f", ImGuiSliderFlags_Logarithmic); - ImGui::DragScalar("drag double", ImGuiDataType_Double, &f64_v, 0.0005f, &f64_zero, NULL, "%.10f grams"); - ImGui::DragScalar("drag double log",ImGuiDataType_Double, &f64_v, 0.0005f, &f64_zero, &f64_one, "0 < %.10f < 1", ImGuiSliderFlags_Logarithmic); - - ImGui::Text("Sliders"); - ImGui::SliderScalar("slider s8 full", ImGuiDataType_S8, &s8_v, &s8_min, &s8_max, "%d"); - ImGui::SliderScalar("slider u8 full", ImGuiDataType_U8, &u8_v, &u8_min, &u8_max, "%u"); - ImGui::SliderScalar("slider s16 full", ImGuiDataType_S16, &s16_v, &s16_min, &s16_max, "%d"); - ImGui::SliderScalar("slider u16 full", ImGuiDataType_U16, &u16_v, &u16_min, &u16_max, "%u"); - ImGui::SliderScalar("slider s32 low", ImGuiDataType_S32, &s32_v, &s32_zero, &s32_fifty,"%d"); - ImGui::SliderScalar("slider s32 high", ImGuiDataType_S32, &s32_v, &s32_hi_a, &s32_hi_b, "%d"); - ImGui::SliderScalar("slider s32 full", ImGuiDataType_S32, &s32_v, &s32_min, &s32_max, "%d"); - ImGui::SliderScalar("slider u32 low", ImGuiDataType_U32, &u32_v, &u32_zero, &u32_fifty,"%u"); - ImGui::SliderScalar("slider u32 high", ImGuiDataType_U32, &u32_v, &u32_hi_a, &u32_hi_b, "%u"); - ImGui::SliderScalar("slider u32 full", ImGuiDataType_U32, &u32_v, &u32_min, &u32_max, "%u"); - ImGui::SliderScalar("slider s64 low", ImGuiDataType_S64, &s64_v, &s64_zero, &s64_fifty,"%I64d"); - ImGui::SliderScalar("slider s64 high", ImGuiDataType_S64, &s64_v, &s64_hi_a, &s64_hi_b, "%I64d"); - ImGui::SliderScalar("slider s64 full", ImGuiDataType_S64, &s64_v, &s64_min, &s64_max, "%I64d"); - ImGui::SliderScalar("slider u64 low", ImGuiDataType_U64, &u64_v, &u64_zero, &u64_fifty,"%I64u ms"); - ImGui::SliderScalar("slider u64 high", ImGuiDataType_U64, &u64_v, &u64_hi_a, &u64_hi_b, "%I64u ms"); - ImGui::SliderScalar("slider u64 full", ImGuiDataType_U64, &u64_v, &u64_min, &u64_max, "%I64u ms"); - ImGui::SliderScalar("slider float low", ImGuiDataType_Float, &f32_v, &f32_zero, &f32_one); - ImGui::SliderScalar("slider float low log", ImGuiDataType_Float, &f32_v, &f32_zero, &f32_one, "%.10f", ImGuiSliderFlags_Logarithmic); - ImGui::SliderScalar("slider float high", ImGuiDataType_Float, &f32_v, &f32_lo_a, &f32_hi_a, "%e"); - ImGui::SliderScalar("slider double low", ImGuiDataType_Double, &f64_v, &f64_zero, &f64_one, "%.10f grams"); - ImGui::SliderScalar("slider double low log",ImGuiDataType_Double, &f64_v, &f64_zero, &f64_one, "%.10f", ImGuiSliderFlags_Logarithmic); - ImGui::SliderScalar("slider double high", ImGuiDataType_Double, &f64_v, &f64_lo_a, &f64_hi_a, "%e grams"); - - ImGui::Text("Sliders (reverse)"); - ImGui::SliderScalar("slider s8 reverse", ImGuiDataType_S8, &s8_v, &s8_max, &s8_min, "%d"); - ImGui::SliderScalar("slider u8 reverse", ImGuiDataType_U8, &u8_v, &u8_max, &u8_min, "%u"); - ImGui::SliderScalar("slider s32 reverse", ImGuiDataType_S32, &s32_v, &s32_fifty, &s32_zero, "%d"); - ImGui::SliderScalar("slider u32 reverse", ImGuiDataType_U32, &u32_v, &u32_fifty, &u32_zero, "%u"); - ImGui::SliderScalar("slider s64 reverse", ImGuiDataType_S64, &s64_v, &s64_fifty, &s64_zero, "%I64d"); - ImGui::SliderScalar("slider u64 reverse", ImGuiDataType_U64, &u64_v, &u64_fifty, &u64_zero, "%I64u ms"); - - static bool inputs_step = true; - ImGui::Text("Inputs"); - ImGui::Checkbox("Show step buttons", &inputs_step); - ImGui::InputScalar("input s8", ImGuiDataType_S8, &s8_v, inputs_step ? &s8_one : NULL, NULL, "%d"); - ImGui::InputScalar("input u8", ImGuiDataType_U8, &u8_v, inputs_step ? &u8_one : NULL, NULL, "%u"); - ImGui::InputScalar("input s16", ImGuiDataType_S16, &s16_v, inputs_step ? &s16_one : NULL, NULL, "%d"); - ImGui::InputScalar("input u16", ImGuiDataType_U16, &u16_v, inputs_step ? &u16_one : NULL, NULL, "%u"); - ImGui::InputScalar("input s32", ImGuiDataType_S32, &s32_v, inputs_step ? &s32_one : NULL, NULL, "%d"); - ImGui::InputScalar("input s32 hex", ImGuiDataType_S32, &s32_v, inputs_step ? &s32_one : NULL, NULL, "%08X", ImGuiInputTextFlags_CharsHexadecimal); - ImGui::InputScalar("input u32", ImGuiDataType_U32, &u32_v, inputs_step ? &u32_one : NULL, NULL, "%u"); - ImGui::InputScalar("input u32 hex", ImGuiDataType_U32, &u32_v, inputs_step ? &u32_one : NULL, NULL, "%08X", ImGuiInputTextFlags_CharsHexadecimal); - ImGui::InputScalar("input s64", ImGuiDataType_S64, &s64_v, inputs_step ? &s64_one : NULL); - ImGui::InputScalar("input u64", ImGuiDataType_U64, &u64_v, inputs_step ? &u64_one : NULL); - ImGui::InputScalar("input float", ImGuiDataType_Float, &f32_v, inputs_step ? &f32_one : NULL); - ImGui::InputScalar("input double", ImGuiDataType_Double, &f64_v, inputs_step ? &f64_one : NULL); - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Multi-component Widgets")) - { - static float vec4f[4] = { 0.10f, 0.20f, 0.30f, 0.44f }; - static int vec4i[4] = { 1, 5, 100, 255 }; - - ImGui::InputFloat2("input float2", vec4f); - ImGui::DragFloat2("drag float2", vec4f, 0.01f, 0.0f, 1.0f); - ImGui::SliderFloat2("slider float2", vec4f, 0.0f, 1.0f); - ImGui::InputInt2("input int2", vec4i); - ImGui::DragInt2("drag int2", vec4i, 1, 0, 255); - 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); - ImGui::SliderFloat3("slider float3", vec4f, 0.0f, 1.0f); - ImGui::InputInt3("input int3", vec4i); - ImGui::DragInt3("drag int3", vec4i, 1, 0, 255); - 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); - ImGui::SliderFloat4("slider float4", vec4f, 0.0f, 1.0f); - ImGui::InputInt4("input int4", vec4i); - ImGui::DragInt4("drag int4", vec4i, 1, 0, 255); - ImGui::SliderInt4("slider int4", vec4i, 0, 255); - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Vertical Sliders")) - { - const float spacing = 4; - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)); - - static int int_value = 0; - ImGui::VSliderInt("##int", ImVec2(18, 160), &int_value, 0, 5); - ImGui::SameLine(); - - static float values[7] = { 0.0f, 0.60f, 0.35f, 0.9f, 0.70f, 0.20f, 0.0f }; - ImGui::PushID("set1"); - for (int i = 0; i < 7; i++) - { - if (i > 0) ImGui::SameLine(); - ImGui::PushID(i); - ImGui::PushStyleColor(ImGuiCol_FrameBg, (ImVec4)ImColor::HSV(i / 7.0f, 0.5f, 0.5f)); - ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, (ImVec4)ImColor::HSV(i / 7.0f, 0.6f, 0.5f)); - ImGui::PushStyleColor(ImGuiCol_FrameBgActive, (ImVec4)ImColor::HSV(i / 7.0f, 0.7f, 0.5f)); - ImGui::PushStyleColor(ImGuiCol_SliderGrab, (ImVec4)ImColor::HSV(i / 7.0f, 0.9f, 0.9f)); - ImGui::VSliderFloat("##v", ImVec2(18, 160), &values[i], 0.0f, 1.0f, ""); - if (ImGui::IsItemActive() || ImGui::IsItemHovered()) - ImGui::SetTooltip("%.3f", values[i]); - ImGui::PopStyleColor(4); - ImGui::PopID(); - } - ImGui::PopID(); - - ImGui::SameLine(); - ImGui::PushID("set2"); - static float values2[4] = { 0.20f, 0.80f, 0.40f, 0.25f }; - const int rows = 3; - const ImVec2 small_slider_size(18, (float)(int)((160.0f - (rows - 1) * spacing) / rows)); - for (int nx = 0; nx < 4; nx++) - { - if (nx > 0) ImGui::SameLine(); - ImGui::BeginGroup(); - for (int ny = 0; ny < rows; ny++) - { - ImGui::PushID(nx * rows + ny); - ImGui::VSliderFloat("##v", small_slider_size, &values2[nx], 0.0f, 1.0f, ""); - if (ImGui::IsItemActive() || ImGui::IsItemHovered()) - ImGui::SetTooltip("%.3f", values2[nx]); - ImGui::PopID(); - } - ImGui::EndGroup(); - } - ImGui::PopID(); - - ImGui::SameLine(); - ImGui::PushID("set3"); - for (int i = 0; i < 4; i++) - { - if (i > 0) ImGui::SameLine(); - ImGui::PushID(i); - ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, 40); - ImGui::VSliderFloat("##v", ImVec2(40, 160), &values[i], 0.0f, 1.0f, "%.2f\nsec"); - ImGui::PopStyleVar(); - ImGui::PopID(); - } - ImGui::PopID(); - ImGui::PopStyleVar(); - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Drag and Drop")) - { - if (ImGui::TreeNode("Drag and drop in standard widgets")) - { - // ColorEdit widgets automatically act as drag source and drag target. - // They are using standardized payload strings IMGUI_PAYLOAD_TYPE_COLOR_3F and IMGUI_PAYLOAD_TYPE_COLOR_4F - // to allow your own widgets to use colors in their drag and drop interaction. - // Also see 'Demo->Widgets->Color/Picker Widgets->Palette' demo. - HelpMarker("You can drag from the color squares."); - 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::ColorEdit4("color 2", col2); - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Drag and drop to copy/swap items")) - { - enum Mode - { - Mode_Copy, - Mode_Move, - Mode_Swap - }; - static int mode = 0; - if (ImGui::RadioButton("Copy", mode == Mode_Copy)) { mode = Mode_Copy; } ImGui::SameLine(); - if (ImGui::RadioButton("Move", mode == Mode_Move)) { mode = Mode_Move; } ImGui::SameLine(); - if (ImGui::RadioButton("Swap", mode == Mode_Swap)) { mode = Mode_Swap; } - static const char* names[9] = - { - "Bobby", "Beatrice", "Betty", - "Brianna", "Barry", "Bernard", - "Bibi", "Blaine", "Bryn" - }; - for (int n = 0; n < IM_ARRAYSIZE(names); n++) - { - ImGui::PushID(n); - if ((n % 3) != 0) - ImGui::SameLine(); - ImGui::Button(names[n], ImVec2(60, 60)); - - // Our buttons are both drag sources and drag targets here! - if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) - { - // Set payload to carry the index of our item (could be anything) - ImGui::SetDragDropPayload("DND_DEMO_CELL", &n, sizeof(int)); - - // Display preview (could be anything, e.g. when dragging an image we could decide to display - // the filename and a small preview of the image, etc.) - if (mode == Mode_Copy) { ImGui::Text("Copy %s", names[n]); } - if (mode == Mode_Move) { ImGui::Text("Move %s", names[n]); } - if (mode == Mode_Swap) { ImGui::Text("Swap %s", names[n]); } - ImGui::EndDragDropSource(); - } - if (ImGui::BeginDragDropTarget()) - { - if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("DND_DEMO_CELL")) - { - IM_ASSERT(payload->DataSize == sizeof(int)); - int payload_n = *(const int*)payload->Data; - if (mode == Mode_Copy) - { - names[n] = names[payload_n]; - } - if (mode == Mode_Move) - { - names[n] = names[payload_n]; - names[payload_n] = ""; - } - if (mode == Mode_Swap) - { - const char* tmp = names[n]; - names[n] = names[payload_n]; - names[payload_n] = tmp; - } - } - ImGui::EndDragDropTarget(); - } - ImGui::PopID(); - } - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Drag to reorder items (simple)")) - { - // Simple reordering - HelpMarker( - "We don't use the drag and drop api at all here! " - "Instead we query when the item is held but not hovered, and order items accordingly."); - static const char* item_names[] = { "Item One", "Item Two", "Item Three", "Item Four", "Item Five" }; - for (int n = 0; n < IM_ARRAYSIZE(item_names); n++) - { - const char* item = item_names[n]; - ImGui::Selectable(item); - - if (ImGui::IsItemActive() && !ImGui::IsItemHovered()) - { - int n_next = n + (ImGui::GetMouseDragDelta(0).y < 0.f ? -1 : 1); - if (n_next >= 0 && n_next < IM_ARRAYSIZE(item_names)) - { - item_names[n] = item_names[n_next]; - item_names[n_next] = item; - ImGui::ResetMouseDragDelta(); - } - } - } - ImGui::TreePop(); - } - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Querying Status (Active/Focused/Hovered etc.)")) - { - // Select an item type - const char* item_names[] = - { - "Text", "Button", "Button (w/ repeat)", "Checkbox", "SliderFloat", "InputText", "InputFloat", - "InputFloat3", "ColorEdit4", "MenuItem", "TreeNode", "TreeNode (w/ double-click)", "ListBox" - }; - static int item_type = 1; - ImGui::Combo("Item Type", &item_type, item_names, IM_ARRAYSIZE(item_names), IM_ARRAYSIZE(item_names)); - ImGui::SameLine(); - HelpMarker("Testing how various types of items are interacting with the IsItemXXX functions."); - - // Submit selected item item so we can query their status in the code following it. - bool ret = false; - static bool b = false; - static float col4f[4] = { 1.0f, 0.5, 0.0f, 1.0f }; - static char str[16] = {}; - if (item_type == 0) { ImGui::Text("ITEM: Text"); } // Testing text items with no identifier/interaction - if (item_type == 1) { ret = ImGui::Button("ITEM: Button"); } // Testing button - if (item_type == 2) { ImGui::PushButtonRepeat(true); ret = ImGui::Button("ITEM: Button"); ImGui::PopButtonRepeat(); } // Testing button (with repeater) - if (item_type == 3) { ret = ImGui::Checkbox("ITEM: Checkbox", &b); } // Testing checkbox - if (item_type == 4) { ret = ImGui::SliderFloat("ITEM: SliderFloat", &col4f[0], 0.0f, 1.0f); } // Testing basic item - if (item_type == 5) { ret = ImGui::InputText("ITEM: InputText", &str[0], IM_ARRAYSIZE(str)); } // Testing input text (which handles tabbing) - if (item_type == 6) { ret = ImGui::InputFloat("ITEM: InputFloat", col4f, 1.0f); } // Testing +/- buttons on scalar input - if (item_type == 7) { ret = ImGui::InputFloat3("ITEM: InputFloat3", col4f); } // Testing multi-component items (IsItemXXX flags are reported merged) - if (item_type == 8) { ret = ImGui::ColorEdit4("ITEM: ColorEdit4", col4f); } // Testing multi-component items (IsItemXXX flags are reported merged) - if (item_type == 9) { ret = ImGui::MenuItem("ITEM: MenuItem"); } // Testing menu item (they use ImGuiButtonFlags_PressedOnRelease button policy) - if (item_type == 10){ ret = ImGui::TreeNode("ITEM: TreeNode"); if (ret) ImGui::TreePop(); } // Testing tree node - if (item_type == 11){ ret = ImGui::TreeNodeEx("ITEM: TreeNode w/ ImGuiTreeNodeFlags_OpenOnDoubleClick", ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_NoTreePushOnOpen); } // Testing tree node with ImGuiButtonFlags_PressedOnDoubleClick button policy. - if (item_type == 12){ const char* items[] = { "Apple", "Banana", "Cherry", "Kiwi" }; static int current = 1; ret = ImGui::ListBox("ITEM: ListBox", ¤t, items, IM_ARRAYSIZE(items), IM_ARRAYSIZE(items)); } - - // Display the values of IsItemHovered() and other common item state functions. - // Note that the ImGuiHoveredFlags_XXX flags can be combined. - // Because BulletText is an item itself and that would affect the output of IsItemXXX functions, - // we query every state in a single call to avoid storing them and to simplify the code. - ImGui::BulletText( - "Return value = %d\n" - "IsItemFocused() = %d\n" - "IsItemHovered() = %d\n" - "IsItemHovered(_AllowWhenBlockedByPopup) = %d\n" - "IsItemHovered(_AllowWhenBlockedByActiveItem) = %d\n" - "IsItemHovered(_AllowWhenOverlapped) = %d\n" - "IsItemHovered(_RectOnly) = %d\n" - "IsItemActive() = %d\n" - "IsItemEdited() = %d\n" - "IsItemActivated() = %d\n" - "IsItemDeactivated() = %d\n" - "IsItemDeactivatedAfterEdit() = %d\n" - "IsItemVisible() = %d\n" - "IsItemClicked() = %d\n" - "IsItemToggledOpen() = %d\n" - "GetItemRectMin() = (%.1f, %.1f)\n" - "GetItemRectMax() = (%.1f, %.1f)\n" - "GetItemRectSize() = (%.1f, %.1f)", - ret, - ImGui::IsItemFocused(), - ImGui::IsItemHovered(), - ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup), - ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem), - ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenOverlapped), - ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly), - ImGui::IsItemActive(), - ImGui::IsItemEdited(), - ImGui::IsItemActivated(), - ImGui::IsItemDeactivated(), - ImGui::IsItemDeactivatedAfterEdit(), - ImGui::IsItemVisible(), - ImGui::IsItemClicked(), - ImGui::IsItemToggledOpen(), - ImGui::GetItemRectMin().x, ImGui::GetItemRectMin().y, - ImGui::GetItemRectMax().x, ImGui::GetItemRectMax().y, - ImGui::GetItemRectSize().x, ImGui::GetItemRectSize().y - ); - - static bool embed_all_inside_a_child_window = false; - ImGui::Checkbox("Embed everything inside a child window (for additional testing)", &embed_all_inside_a_child_window); - if (embed_all_inside_a_child_window) - ImGui::BeginChild("outer_child", ImVec2(0, ImGui::GetFontSize() * 20.0f), true); - - // Testing IsWindowFocused() function with its various flags. - // Note that the ImGuiFocusedFlags_XXX flags can be combined. - ImGui::BulletText( - "IsWindowFocused() = %d\n" - "IsWindowFocused(_ChildWindows) = %d\n" - "IsWindowFocused(_ChildWindows|_RootWindow) = %d\n" - "IsWindowFocused(_RootWindow) = %d\n" - "IsWindowFocused(_AnyWindow) = %d\n", - ImGui::IsWindowFocused(), - ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows), - ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows | ImGuiFocusedFlags_RootWindow), - ImGui::IsWindowFocused(ImGuiFocusedFlags_RootWindow), - ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow)); - - // Testing IsWindowHovered() function with its various flags. - // Note that the ImGuiHoveredFlags_XXX flags can be combined. - ImGui::BulletText( - "IsWindowHovered() = %d\n" - "IsWindowHovered(_AllowWhenBlockedByPopup) = %d\n" - "IsWindowHovered(_AllowWhenBlockedByActiveItem) = %d\n" - "IsWindowHovered(_ChildWindows) = %d\n" - "IsWindowHovered(_ChildWindows|_RootWindow) = %d\n" - "IsWindowHovered(_ChildWindows|_AllowWhenBlockedByPopup) = %d\n" - "IsWindowHovered(_RootWindow) = %d\n" - "IsWindowHovered(_AnyWindow) = %d\n", - ImGui::IsWindowHovered(), - ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup), - ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem), - ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows), - ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_RootWindow), - ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_AllowWhenBlockedByPopup), - ImGui::IsWindowHovered(ImGuiHoveredFlags_RootWindow), - ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow)); - - ImGui::BeginChild("child", ImVec2(0, 50), true); - ImGui::Text("This is another child window for testing the _ChildWindows flag."); - ImGui::EndChild(); - if (embed_all_inside_a_child_window) - ImGui::EndChild(); - - static char unused_str[] = "This widget is only here to be able to tab-out of the widgets above."; - ImGui::InputText("unused", unused_str, IM_ARRAYSIZE(unused_str), ImGuiInputTextFlags_ReadOnly); - - // Calling IsItemHovered() after begin returns the hovered status of the title bar. - // This is useful in particular if you want to create a context menu associated to the title bar of a window. - // This will also work when docked into a Tab (the Tab replace the Title Bar and guarantee the same properties). - static bool test_window = false; - ImGui::Checkbox("Hovered/Active tests after Begin() for title bar testing", &test_window); - if (test_window) - { - // FIXME-DOCK: This window cannot be docked within the ImGui Demo window, this will cause a feedback loop and get them stuck. - // Could we fix this through an ImGuiWindowClass feature? Or an API call to tag our parent as "don't skip items"? - ImGui::Begin("Title bar Hovered/Active tests", &test_window); - if (ImGui::BeginPopupContextItem()) // <-- This is using IsItemHovered() - { - if (ImGui::MenuItem("Close")) { test_window = false; } - ImGui::EndPopup(); - } - ImGui::Text( - "IsItemHovered() after begin = %d (== is title bar hovered)\n" - "IsItemActive() after begin = %d (== is window being clicked/moved)\n", - ImGui::IsItemHovered(), ImGui::IsItemActive()); - ImGui::End(); - } - - ImGui::TreePop(); - } -} - -static void ShowDemoWindowLayout() -{ - if (!ImGui::CollapsingHeader("Layout & Scrolling")) - return; - - if (ImGui::TreeNode("Child windows")) - { - HelpMarker("Use child windows to begin into a self-contained independent scrolling/clipping regions within a host window."); - static bool disable_mouse_wheel = false; - static bool disable_menu = false; - ImGui::Checkbox("Disable Mouse Wheel", &disable_mouse_wheel); - ImGui::Checkbox("Disable Menu", &disable_menu); - - // Child 1: no border, enable horizontal scrollbar - { - ImGuiWindowFlags window_flags = ImGuiWindowFlags_HorizontalScrollbar; - if (disable_mouse_wheel) - window_flags |= ImGuiWindowFlags_NoScrollWithMouse; - ImGui::BeginChild("ChildL", ImVec2(ImGui::GetWindowContentRegionWidth() * 0.5f, 260), false, window_flags); - for (int i = 0; i < 100; i++) - ImGui::Text("%04d: scrollable region", i); - ImGui::EndChild(); - } - - ImGui::SameLine(); - - // Child 2: rounded border - { - ImGuiWindowFlags window_flags = ImGuiWindowFlags_None; - if (disable_mouse_wheel) - window_flags |= ImGuiWindowFlags_NoScrollWithMouse; - if (!disable_menu) - window_flags |= ImGuiWindowFlags_MenuBar; - ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 5.0f); - ImGui::BeginChild("ChildR", ImVec2(0, 260), true, window_flags); - if (!disable_menu && ImGui::BeginMenuBar()) - { - if (ImGui::BeginMenu("Menu")) - { - ShowExampleMenuFile(); - ImGui::EndMenu(); - } - ImGui::EndMenuBar(); - } - ImGui::Columns(2); - for (int i = 0; i < 100; i++) - { - char buf[32]; - sprintf(buf, "%03d", i); - ImGui::Button(buf, ImVec2(-FLT_MIN, 0.0f)); - ImGui::NextColumn(); - } - ImGui::EndChild(); - ImGui::PopStyleVar(); - } - - ImGui::Separator(); - - // Demonstrate a few extra things - // - Changing ImGuiCol_ChildBg (which is transparent black in default styles) - // - Using SetCursorPos() to position child window (the child window is an item from the POV of parent window) - // You can also call SetNextWindowPos() to position the child window. The parent window will effectively - // layout from this position. - // - Using ImGui::GetItemRectMin/Max() to query the "item" state (because the child window is an item from - // the POV of the parent window). See 'Demo->Querying Status (Active/Focused/Hovered etc.)' for details. - { - static int offset_x = 0; - ImGui::SetNextItemWidth(100); - ImGui::DragInt("Offset X", &offset_x, 1.0f, -1000, 1000); - - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (float)offset_x); - ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(255, 0, 0, 100)); - ImGui::BeginChild("Red", ImVec2(200, 100), true, ImGuiWindowFlags_None); - for (int n = 0; n < 50; n++) - ImGui::Text("Some test %d", n); - ImGui::EndChild(); - bool child_is_hovered = ImGui::IsItemHovered(); - ImVec2 child_rect_min = ImGui::GetItemRectMin(); - ImVec2 child_rect_max = ImGui::GetItemRectMax(); - ImGui::PopStyleColor(); - ImGui::Text("Hovered: %d", child_is_hovered); - ImGui::Text("Rect of child window is: (%.0f,%.0f) (%.0f,%.0f)", child_rect_min.x, child_rect_min.y, child_rect_max.x, child_rect_max.y); - } - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Widgets Width")) - { - // Use SetNextItemWidth() to set the width of a single upcoming item. - // Use PushItemWidth()/PopItemWidth() to set the width of a group of items. - // In real code use you'll probably want to choose width values that are proportional to your font size - // e.g. Using '20.0f * GetFontSize()' as width instead of '200.0f', etc. - - static float f = 0.0f; - ImGui::Text("SetNextItemWidth/PushItemWidth(100)"); - ImGui::SameLine(); HelpMarker("Fixed width."); - ImGui::SetNextItemWidth(100); - ImGui::DragFloat("float##1", &f); - - ImGui::Text("SetNextItemWidth/PushItemWidth(GetWindowWidth() * 0.5f)"); - ImGui::SameLine(); HelpMarker("Half of window width."); - ImGui::SetNextItemWidth(ImGui::GetWindowWidth() * 0.5f); - ImGui::DragFloat("float##2", &f); - - ImGui::Text("SetNextItemWidth/PushItemWidth(GetContentRegionAvail().x * 0.5f)"); - ImGui::SameLine(); HelpMarker("Half of available width.\n(~ right-cursor_pos)\n(works within a column set)"); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x * 0.5f); - ImGui::DragFloat("float##3", &f); - - ImGui::Text("SetNextItemWidth/PushItemWidth(-100)"); - ImGui::SameLine(); HelpMarker("Align to right edge minus 100"); - ImGui::SetNextItemWidth(-100); - ImGui::DragFloat("float##4", &f); - - // Demonstrate using PushItemWidth to surround three items. - // Calling SetNextItemWidth() before each of them would have the same effect. - ImGui::Text("SetNextItemWidth/PushItemWidth(-1)"); - ImGui::SameLine(); HelpMarker("Align to right edge"); - ImGui::PushItemWidth(-1); - ImGui::DragFloat("##float5a", &f); - ImGui::DragFloat("##float5b", &f); - ImGui::DragFloat("##float5c", &f); - ImGui::PopItemWidth(); - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Basic Horizontal Layout")) - { - ImGui::TextWrapped("(Use ImGui::SameLine() to keep adding items to the right of the preceding item)"); - - // Text - ImGui::Text("Two items: Hello"); ImGui::SameLine(); - ImGui::TextColored(ImVec4(1,1,0,1), "Sailor"); - - // Adjust spacing - ImGui::Text("More spacing: Hello"); ImGui::SameLine(0, 20); - ImGui::TextColored(ImVec4(1,1,0,1), "Sailor"); - - // Button - ImGui::AlignTextToFramePadding(); - ImGui::Text("Normal buttons"); ImGui::SameLine(); - ImGui::Button("Banana"); ImGui::SameLine(); - ImGui::Button("Apple"); ImGui::SameLine(); - ImGui::Button("Corniflower"); - - // Button - ImGui::Text("Small buttons"); ImGui::SameLine(); - ImGui::SmallButton("Like this one"); ImGui::SameLine(); - ImGui::Text("can fit within a text block."); - - // Aligned to arbitrary position. Easy/cheap column. - ImGui::Text("Aligned"); - ImGui::SameLine(150); ImGui::Text("x=150"); - ImGui::SameLine(300); ImGui::Text("x=300"); - ImGui::Text("Aligned"); - ImGui::SameLine(150); ImGui::SmallButton("x=150"); - ImGui::SameLine(300); ImGui::SmallButton("x=300"); - - // Checkbox - static bool c1 = false, c2 = false, c3 = false, c4 = false; - ImGui::Checkbox("My", &c1); ImGui::SameLine(); - ImGui::Checkbox("Tailor", &c2); ImGui::SameLine(); - ImGui::Checkbox("Is", &c3); ImGui::SameLine(); - ImGui::Checkbox("Rich", &c4); - - // Various - static float f0 = 1.0f, f1 = 2.0f, f2 = 3.0f; - ImGui::PushItemWidth(80); - const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD" }; - static int item = -1; - ImGui::Combo("Combo", &item, items, IM_ARRAYSIZE(items)); ImGui::SameLine(); - ImGui::SliderFloat("X", &f0, 0.0f, 5.0f); ImGui::SameLine(); - ImGui::SliderFloat("Y", &f1, 0.0f, 5.0f); ImGui::SameLine(); - ImGui::SliderFloat("Z", &f2, 0.0f, 5.0f); - ImGui::PopItemWidth(); - - ImGui::PushItemWidth(80); - ImGui::Text("Lists:"); - static int selection[4] = { 0, 1, 2, 3 }; - for (int i = 0; i < 4; i++) - { - if (i > 0) ImGui::SameLine(); - ImGui::PushID(i); - ImGui::ListBox("", &selection[i], items, IM_ARRAYSIZE(items)); - ImGui::PopID(); - //if (ImGui::IsItemHovered()) ImGui::SetTooltip("ListBox %d hovered", i); - } - ImGui::PopItemWidth(); - - // Dummy - ImVec2 button_sz(40, 40); - ImGui::Button("A", button_sz); ImGui::SameLine(); - ImGui::Dummy(button_sz); ImGui::SameLine(); - ImGui::Button("B", button_sz); - - // Manually wrapping - // (we should eventually provide this as an automatic layout feature, but for now you can do it manually) - ImGui::Text("Manually wrapping:"); - ImGuiStyle& style = ImGui::GetStyle(); - int buttons_count = 20; - float window_visible_x2 = ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMax().x; - for (int n = 0; n < buttons_count; n++) - { - ImGui::PushID(n); - ImGui::Button("Box", button_sz); - float last_button_x2 = ImGui::GetItemRectMax().x; - float next_button_x2 = last_button_x2 + style.ItemSpacing.x + button_sz.x; // Expected position if next button was on same line - if (n + 1 < buttons_count && next_button_x2 < window_visible_x2) - ImGui::SameLine(); - ImGui::PopID(); - } - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Tabs")) - { - if (ImGui::TreeNode("Basic")) - { - ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_None; - if (ImGui::BeginTabBar("MyTabBar", tab_bar_flags)) - { - if (ImGui::BeginTabItem("Avocado")) - { - ImGui::Text("This is the Avocado tab!\nblah blah blah blah blah"); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("Broccoli")) - { - ImGui::Text("This is the Broccoli tab!\nblah blah blah blah blah"); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("Cucumber")) - { - ImGui::Text("This is the Cucumber tab!\nblah blah blah blah blah"); - ImGui::EndTabItem(); - } - ImGui::EndTabBar(); - } - ImGui::Separator(); - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Advanced & Close Button")) - { - // Expose a couple of the available flags. In most cases you may just call BeginTabBar() with no flags (0). - static ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_Reorderable; - ImGui::CheckboxFlags("ImGuiTabBarFlags_Reorderable", (unsigned int*)&tab_bar_flags, ImGuiTabBarFlags_Reorderable); - ImGui::CheckboxFlags("ImGuiTabBarFlags_AutoSelectNewTabs", (unsigned int*)&tab_bar_flags, ImGuiTabBarFlags_AutoSelectNewTabs); - ImGui::CheckboxFlags("ImGuiTabBarFlags_TabListPopupButton", (unsigned int*)&tab_bar_flags, ImGuiTabBarFlags_TabListPopupButton); - ImGui::CheckboxFlags("ImGuiTabBarFlags_NoCloseWithMiddleMouseButton", (unsigned int*)&tab_bar_flags, ImGuiTabBarFlags_NoCloseWithMiddleMouseButton); - if ((tab_bar_flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0) - tab_bar_flags |= ImGuiTabBarFlags_FittingPolicyDefault_; - if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyResizeDown", (unsigned int*)&tab_bar_flags, ImGuiTabBarFlags_FittingPolicyResizeDown)) - tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyResizeDown); - if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyScroll", (unsigned int*)&tab_bar_flags, ImGuiTabBarFlags_FittingPolicyScroll)) - tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyScroll); - - // Tab Bar - const char* names[4] = { "Artichoke", "Beetroot", "Celery", "Daikon" }; - static bool opened[4] = { true, true, true, true }; // Persistent user state - for (int n = 0; n < IM_ARRAYSIZE(opened); n++) - { - if (n > 0) { ImGui::SameLine(); } - ImGui::Checkbox(names[n], &opened[n]); - } - - // Passing a bool* to BeginTabItem() is similar to passing one to Begin(): - // the underlying bool will be set to false when the tab is closed. - if (ImGui::BeginTabBar("MyTabBar", tab_bar_flags)) - { - for (int n = 0; n < IM_ARRAYSIZE(opened); n++) - if (opened[n] && ImGui::BeginTabItem(names[n], &opened[n], ImGuiTabItemFlags_None)) - { - ImGui::Text("This is the %s tab!", names[n]); - if (n & 1) - ImGui::Text("I am an odd tab."); - ImGui::EndTabItem(); - } - ImGui::EndTabBar(); - } - ImGui::Separator(); - ImGui::TreePop(); - } - - if (ImGui::TreeNode("TabItemButton & Leading/Trailing flags")) - { - static ImVector active_tabs; - static int next_tab_id = 0; - if (next_tab_id == 0) // Initialize with some default tabs - for (int i = 0; i < 3; i++) - active_tabs.push_back(next_tab_id++); - - // TabItemButton() and Leading/Trailing flags are distinct features which we will demo together. - // (It is possible to submit regular tabs with Leading/Trailing flags, or TabItemButton tabs without Leading/Trailing flags... - // but they tend to make more sense together) - static bool show_leading_button = true; - static bool show_trailing_button = true; - ImGui::Checkbox("Show Leading TabItemButton()", &show_leading_button); - ImGui::Checkbox("Show Trailing TabItemButton()", &show_trailing_button); - - // Expose some other flags which are useful to showcase how they interact with Leading/Trailing tabs - static ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_FittingPolicyResizeDown; - ImGui::CheckboxFlags("ImGuiTabBarFlags_TabListPopupButton", (unsigned int*)&tab_bar_flags, ImGuiTabBarFlags_TabListPopupButton); - if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyResizeDown", (unsigned int*)&tab_bar_flags, ImGuiTabBarFlags_FittingPolicyResizeDown)) - tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyResizeDown); - if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyScroll", (unsigned int*)&tab_bar_flags, ImGuiTabBarFlags_FittingPolicyScroll)) - tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyScroll); - - if (ImGui::BeginTabBar("MyTabBar", tab_bar_flags)) - { - // Demo a Leading TabItemButton(): click the "?" button to open a menu - if (show_leading_button) - if (ImGui::TabItemButton("?", ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_NoTooltip)) - ImGui::OpenPopup("MyHelpMenu"); - if (ImGui::BeginPopup("MyHelpMenu")) - { - ImGui::Selectable("Hello!"); - ImGui::EndPopup(); - } - - // Demo Trailing Tabs: click the "+" button to add a new tab (in your app you may want to use a font icon instead of the "+") - // Note that we submit it before the regular tabs, but because of the ImGuiTabItemFlags_Trailing flag it will always appear at the end. - if (show_trailing_button) - if (ImGui::TabItemButton("+", ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip)) - active_tabs.push_back(next_tab_id++); // Add new tab - - // Submit our regular tabs - for (int n = 0; n < active_tabs.Size; ) - { - bool open = true; - char name[16]; - snprintf(name, IM_ARRAYSIZE(name), "%04d", active_tabs[n]); - if (ImGui::BeginTabItem(name, &open, ImGuiTabItemFlags_None)) - { - ImGui::Text("This is the %s tab!", name); - ImGui::EndTabItem(); - } - - if (!open) - active_tabs.erase(active_tabs.Data + n); - else - n++; - } - - ImGui::EndTabBar(); - } - ImGui::Separator(); - ImGui::TreePop(); - } - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Groups")) - { - HelpMarker( - "BeginGroup() basically locks the horizontal position for new line. " - "EndGroup() bundles the whole group so that you can use \"item\" functions such as " - "IsItemHovered()/IsItemActive() or SameLine() etc. on the whole group."); - ImGui::BeginGroup(); - { - ImGui::BeginGroup(); - ImGui::Button("AAA"); - ImGui::SameLine(); - ImGui::Button("BBB"); - ImGui::SameLine(); - ImGui::BeginGroup(); - ImGui::Button("CCC"); - ImGui::Button("DDD"); - ImGui::EndGroup(); - ImGui::SameLine(); - ImGui::Button("EEE"); - ImGui::EndGroup(); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("First group hovered"); - } - // Capture the group size and create widgets using the same size - ImVec2 size = ImGui::GetItemRectSize(); - const float values[5] = { 0.5f, 0.20f, 0.80f, 0.60f, 0.25f }; - ImGui::PlotHistogram("##values", values, IM_ARRAYSIZE(values), 0, NULL, 0.0f, 1.0f, size); - - ImGui::Button("ACTION", ImVec2((size.x - ImGui::GetStyle().ItemSpacing.x) * 0.5f, size.y)); - ImGui::SameLine(); - ImGui::Button("REACTION", ImVec2((size.x - ImGui::GetStyle().ItemSpacing.x) * 0.5f, size.y)); - ImGui::EndGroup(); - ImGui::SameLine(); - - ImGui::Button("LEVERAGE\nBUZZWORD", size); - ImGui::SameLine(); - - if (ImGui::ListBoxHeader("List", size)) - { - ImGui::Selectable("Selected", true); - ImGui::Selectable("Not Selected", false); - ImGui::ListBoxFooter(); - } - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Text Baseline Alignment")) - { - { - ImGui::BulletText("Text baseline:"); - ImGui::SameLine(); HelpMarker( - "This is testing the vertical alignment that gets applied on text to keep it aligned with widgets. " - "Lines only composed of text or \"small\" widgets use less vertical space than lines with framed widgets."); - ImGui::Indent(); - - ImGui::Text("KO Blahblah"); ImGui::SameLine(); - ImGui::Button("Some framed item"); ImGui::SameLine(); - HelpMarker("Baseline of button will look misaligned with text.."); - - // If your line starts with text, call AlignTextToFramePadding() to align text to upcoming widgets. - // (because we don't know what's coming after the Text() statement, we need to move the text baseline - // down by FramePadding.y ahead of time) - ImGui::AlignTextToFramePadding(); - ImGui::Text("OK Blahblah"); ImGui::SameLine(); - ImGui::Button("Some framed item"); ImGui::SameLine(); - HelpMarker("We call AlignTextToFramePadding() to vertically align the text baseline by +FramePadding.y"); - - // SmallButton() uses the same vertical padding as Text - ImGui::Button("TEST##1"); ImGui::SameLine(); - ImGui::Text("TEST"); ImGui::SameLine(); - ImGui::SmallButton("TEST##2"); - - // If your line starts with text, call AlignTextToFramePadding() to align text to upcoming widgets. - ImGui::AlignTextToFramePadding(); - ImGui::Text("Text aligned to framed item"); ImGui::SameLine(); - ImGui::Button("Item##1"); ImGui::SameLine(); - ImGui::Text("Item"); ImGui::SameLine(); - ImGui::SmallButton("Item##2"); ImGui::SameLine(); - ImGui::Button("Item##3"); - - ImGui::Unindent(); - } - - ImGui::Spacing(); - - { - ImGui::BulletText("Multi-line text:"); - ImGui::Indent(); - ImGui::Text("One\nTwo\nThree"); ImGui::SameLine(); - ImGui::Text("Hello\nWorld"); ImGui::SameLine(); - ImGui::Text("Banana"); - - ImGui::Text("Banana"); ImGui::SameLine(); - ImGui::Text("Hello\nWorld"); ImGui::SameLine(); - ImGui::Text("One\nTwo\nThree"); - - ImGui::Button("HOP##1"); ImGui::SameLine(); - ImGui::Text("Banana"); ImGui::SameLine(); - ImGui::Text("Hello\nWorld"); ImGui::SameLine(); - ImGui::Text("Banana"); - - ImGui::Button("HOP##2"); ImGui::SameLine(); - ImGui::Text("Hello\nWorld"); ImGui::SameLine(); - ImGui::Text("Banana"); - ImGui::Unindent(); - } - - ImGui::Spacing(); - - { - ImGui::BulletText("Misc items:"); - ImGui::Indent(); - - // SmallButton() sets FramePadding to zero. Text baseline is aligned to match baseline of previous Button. - ImGui::Button("80x80", ImVec2(80, 80)); - ImGui::SameLine(); - ImGui::Button("50x50", ImVec2(50, 50)); - ImGui::SameLine(); - ImGui::Button("Button()"); - ImGui::SameLine(); - ImGui::SmallButton("SmallButton()"); - - // Tree - const float spacing = ImGui::GetStyle().ItemInnerSpacing.x; - ImGui::Button("Button##1"); - ImGui::SameLine(0.0f, spacing); - if (ImGui::TreeNode("Node##1")) - { - // Placeholder tree data - for (int i = 0; i < 6; i++) - ImGui::BulletText("Item %d..", i); - ImGui::TreePop(); - } - - // Vertically align text node a bit lower so it'll be vertically centered with upcoming widget. - // Otherwise you can use SmallButton() (smaller fit). - ImGui::AlignTextToFramePadding(); - - // Common mistake to avoid: if we want to SameLine after TreeNode we need to do it before we add - // other contents below the node. - bool node_open = ImGui::TreeNode("Node##2"); - ImGui::SameLine(0.0f, spacing); ImGui::Button("Button##2"); - if (node_open) - { - // Placeholder tree data - for (int i = 0; i < 6; i++) - ImGui::BulletText("Item %d..", i); - ImGui::TreePop(); - } - - // Bullet - ImGui::Button("Button##3"); - ImGui::SameLine(0.0f, spacing); - ImGui::BulletText("Bullet text"); - - ImGui::AlignTextToFramePadding(); - ImGui::BulletText("Node"); - ImGui::SameLine(0.0f, spacing); ImGui::Button("Button##4"); - ImGui::Unindent(); - } - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Scrolling")) - { - // Vertical scroll functions - HelpMarker("Use SetScrollHereY() or SetScrollFromPosY() to scroll to a given vertical position."); - - static int track_item = 50; - static bool enable_track = true; - static bool enable_extra_decorations = false; - static float scroll_to_off_px = 0.0f; - static float scroll_to_pos_px = 200.0f; - - ImGui::Checkbox("Decoration", &enable_extra_decorations); - - ImGui::Checkbox("Track", &enable_track); - ImGui::PushItemWidth(100); - ImGui::SameLine(140); enable_track |= ImGui::DragInt("##item", &track_item, 0.25f, 0, 99, "Item = %d"); - - bool scroll_to_off = ImGui::Button("Scroll Offset"); - ImGui::SameLine(140); scroll_to_off |= ImGui::DragFloat("##off", &scroll_to_off_px, 1.00f, 0, FLT_MAX, "+%.0f px"); - - bool scroll_to_pos = ImGui::Button("Scroll To Pos"); - ImGui::SameLine(140); scroll_to_pos |= ImGui::DragFloat("##pos", &scroll_to_pos_px, 1.00f, -10, FLT_MAX, "X/Y = %.0f px"); - ImGui::PopItemWidth(); - - if (scroll_to_off || scroll_to_pos) - enable_track = false; - - ImGuiStyle& style = ImGui::GetStyle(); - float child_w = (ImGui::GetContentRegionAvail().x - 4 * style.ItemSpacing.x) / 5; - if (child_w < 1.0f) - child_w = 1.0f; - ImGui::PushID("##VerticalScrolling"); - for (int i = 0; i < 5; i++) - { - if (i > 0) ImGui::SameLine(); - ImGui::BeginGroup(); - const char* names[] = { "Top", "25%", "Center", "75%", "Bottom" }; - ImGui::TextUnformatted(names[i]); - - const ImGuiWindowFlags child_flags = enable_extra_decorations ? ImGuiWindowFlags_MenuBar : 0; - const ImGuiID child_id = ImGui::GetID((void*)(intptr_t)i); - const bool child_is_visible = ImGui::BeginChild(child_id, ImVec2(child_w, 200.0f), true, child_flags); - if (ImGui::BeginMenuBar()) - { - ImGui::TextUnformatted("abc"); - ImGui::EndMenuBar(); - } - if (scroll_to_off) - ImGui::SetScrollY(scroll_to_off_px); - if (scroll_to_pos) - ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + scroll_to_pos_px, i * 0.25f); - if (child_is_visible) // Avoid calling SetScrollHereY when running with culled items - { - for (int item = 0; item < 100; item++) - { - if (enable_track && item == track_item) - { - ImGui::TextColored(ImVec4(1, 1, 0, 1), "Item %d", item); - ImGui::SetScrollHereY(i * 0.25f); // 0.0f:top, 0.5f:center, 1.0f:bottom - } - else - { - ImGui::Text("Item %d", item); - } - } - } - float scroll_y = ImGui::GetScrollY(); - float scroll_max_y = ImGui::GetScrollMaxY(); - ImGui::EndChild(); - ImGui::Text("%.0f/%.0f", scroll_y, scroll_max_y); - ImGui::EndGroup(); - } - ImGui::PopID(); - - // Horizontal scroll functions - ImGui::Spacing(); - HelpMarker( - "Use SetScrollHereX() or SetScrollFromPosX() to scroll to a given horizontal position.\n\n" - "Because the clipping rectangle of most window hides half worth of WindowPadding on the " - "left/right, using SetScrollFromPosX(+1) will usually result in clipped text whereas the " - "equivalent SetScrollFromPosY(+1) wouldn't."); - ImGui::PushID("##HorizontalScrolling"); - for (int i = 0; i < 5; i++) - { - float child_height = ImGui::GetTextLineHeight() + style.ScrollbarSize + style.WindowPadding.y * 2.0f; - ImGuiWindowFlags child_flags = ImGuiWindowFlags_HorizontalScrollbar | (enable_extra_decorations ? ImGuiWindowFlags_AlwaysVerticalScrollbar : 0); - ImGuiID child_id = ImGui::GetID((void*)(intptr_t)i); - bool child_is_visible = ImGui::BeginChild(child_id, ImVec2(-100, child_height), true, child_flags); - if (scroll_to_off) - ImGui::SetScrollX(scroll_to_off_px); - if (scroll_to_pos) - ImGui::SetScrollFromPosX(ImGui::GetCursorStartPos().x + scroll_to_pos_px, i * 0.25f); - if (child_is_visible) // Avoid calling SetScrollHereY when running with culled items - { - for (int item = 0; item < 100; item++) - { - if (enable_track && item == track_item) - { - ImGui::TextColored(ImVec4(1, 1, 0, 1), "Item %d", item); - ImGui::SetScrollHereX(i * 0.25f); // 0.0f:left, 0.5f:center, 1.0f:right - } - else - { - ImGui::Text("Item %d", item); - } - ImGui::SameLine(); - } - } - float scroll_x = ImGui::GetScrollX(); - float scroll_max_x = ImGui::GetScrollMaxX(); - ImGui::EndChild(); - ImGui::SameLine(); - const char* names[] = { "Left", "25%", "Center", "75%", "Right" }; - ImGui::Text("%s\n%.0f/%.0f", names[i], scroll_x, scroll_max_x); - ImGui::Spacing(); - } - ImGui::PopID(); - - // Miscellaneous Horizontal Scrolling Demo - HelpMarker( - "Horizontal scrolling for a window is enabled via the ImGuiWindowFlags_HorizontalScrollbar flag.\n\n" - "You may want to also explicitly specify content width by using SetNextWindowContentWidth() before Begin()."); - static int lines = 7; - ImGui::SliderInt("Lines", &lines, 1, 15); - ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2.0f, 1.0f)); - ImVec2 scrolling_child_size = ImVec2(0, ImGui::GetFrameHeightWithSpacing() * 7 + 30); - ImGui::BeginChild("scrolling", scrolling_child_size, true, ImGuiWindowFlags_HorizontalScrollbar); - for (int line = 0; line < lines; line++) - { - // Display random stuff. For the sake of this trivial demo we are using basic Button() + SameLine() - // If you want to create your own time line for a real application you may be better off manipulating - // the cursor position yourself, aka using SetCursorPos/SetCursorScreenPos to position the widgets - // yourself. You may also want to use the lower-level ImDrawList API. - int num_buttons = 10 + ((line & 1) ? line * 9 : line * 3); - for (int n = 0; n < num_buttons; n++) - { - if (n > 0) ImGui::SameLine(); - ImGui::PushID(n + line * 1000); - char num_buf[16]; - sprintf(num_buf, "%d", n); - const char* label = (!(n % 15)) ? "FizzBuzz" : (!(n % 3)) ? "Fizz" : (!(n % 5)) ? "Buzz" : num_buf; - float hue = n * 0.05f; - ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(hue, 0.6f, 0.6f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(hue, 0.7f, 0.7f)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(hue, 0.8f, 0.8f)); - ImGui::Button(label, ImVec2(40.0f + sinf((float)(line + n)) * 20.0f, 0.0f)); - ImGui::PopStyleColor(3); - ImGui::PopID(); - } - } - float scroll_x = ImGui::GetScrollX(); - float scroll_max_x = ImGui::GetScrollMaxX(); - ImGui::EndChild(); - ImGui::PopStyleVar(2); - float scroll_x_delta = 0.0f; - ImGui::SmallButton("<<"); - if (ImGui::IsItemActive()) - scroll_x_delta = -ImGui::GetIO().DeltaTime * 1000.0f; - ImGui::SameLine(); - ImGui::Text("Scroll from code"); ImGui::SameLine(); - ImGui::SmallButton(">>"); - if (ImGui::IsItemActive()) - scroll_x_delta = +ImGui::GetIO().DeltaTime * 1000.0f; - ImGui::SameLine(); - ImGui::Text("%.0f/%.0f", scroll_x, scroll_max_x); - if (scroll_x_delta != 0.0f) - { - // Demonstrate a trick: you can use Begin to set yourself in the context of another window - // (here we are already out of your child window) - ImGui::BeginChild("scrolling"); - ImGui::SetScrollX(ImGui::GetScrollX() + scroll_x_delta); - ImGui::EndChild(); - } - ImGui::Spacing(); - - static bool show_horizontal_contents_size_demo_window = false; - ImGui::Checkbox("Show Horizontal contents size demo window", &show_horizontal_contents_size_demo_window); - - if (show_horizontal_contents_size_demo_window) - { - static bool show_h_scrollbar = true; - static bool show_button = true; - static bool show_tree_nodes = true; - static bool show_text_wrapped = false; - static bool show_columns = true; - static bool show_tab_bar = true; - static bool show_child = false; - static bool explicit_content_size = false; - static float contents_size_x = 300.0f; - if (explicit_content_size) - ImGui::SetNextWindowContentSize(ImVec2(contents_size_x, 0.0f)); - ImGui::Begin("Horizontal contents size demo window", &show_horizontal_contents_size_demo_window, show_h_scrollbar ? ImGuiWindowFlags_HorizontalScrollbar : 0); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(2, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 0)); - HelpMarker("Test of different widgets react and impact the work rectangle growing when horizontal scrolling is enabled.\n\nUse 'Metrics->Tools->Show windows rectangles' to visualize rectangles."); - ImGui::Checkbox("H-scrollbar", &show_h_scrollbar); - ImGui::Checkbox("Button", &show_button); // Will grow contents size (unless explicitly overwritten) - ImGui::Checkbox("Tree nodes", &show_tree_nodes); // Will grow contents size and display highlight over full width - ImGui::Checkbox("Text wrapped", &show_text_wrapped);// Will grow and use contents size - ImGui::Checkbox("Columns", &show_columns); // Will use contents size - ImGui::Checkbox("Tab bar", &show_tab_bar); // Will use contents size - ImGui::Checkbox("Child", &show_child); // Will grow and use contents size - ImGui::Checkbox("Explicit content size", &explicit_content_size); - ImGui::Text("Scroll %.1f/%.1f %.1f/%.1f", ImGui::GetScrollX(), ImGui::GetScrollMaxX(), ImGui::GetScrollY(), ImGui::GetScrollMaxY()); - if (explicit_content_size) - { - ImGui::SameLine(); - ImGui::SetNextItemWidth(100); - ImGui::DragFloat("##csx", &contents_size_x); - ImVec2 p = ImGui::GetCursorScreenPos(); - ImGui::GetWindowDrawList()->AddRectFilled(p, ImVec2(p.x + 10, p.y + 10), IM_COL32_WHITE); - ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(p.x + contents_size_x - 10, p.y), ImVec2(p.x + contents_size_x, p.y + 10), IM_COL32_WHITE); - ImGui::Dummy(ImVec2(0, 10)); - } - ImGui::PopStyleVar(2); - ImGui::Separator(); - if (show_button) - { - ImGui::Button("this is a 300-wide button", ImVec2(300, 0)); - } - if (show_tree_nodes) - { - bool open = true; - if (ImGui::TreeNode("this is a tree node")) - { - if (ImGui::TreeNode("another one of those tree node...")) - { - ImGui::Text("Some tree contents"); - ImGui::TreePop(); - } - ImGui::TreePop(); - } - ImGui::CollapsingHeader("CollapsingHeader", &open); - } - if (show_text_wrapped) - { - ImGui::TextWrapped("This text should automatically wrap on the edge of the work rectangle."); - } - if (show_columns) - { - ImGui::Columns(4); - for (int n = 0; n < 4; n++) - { - ImGui::Text("Width %.2f", ImGui::GetColumnWidth()); - ImGui::NextColumn(); - } - ImGui::Columns(1); - } - if (show_tab_bar && ImGui::BeginTabBar("Hello")) - { - if (ImGui::BeginTabItem("OneOneOne")) { ImGui::EndTabItem(); } - if (ImGui::BeginTabItem("TwoTwoTwo")) { ImGui::EndTabItem(); } - if (ImGui::BeginTabItem("ThreeThreeThree")) { ImGui::EndTabItem(); } - if (ImGui::BeginTabItem("FourFourFour")) { ImGui::EndTabItem(); } - ImGui::EndTabBar(); - } - if (show_child) - { - ImGui::BeginChild("child", ImVec2(0, 0), true); - ImGui::EndChild(); - } - ImGui::End(); - } - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Clipping")) - { - static ImVec2 size(100.0f, 100.0f); - static ImVec2 offset(30.0f, 30.0f); - ImGui::DragFloat2("size", (float*)&size, 0.5f, 1.0f, 200.0f, "%.0f"); - ImGui::TextWrapped("(Click and drag to scroll)"); - - for (int n = 0; n < 3; n++) - { - if (n > 0) - ImGui::SameLine(); - ImGui::PushID(n); - ImGui::BeginGroup(); // Lock X position - - ImGui::InvisibleButton("##empty", size); - if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) - { - offset.x += ImGui::GetIO().MouseDelta.x; - offset.y += ImGui::GetIO().MouseDelta.y; - } - const ImVec2 p0 = ImGui::GetItemRectMin(); - const ImVec2 p1 = ImGui::GetItemRectMax(); - const char* text_str = "Line 1 hello\nLine 2 clip me!"; - const ImVec2 text_pos = ImVec2(p0.x + offset.x, p0.y + offset.y); - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - - switch (n) - { - case 0: - HelpMarker( - "Using ImGui::PushClipRect():\n" - "Will alter ImGui hit-testing logic + ImDrawList rendering.\n" - "(use this if you want your clipping rectangle to affect interactions)"); - ImGui::PushClipRect(p0, p1, true); - draw_list->AddRectFilled(p0, p1, IM_COL32(90, 90, 120, 255)); - draw_list->AddText(text_pos, IM_COL32_WHITE, text_str); - ImGui::PopClipRect(); - break; - case 1: - HelpMarker( - "Using ImDrawList::PushClipRect():\n" - "Will alter ImDrawList rendering only.\n" - "(use this as a shortcut if you are only using ImDrawList calls)"); - draw_list->PushClipRect(p0, p1, true); - draw_list->AddRectFilled(p0, p1, IM_COL32(90, 90, 120, 255)); - draw_list->AddText(text_pos, IM_COL32_WHITE, text_str); - draw_list->PopClipRect(); - break; - case 2: - HelpMarker( - "Using ImDrawList::AddText() with a fine ClipRect:\n" - "Will alter only this specific ImDrawList::AddText() rendering.\n" - "(this is often used internally to avoid altering the clipping rectangle and minimize draw calls)"); - ImVec4 clip_rect(p0.x, p0.y, p1.x, p1.y); // AddText() takes a ImVec4* here so let's convert. - draw_list->AddRectFilled(p0, p1, IM_COL32(90, 90, 120, 255)); - draw_list->AddText(ImGui::GetFont(), ImGui::GetFontSize(), text_pos, IM_COL32_WHITE, text_str, NULL, 0.0f, &clip_rect); - break; - } - ImGui::EndGroup(); - ImGui::PopID(); - } - - ImGui::TreePop(); - } -} - -static void ShowDemoWindowPopups() -{ - if (!ImGui::CollapsingHeader("Popups & Modal windows")) - return; - - // The properties of popups windows are: - // - They block normal mouse hovering detection outside them. (*) - // - Unless modal, they can be closed by clicking anywhere outside them, or by pressing ESCAPE. - // - Their visibility state (~bool) is held internally by Dear ImGui instead of being held by the programmer as - // we are used to with regular Begin() calls. User can manipulate the visibility state by calling OpenPopup(). - // (*) One can use IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup) to bypass it and detect hovering even - // when normally blocked by a popup. - // Those three properties are connected. The library needs to hold their visibility state BECAUSE it can close - // popups at any time. - - // Typical use for regular windows: - // bool my_tool_is_active = false; if (ImGui::Button("Open")) my_tool_is_active = true; [...] if (my_tool_is_active) Begin("My Tool", &my_tool_is_active) { [...] } End(); - // Typical use for popups: - // if (ImGui::Button("Open")) ImGui::OpenPopup("MyPopup"); if (ImGui::BeginPopup("MyPopup") { [...] EndPopup(); } - - // With popups we have to go through a library call (here OpenPopup) to manipulate the visibility state. - // This may be a bit confusing at first but it should quickly make sense. Follow on the examples below. - - if (ImGui::TreeNode("Popups")) - { - 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 }; - - // Simple selection popup (if you want to show the current selection inside the Button itself, - // you may want to build a string using the "###" operator to preserve a constant ID with a variable label) - if (ImGui::Button("Select..")) - ImGui::OpenPopup("my_select_popup"); - ImGui::SameLine(); - ImGui::TextUnformatted(selected_fish == -1 ? "" : names[selected_fish]); - if (ImGui::BeginPopup("my_select_popup")) - { - ImGui::Text("Aquarium"); - ImGui::Separator(); - for (int i = 0; i < IM_ARRAYSIZE(names); i++) - if (ImGui::Selectable(names[i])) - selected_fish = i; - ImGui::EndPopup(); - } - - // Showing a menu with toggles - if (ImGui::Button("Toggle..")) - ImGui::OpenPopup("my_toggle_popup"); - if (ImGui::BeginPopup("my_toggle_popup")) - { - 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")) - { - for (int i = 0; i < IM_ARRAYSIZE(names); i++) - ImGui::MenuItem(names[i], "", &toggles[i]); - if (ImGui::BeginMenu("Sub-menu")) - { - ImGui::MenuItem("Click me"); - if (ImGui::Button("Stacked Popup")) - ImGui::OpenPopup("another popup"); - if (ImGui::BeginPopup("another popup")) - { - ImGui::Text("I am the last one here."); - ImGui::EndPopup(); - } - ImGui::EndMenu(); - } - ImGui::EndPopup(); - } - ImGui::EndPopup(); - } - - // Call the more complete ShowExampleMenuFile which we use in various places of this demo - if (ImGui::Button("File Menu..")) - ImGui::OpenPopup("my_file_popup"); - if (ImGui::BeginPopup("my_file_popup")) - { - ShowExampleMenuFile(); - ImGui::EndPopup(); - } - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Context menus")) - { - // BeginPopupContextItem() is a helper to provide common/simple popup behavior of essentially doing: - // if (IsItemHovered() && IsMouseReleased(ImGuiMouseButton_Right)) - // OpenPopup(id); - // return BeginPopup(id); - // For more advanced uses you may want to replicate and customize this code. - // See details in BeginPopupContextItem(). - static float value = 0.5f; - ImGui::Text("Value = %.3f (<-- right-click here)", value); - if (ImGui::BeginPopupContextItem("item context menu")) - { - if (ImGui::Selectable("Set to zero")) value = 0.0f; - if (ImGui::Selectable("Set to PI")) value = 3.1415f; - ImGui::SetNextItemWidth(-1); - ImGui::DragFloat("##Value", &value, 0.1f, 0.0f, 0.0f); - ImGui::EndPopup(); - } - - // We can also use OpenPopupOnItemClick() which is the same as BeginPopupContextItem() but without the - // Begin() call. So here we will make it that clicking on the text field with the right mouse button (1) - // will toggle the visibility of the popup above. - ImGui::Text("(You can also right-click me to open the same popup as above.)"); - ImGui::OpenPopupOnItemClick("item context menu", 1); - - // When used after an item that has an ID (e.g.Button), we can skip providing an ID to BeginPopupContextItem(). - // BeginPopupContextItem() will use the last item ID as the popup ID. - // In addition here, we want to include your editable label inside the button label. - // We use the ### operator to override the ID (read FAQ about ID for details) - static char name[32] = "Label1"; - char buf[64]; - sprintf(buf, "Button: %s###Button", name); // ### operator override ID ignoring the preceding label - ImGui::Button(buf); - if (ImGui::BeginPopupContextItem()) - { - ImGui::Text("Edit name:"); - ImGui::InputText("##edit", name, IM_ARRAYSIZE(name)); - if (ImGui::Button("Close")) - ImGui::CloseCurrentPopup(); - ImGui::EndPopup(); - } - ImGui::SameLine(); ImGui::Text("(<-- right-click here)"); - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Modals")) - { - ImGui::TextWrapped("Modal windows are like popups but the user cannot close them by clicking outside."); - - if (ImGui::Button("Delete..")) - ImGui::OpenPopup("Delete?"); - - // Always center this window when appearing - ImVec2 center = ImGui::GetMainViewport()->GetCenter(); - //ImVec2 parent_pos = ImGui::GetWindowPos(); - //ImVec2 parent_size = ImGui::GetWindowSize(); - //ImVec2 center(parent_pos.x + parent_size.x * 0.5f, parent_pos.y + parent_size.y * 0.5f); - ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); - - if (ImGui::BeginPopupModal("Delete?", NULL, ImGuiWindowFlags_AlwaysAutoResize)) - { - ImGui::Text("All those beautiful files will be deleted.\nThis operation cannot be undone!\n\n"); - ImGui::Separator(); - - //static int unused_i = 0; - //ImGui::Combo("Combo", &unused_i, "Delete\0Delete harder\0"); - - static bool dont_ask_me_next_time = false; - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); - ImGui::Checkbox("Don't ask me next time", &dont_ask_me_next_time); - ImGui::PopStyleVar(); - - if (ImGui::Button("OK", ImVec2(120, 0))) { ImGui::CloseCurrentPopup(); } - ImGui::SetItemDefaultFocus(); - ImGui::SameLine(); - if (ImGui::Button("Cancel", ImVec2(120, 0))) { ImGui::CloseCurrentPopup(); } - ImGui::EndPopup(); - } - - if (ImGui::Button("Stacked modals..")) - ImGui::OpenPopup("Stacked 1"); - if (ImGui::BeginPopupModal("Stacked 1", NULL, ImGuiWindowFlags_MenuBar)) - { - if (ImGui::BeginMenuBar()) - { - if (ImGui::BeginMenu("File")) - { - if (ImGui::MenuItem("Some menu item")) {} - ImGui::EndMenu(); - } - ImGui::EndMenuBar(); - } - ImGui::Text("Hello from Stacked The First\nUsing style.Colors[ImGuiCol_ModalWindowDimBg] behind it."); - - // Testing behavior of widgets stacking their own regular popups over the modal. - static int item = 1; - static float color[4] = { 0.4f, 0.7f, 0.0f, 0.5f }; - ImGui::Combo("Combo", &item, "aaaa\0bbbb\0cccc\0dddd\0eeee\0\0"); - ImGui::ColorEdit4("color", color); - - if (ImGui::Button("Add another modal..")) - ImGui::OpenPopup("Stacked 2"); - - // Also demonstrate passing a bool* to BeginPopupModal(), this will create a regular close button which - // will close the popup. Note that the visibility state of popups is owned by imgui, so the input value - // of the bool actually doesn't matter here. - bool unused_open = true; - if (ImGui::BeginPopupModal("Stacked 2", &unused_open)) - { - ImGui::Text("Hello from Stacked The Second!"); - if (ImGui::Button("Close")) - ImGui::CloseCurrentPopup(); - ImGui::EndPopup(); - } - - if (ImGui::Button("Close")) - ImGui::CloseCurrentPopup(); - ImGui::EndPopup(); - } - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Menus inside a regular window")) - { - ImGui::TextWrapped("Below we are testing adding menu items to a regular window. It's rather unusual but should work!"); - ImGui::Separator(); - - // Note: As a quirk in this very specific example, we want to differentiate the parent of this menu from the - // parent of the various popup menus above. To do so we are encloding the items in a PushID()/PopID() block - // to make them two different menusets. If we don't, opening any popup above and hovering our menu here would - // open it. This is because once a menu is active, we allow to switch to a sibling menu by just hovering on it, - // which is the desired behavior for regular menus. - ImGui::PushID("foo"); - ImGui::MenuItem("Menu item", "CTRL+M"); - if (ImGui::BeginMenu("Menu inside a regular window")) - { - ShowExampleMenuFile(); - ImGui::EndMenu(); - } - ImGui::PopID(); - ImGui::Separator(); - ImGui::TreePop(); - } -} - -static void ShowDemoWindowColumns() -{ - if (!ImGui::CollapsingHeader("Columns")) - return; - - ImGui::PushID("Columns"); - - static bool disable_indent = false; - ImGui::Checkbox("Disable tree indentation", &disable_indent); - ImGui::SameLine(); - HelpMarker("Disable the indenting of tree nodes so demo columns can use the full window width."); - if (disable_indent) - ImGui::PushStyleVar(ImGuiStyleVar_IndentSpacing, 0.0f); - - // Basic columns - if (ImGui::TreeNode("Basic")) - { - ImGui::Text("Without border:"); - ImGui::Columns(3, "mycolumns3", false); // 3-ways, no border - ImGui::Separator(); - for (int n = 0; n < 14; n++) - { - char label[32]; - sprintf(label, "Item %d", n); - if (ImGui::Selectable(label)) {} - //if (ImGui::Button(label, ImVec2(-FLT_MIN,0.0f))) {} - ImGui::NextColumn(); - } - ImGui::Columns(1); - ImGui::Separator(); - - ImGui::Text("With border:"); - ImGui::Columns(4, "mycolumns"); // 4-ways, with border - ImGui::Separator(); - ImGui::Text("ID"); ImGui::NextColumn(); - ImGui::Text("Name"); ImGui::NextColumn(); - ImGui::Text("Path"); ImGui::NextColumn(); - ImGui::Text("Hovered"); ImGui::NextColumn(); - ImGui::Separator(); - const char* names[3] = { "One", "Two", "Three" }; - const char* paths[3] = { "/path/one", "/path/two", "/path/three" }; - static int selected = -1; - for (int i = 0; i < 3; i++) - { - char label[32]; - sprintf(label, "%04d", i); - if (ImGui::Selectable(label, selected == i, ImGuiSelectableFlags_SpanAllColumns)) - selected = i; - bool hovered = ImGui::IsItemHovered(); - ImGui::NextColumn(); - ImGui::Text(names[i]); ImGui::NextColumn(); - ImGui::Text(paths[i]); ImGui::NextColumn(); - ImGui::Text("%d", hovered); ImGui::NextColumn(); - } - ImGui::Columns(1); - ImGui::Separator(); - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Borders")) - { - // NB: Future columns API should allow automatic horizontal borders. - static bool h_borders = true; - static bool v_borders = true; - static int columns_count = 4; - const int lines_count = 3; - ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8); - ImGui::DragInt("##columns_count", &columns_count, 0.1f, 2, 10, "%d columns"); - if (columns_count < 2) - columns_count = 2; - ImGui::SameLine(); - ImGui::Checkbox("horizontal", &h_borders); - ImGui::SameLine(); - ImGui::Checkbox("vertical", &v_borders); - ImGui::Columns(columns_count, NULL, v_borders); - for (int i = 0; i < columns_count * lines_count; i++) - { - if (h_borders && ImGui::GetColumnIndex() == 0) - ImGui::Separator(); - ImGui::Text("%c%c%c", 'a' + i, 'a' + i, 'a' + i); - ImGui::Text("Width %.2f", ImGui::GetColumnWidth()); - ImGui::Text("Avail %.2f", ImGui::GetContentRegionAvail().x); - ImGui::Text("Offset %.2f", ImGui::GetColumnOffset()); - ImGui::Text("Long text that is likely to clip"); - ImGui::Button("Button", ImVec2(-FLT_MIN, 0.0f)); - ImGui::NextColumn(); - } - ImGui::Columns(1); - if (h_borders) - ImGui::Separator(); - ImGui::TreePop(); - } - - // Create multiple items in a same cell before switching to next column - if (ImGui::TreeNode("Mixed items")) - { - ImGui::Columns(3, "mixed"); - ImGui::Separator(); - - ImGui::Text("Hello"); - ImGui::Button("Banana"); - ImGui::NextColumn(); - - ImGui::Text("ImGui"); - ImGui::Button("Apple"); - static float foo = 1.0f; - ImGui::InputFloat("red", &foo, 0.05f, 0, "%.3f"); - ImGui::Text("An extra line here."); - ImGui::NextColumn(); - - ImGui::Text("Sailor"); - ImGui::Button("Corniflower"); - static float bar = 1.0f; - ImGui::InputFloat("blue", &bar, 0.05f, 0, "%.3f"); - ImGui::NextColumn(); - - if (ImGui::CollapsingHeader("Category A")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); - if (ImGui::CollapsingHeader("Category B")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); - if (ImGui::CollapsingHeader("Category C")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); - ImGui::Columns(1); - ImGui::Separator(); - ImGui::TreePop(); - } - - // Word wrapping - if (ImGui::TreeNode("Word-wrapping")) - { - ImGui::Columns(2, "word-wrapping"); - ImGui::Separator(); - ImGui::TextWrapped("The quick brown fox jumps over the lazy dog."); - ImGui::TextWrapped("Hello Left"); - ImGui::NextColumn(); - ImGui::TextWrapped("The quick brown fox jumps over the lazy dog."); - ImGui::TextWrapped("Hello Right"); - ImGui::Columns(1); - ImGui::Separator(); - ImGui::TreePop(); - } - - // Scrolling columns - /* - if (ImGui::TreeNode("Vertical Scrolling")) - { - ImGui::BeginChild("##header", ImVec2(0, ImGui::GetTextLineHeightWithSpacing()+ImGui::GetStyle().ItemSpacing.y)); - ImGui::Columns(3); - ImGui::Text("ID"); ImGui::NextColumn(); - ImGui::Text("Name"); ImGui::NextColumn(); - ImGui::Text("Path"); ImGui::NextColumn(); - ImGui::Columns(1); - ImGui::Separator(); - ImGui::EndChild(); - ImGui::BeginChild("##scrollingregion", ImVec2(0, 60)); - ImGui::Columns(3); - for (int i = 0; i < 10; i++) - { - ImGui::Text("%04d", i); ImGui::NextColumn(); - ImGui::Text("Foobar"); ImGui::NextColumn(); - ImGui::Text("/path/foobar/%04d/", i); ImGui::NextColumn(); - } - ImGui::Columns(1); - ImGui::EndChild(); - ImGui::TreePop(); - } - */ - - if (ImGui::TreeNode("Horizontal Scrolling")) - { - ImGui::SetNextWindowContentSize(ImVec2(1500.0f, 0.0f)); - ImVec2 child_size = ImVec2(0, ImGui::GetFontSize() * 20.0f); - ImGui::BeginChild("##ScrollingRegion", child_size, false, ImGuiWindowFlags_HorizontalScrollbar); - ImGui::Columns(10); - int ITEMS_COUNT = 2000; - ImGuiListClipper clipper; // Also demonstrate using the clipper for large list - clipper.Begin(ITEMS_COUNT); - while (clipper.Step()) - { - for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) - for (int j = 0; j < 10; j++) - { - ImGui::Text("Line %d Column %d...", i, j); - ImGui::NextColumn(); - } - } - ImGui::Columns(1); - ImGui::EndChild(); - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Tree")) - { - ImGui::Columns(2, "tree", true); - for (int x = 0; x < 3; x++) - { - bool open1 = ImGui::TreeNode((void*)(intptr_t)x, "Node%d", x); - ImGui::NextColumn(); - ImGui::Text("Node contents"); - ImGui::NextColumn(); - if (open1) - { - for (int y = 0; y < 3; y++) - { - bool open2 = ImGui::TreeNode((void*)(intptr_t)y, "Node%d.%d", x, y); - ImGui::NextColumn(); - ImGui::Text("Node contents"); - if (open2) - { - ImGui::Text("Even more contents"); - if (ImGui::TreeNode("Tree in column")) - { - ImGui::Text("The quick brown fox jumps over the lazy dog"); - ImGui::TreePop(); - } - } - ImGui::NextColumn(); - if (open2) - ImGui::TreePop(); - } - ImGui::TreePop(); - } - } - ImGui::Columns(1); - ImGui::TreePop(); - } - - if (disable_indent) - ImGui::PopStyleVar(); - ImGui::PopID(); -} - -static void ShowDemoWindowMisc() -{ - if (ImGui::CollapsingHeader("Filtering")) - { - // Helper class to easy setup a text filter. - // You may want to implement a more feature-full filtering scheme in your own application. - static ImGuiTextFilter filter; - ImGui::Text("Filter usage:\n" - " \"\" display all lines\n" - " \"xxx\" display lines containing \"xxx\"\n" - " \"xxx,yyy\" display lines containing \"xxx\" or \"yyy\"\n" - " \"-xxx\" hide lines containing \"xxx\""); - filter.Draw(); - const char* lines[] = { "aaa1.c", "bbb1.c", "ccc1.c", "aaa2.cpp", "bbb2.cpp", "ccc2.cpp", "abc.h", "hello, world" }; - for (int i = 0; i < IM_ARRAYSIZE(lines); i++) - if (filter.PassFilter(lines[i])) - ImGui::BulletText("%s", lines[i]); - } - - if (ImGui::CollapsingHeader("Inputs, Navigation & Focus")) - { - ImGuiIO& io = ImGui::GetIO(); - - // Display ImGuiIO output flags - ImGui::Text("WantCaptureMouse: %d", io.WantCaptureMouse); - ImGui::Text("WantCaptureKeyboard: %d", io.WantCaptureKeyboard); - ImGui::Text("WantTextInput: %d", io.WantTextInput); - ImGui::Text("WantSetMousePos: %d", io.WantSetMousePos); - ImGui::Text("NavActive: %d, NavVisible: %d", io.NavActive, io.NavVisible); - - // Display Keyboard/Mouse state - if (ImGui::TreeNode("Keyboard, Mouse & Navigation State")) - { - if (ImGui::IsMousePosValid()) - ImGui::Text("Mouse pos: (%g, %g)", io.MousePos.x, io.MousePos.y); - else - ImGui::Text("Mouse pos: "); - ImGui::Text("Mouse delta: (%g, %g)", io.MouseDelta.x, io.MouseDelta.y); - ImGui::Text("Mouse down:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (io.MouseDownDuration[i] >= 0.0f) { ImGui::SameLine(); ImGui::Text("b%d (%.02f secs)", i, io.MouseDownDuration[i]); } - ImGui::Text("Mouse clicked:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseClicked(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } - ImGui::Text("Mouse dblclick:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseDoubleClicked(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } - ImGui::Text("Mouse released:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseReleased(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } - ImGui::Text("Mouse wheel: %.1f", io.MouseWheel); - - ImGui::Text("Keys down:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (io.KeysDownDuration[i] >= 0.0f) { ImGui::SameLine(); ImGui::Text("%d (0x%X) (%.02f secs)", i, i, io.KeysDownDuration[i]); } - ImGui::Text("Keys pressed:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (ImGui::IsKeyPressed(i)) { ImGui::SameLine(); ImGui::Text("%d (0x%X)", i, i); } - ImGui::Text("Keys release:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (ImGui::IsKeyReleased(i)) { ImGui::SameLine(); ImGui::Text("%d (0x%X)", i, i); } - ImGui::Text("Keys mods: %s%s%s%s", io.KeyCtrl ? "CTRL " : "", io.KeyShift ? "SHIFT " : "", io.KeyAlt ? "ALT " : "", io.KeySuper ? "SUPER " : ""); - ImGui::Text("Chars queue:"); for (int i = 0; i < io.InputQueueCharacters.Size; i++) { ImWchar c = io.InputQueueCharacters[i]; ImGui::SameLine(); ImGui::Text("\'%c\' (0x%04X)", (c > ' ' && c <= 255) ? (char)c : '?', c); } // FIXME: We should convert 'c' to UTF-8 here but the functions are not public. - - ImGui::Text("NavInputs down:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputs[i] > 0.0f) { ImGui::SameLine(); ImGui::Text("[%d] %.2f", i, io.NavInputs[i]); } - ImGui::Text("NavInputs pressed:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputsDownDuration[i] == 0.0f) { ImGui::SameLine(); ImGui::Text("[%d]", i); } - ImGui::Text("NavInputs duration:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputsDownDuration[i] >= 0.0f) { ImGui::SameLine(); ImGui::Text("[%d] %.2f", i, io.NavInputsDownDuration[i]); } - - ImGui::Button("Hovering me sets the\nkeyboard capture flag"); - if (ImGui::IsItemHovered()) - ImGui::CaptureKeyboardFromApp(true); - ImGui::SameLine(); - ImGui::Button("Holding me clears the\nthe keyboard capture flag"); - if (ImGui::IsItemActive()) - ImGui::CaptureKeyboardFromApp(false); - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Tabbing")) - { - ImGui::Text("Use TAB/SHIFT+TAB to cycle through keyboard editable fields."); - static char buf[32] = "hello"; - ImGui::InputText("1", buf, IM_ARRAYSIZE(buf)); - ImGui::InputText("2", buf, IM_ARRAYSIZE(buf)); - ImGui::InputText("3", buf, IM_ARRAYSIZE(buf)); - ImGui::PushAllowKeyboardFocus(false); - ImGui::InputText("4 (tab skip)", buf, IM_ARRAYSIZE(buf)); - //ImGui::SameLine(); HelpMarker("Use ImGui::PushAllowKeyboardFocus(bool) to disable tabbing through certain widgets."); - ImGui::PopAllowKeyboardFocus(); - ImGui::InputText("5", buf, IM_ARRAYSIZE(buf)); - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Focus from code")) - { - bool focus_1 = ImGui::Button("Focus on 1"); ImGui::SameLine(); - bool focus_2 = ImGui::Button("Focus on 2"); ImGui::SameLine(); - bool focus_3 = ImGui::Button("Focus on 3"); - int has_focus = 0; - static char buf[128] = "click on a button to set focus"; - - if (focus_1) ImGui::SetKeyboardFocusHere(); - ImGui::InputText("1", buf, IM_ARRAYSIZE(buf)); - if (ImGui::IsItemActive()) has_focus = 1; - - if (focus_2) ImGui::SetKeyboardFocusHere(); - ImGui::InputText("2", buf, IM_ARRAYSIZE(buf)); - if (ImGui::IsItemActive()) has_focus = 2; - - ImGui::PushAllowKeyboardFocus(false); - if (focus_3) ImGui::SetKeyboardFocusHere(); - ImGui::InputText("3 (tab skip)", buf, IM_ARRAYSIZE(buf)); - if (ImGui::IsItemActive()) has_focus = 3; - ImGui::PopAllowKeyboardFocus(); - - if (has_focus) - ImGui::Text("Item with focus: %d", has_focus); - else - ImGui::Text("Item with focus: "); - - // Use >= 0 parameter to SetKeyboardFocusHere() to focus an upcoming item - static float f3[3] = { 0.0f, 0.0f, 0.0f }; - int focus_ahead = -1; - if (ImGui::Button("Focus on X")) { focus_ahead = 0; } ImGui::SameLine(); - if (ImGui::Button("Focus on Y")) { focus_ahead = 1; } ImGui::SameLine(); - if (ImGui::Button("Focus on Z")) { focus_ahead = 2; } - if (focus_ahead != -1) ImGui::SetKeyboardFocusHere(focus_ahead); - ImGui::SliderFloat3("Float3", &f3[0], 0.0f, 1.0f); - - ImGui::TextWrapped("NB: Cursor & selection are preserved when refocusing last used item in code."); - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Dragging")) - { - ImGui::TextWrapped("You can use ImGui::GetMouseDragDelta(0) to query for the dragged amount on any widget."); - for (int button = 0; button < 3; button++) - { - ImGui::Text("IsMouseDragging(%d):", button); - ImGui::Text(" w/ default threshold: %d,", ImGui::IsMouseDragging(button)); - ImGui::Text(" w/ zero threshold: %d,", ImGui::IsMouseDragging(button, 0.0f)); - ImGui::Text(" w/ large threshold: %d,", ImGui::IsMouseDragging(button, 20.0f)); - } - - ImGui::Button("Drag Me"); - if (ImGui::IsItemActive()) - ImGui::GetForegroundDrawList()->AddLine(io.MouseClickedPos[0], io.MousePos, ImGui::GetColorU32(ImGuiCol_Button), 4.0f); // Draw a line between the button and the mouse cursor - - // Drag operations gets "unlocked" when the mouse has moved past a certain threshold - // (the default threshold is stored in io.MouseDragThreshold). You can request a lower or higher - // threshold using the second parameter of IsMouseDragging() and GetMouseDragDelta(). - ImVec2 value_raw = ImGui::GetMouseDragDelta(0, 0.0f); - ImVec2 value_with_lock_threshold = ImGui::GetMouseDragDelta(0); - ImVec2 mouse_delta = io.MouseDelta; - ImGui::Text("GetMouseDragDelta(0):"); - ImGui::Text(" w/ default threshold: (%.1f, %.1f)", value_with_lock_threshold.x, value_with_lock_threshold.y); - ImGui::Text(" w/ zero threshold: (%.1f, %.1f)", value_raw.x, value_raw.y); - ImGui::Text("io.MouseDelta: (%.1f, %.1f)", mouse_delta.x, mouse_delta.y); - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Mouse cursors")) - { - const char* mouse_cursors_names[] = { "Arrow", "TextInput", "ResizeAll", "ResizeNS", "ResizeEW", "ResizeNESW", "ResizeNWSE", "Hand", "NotAllowed" }; - IM_ASSERT(IM_ARRAYSIZE(mouse_cursors_names) == ImGuiMouseCursor_COUNT); - - ImGuiMouseCursor current = ImGui::GetMouseCursor(); - ImGui::Text("Current mouse cursor = %d: %s", current, mouse_cursors_names[current]); - ImGui::Text("Hover to see mouse cursors:"); - ImGui::SameLine(); HelpMarker( - "Your application can render a different mouse cursor based on what ImGui::GetMouseCursor() returns. " - "If software cursor rendering (io.MouseDrawCursor) is set ImGui will draw the right cursor for you, " - "otherwise your backend needs to handle it."); - for (int i = 0; i < ImGuiMouseCursor_COUNT; i++) - { - char label[32]; - sprintf(label, "Mouse cursor %d: %s", i, mouse_cursors_names[i]); - ImGui::Bullet(); ImGui::Selectable(label, false); - if (ImGui::IsItemHovered() || ImGui::IsItemFocused()) - ImGui::SetMouseCursor(i); - } - ImGui::TreePop(); - } - } -} - -//----------------------------------------------------------------------------- -// [SECTION] About Window / ShowAboutWindow() -// Access from Dear ImGui Demo -> Tools -> About -//----------------------------------------------------------------------------- - -void ImGui::ShowAboutWindow(bool* p_open) -{ - if (!ImGui::Begin("About Dear ImGui", p_open, ImGuiWindowFlags_AlwaysAutoResize)) - { - ImGui::End(); - return; - } - ImGui::Text("Dear ImGui %s", ImGui::GetVersion()); - ImGui::Separator(); - ImGui::Text("By Omar Cornut and all Dear ImGui contributors."); - ImGui::Text("Dear ImGui is licensed under the MIT License, see LICENSE for more information."); - - static bool show_config_info = false; - ImGui::Checkbox("Config/Build Information", &show_config_info); - if (show_config_info) - { - ImGuiIO& io = ImGui::GetIO(); - ImGuiStyle& style = ImGui::GetStyle(); - - bool copy_to_clipboard = ImGui::Button("Copy to clipboard"); - ImVec2 child_size = ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 18); - ImGui::BeginChildFrame(ImGui::GetID("cfg_infos"), child_size, ImGuiWindowFlags_NoMove); - if (copy_to_clipboard) - { - ImGui::LogToClipboard(); - ImGui::LogText("```\n"); // Back quotes will make text appears without formatting when pasting on GitHub - } - - ImGui::Text("Dear ImGui %s (%d)", IMGUI_VERSION, IMGUI_VERSION_NUM); - ImGui::Separator(); - ImGui::Text("sizeof(size_t): %d, sizeof(ImDrawIdx): %d, sizeof(ImDrawVert): %d", (int)sizeof(size_t), (int)sizeof(ImDrawIdx), (int)sizeof(ImDrawVert)); - ImGui::Text("define: __cplusplus=%d", (int)__cplusplus); -#ifdef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - ImGui::Text("define: IMGUI_DISABLE_OBSOLETE_FUNCTIONS"); -#endif -#ifdef IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS - ImGui::Text("define: IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS"); -#endif -#ifdef IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS - ImGui::Text("define: IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS"); -#endif -#ifdef IMGUI_DISABLE_WIN32_FUNCTIONS - ImGui::Text("define: IMGUI_DISABLE_WIN32_FUNCTIONS"); -#endif -#ifdef IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS - ImGui::Text("define: IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS"); -#endif -#ifdef IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS - ImGui::Text("define: IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS"); -#endif -#ifdef IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS - ImGui::Text("define: IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS"); -#endif -#ifdef IMGUI_DISABLE_FILE_FUNCTIONS - ImGui::Text("define: IMGUI_DISABLE_FILE_FUNCTIONS"); -#endif -#ifdef IMGUI_DISABLE_DEFAULT_ALLOCATORS - ImGui::Text("define: IMGUI_DISABLE_DEFAULT_ALLOCATORS"); -#endif -#ifdef IMGUI_USE_BGRA_PACKED_COLOR - ImGui::Text("define: IMGUI_USE_BGRA_PACKED_COLOR"); -#endif -#ifdef _WIN32 - ImGui::Text("define: _WIN32"); -#endif -#ifdef _WIN64 - ImGui::Text("define: _WIN64"); -#endif -#ifdef __linux__ - ImGui::Text("define: __linux__"); -#endif -#ifdef __APPLE__ - ImGui::Text("define: __APPLE__"); -#endif -#ifdef _MSC_VER - ImGui::Text("define: _MSC_VER=%d", _MSC_VER); -#endif -#ifdef __MINGW32__ - ImGui::Text("define: __MINGW32__"); -#endif -#ifdef __MINGW64__ - ImGui::Text("define: __MINGW64__"); -#endif -#ifdef __GNUC__ - ImGui::Text("define: __GNUC__=%d", (int)__GNUC__); -#endif -#ifdef __clang_version__ - ImGui::Text("define: __clang_version__=%s", __clang_version__); -#endif -#ifdef IMGUI_HAS_VIEWPORT - ImGui::Text("define: IMGUI_HAS_VIEWPORT"); -#endif -#ifdef IMGUI_HAS_DOCK - ImGui::Text("define: IMGUI_HAS_DOCK"); -#endif - ImGui::Separator(); - ImGui::Text("io.BackendPlatformName: %s", io.BackendPlatformName ? io.BackendPlatformName : "NULL"); - ImGui::Text("io.BackendRendererName: %s", io.BackendRendererName ? io.BackendRendererName : "NULL"); - ImGui::Text("io.ConfigFlags: 0x%08X", io.ConfigFlags); - if (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) ImGui::Text(" NavEnableKeyboard"); - if (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) ImGui::Text(" NavEnableGamepad"); - if (io.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) ImGui::Text(" NavEnableSetMousePos"); - if (io.ConfigFlags & ImGuiConfigFlags_NavNoCaptureKeyboard) ImGui::Text(" NavNoCaptureKeyboard"); - if (io.ConfigFlags & ImGuiConfigFlags_NoMouse) ImGui::Text(" NoMouse"); - if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) ImGui::Text(" NoMouseCursorChange"); - if (io.ConfigFlags & ImGuiConfigFlags_DockingEnable) ImGui::Text(" DockingEnable"); - if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) ImGui::Text(" ViewportsEnable"); - if (io.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleViewports) ImGui::Text(" DpiEnableScaleViewports"); - if (io.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleFonts) ImGui::Text(" DpiEnableScaleFonts"); - if (io.MouseDrawCursor) ImGui::Text("io.MouseDrawCursor"); - if (io.ConfigViewportsNoAutoMerge) ImGui::Text("io.ConfigViewportsNoAutoMerge"); - if (io.ConfigViewportsNoTaskBarIcon) ImGui::Text("io.ConfigViewportsNoTaskBarIcon"); - if (io.ConfigViewportsNoDecoration) ImGui::Text("io.ConfigViewportsNoDecoration"); - if (io.ConfigViewportsNoDefaultParent) ImGui::Text("io.ConfigViewportsNoDefaultParent"); - if (io.ConfigDockingNoSplit) ImGui::Text("io.ConfigDockingNoSplit"); - if (io.ConfigDockingWithShift) ImGui::Text("io.ConfigDockingWithShift"); - if (io.ConfigDockingAlwaysTabBar) ImGui::Text("io.ConfigDockingAlwaysTabBar"); - if (io.ConfigDockingTransparentPayload) ImGui::Text("io.ConfigDockingTransparentPayload"); - if (io.ConfigMacOSXBehaviors) ImGui::Text("io.ConfigMacOSXBehaviors"); - if (io.ConfigInputTextCursorBlink) ImGui::Text("io.ConfigInputTextCursorBlink"); - if (io.ConfigWindowsResizeFromEdges) ImGui::Text("io.ConfigWindowsResizeFromEdges"); - if (io.ConfigWindowsMoveFromTitleBarOnly) ImGui::Text("io.ConfigWindowsMoveFromTitleBarOnly"); - if (io.ConfigWindowsMemoryCompactTimer >= 0.0f) ImGui::Text("io.ConfigWindowsMemoryCompactTimer = %.1ff", io.ConfigWindowsMemoryCompactTimer); - ImGui::Text("io.BackendFlags: 0x%08X", io.BackendFlags); - if (io.BackendFlags & ImGuiBackendFlags_HasGamepad) ImGui::Text(" HasGamepad"); - if (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) ImGui::Text(" HasMouseCursors"); - if (io.BackendFlags & ImGuiBackendFlags_HasSetMousePos) ImGui::Text(" HasSetMousePos"); - if (io.BackendFlags & ImGuiBackendFlags_PlatformHasViewports) ImGui::Text(" PlatformHasViewports"); - if (io.BackendFlags & ImGuiBackendFlags_HasMouseHoveredViewport)ImGui::Text(" HasMouseHoveredViewport"); - if (io.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) ImGui::Text(" RendererHasVtxOffset"); - if (io.BackendFlags & ImGuiBackendFlags_RendererHasViewports) ImGui::Text(" RendererHasViewports"); - ImGui::Separator(); - ImGui::Text("io.Fonts: %d fonts, Flags: 0x%08X, TexSize: %d,%d", io.Fonts->Fonts.Size, io.Fonts->Flags, io.Fonts->TexWidth, io.Fonts->TexHeight); - ImGui::Text("io.DisplaySize: %.2f,%.2f", io.DisplaySize.x, io.DisplaySize.y); - ImGui::Text("io.DisplayFramebufferScale: %.2f,%.2f", io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y); - ImGui::Separator(); - ImGui::Text("style.WindowPadding: %.2f,%.2f", style.WindowPadding.x, style.WindowPadding.y); - ImGui::Text("style.WindowBorderSize: %.2f", style.WindowBorderSize); - ImGui::Text("style.FramePadding: %.2f,%.2f", style.FramePadding.x, style.FramePadding.y); - ImGui::Text("style.FrameRounding: %.2f", style.FrameRounding); - ImGui::Text("style.FrameBorderSize: %.2f", style.FrameBorderSize); - ImGui::Text("style.ItemSpacing: %.2f,%.2f", style.ItemSpacing.x, style.ItemSpacing.y); - ImGui::Text("style.ItemInnerSpacing: %.2f,%.2f", style.ItemInnerSpacing.x, style.ItemInnerSpacing.y); - - if (copy_to_clipboard) - { - ImGui::LogText("\n```\n"); - ImGui::LogFinish(); - } - ImGui::EndChildFrame(); - } - ImGui::End(); -} - -//----------------------------------------------------------------------------- -// [SECTION] Style Editor / ShowStyleEditor() -//----------------------------------------------------------------------------- -// - ShowStyleSelector() -// - ShowFontSelector() -// - ShowStyleEditor() -//----------------------------------------------------------------------------- - -// Demo helper function to select among default colors. See ShowStyleEditor() for more advanced options. -// Here we use the simplified Combo() api that packs items into a single literal string. -// Useful for quick combo boxes where the choices are known locally. -bool ImGui::ShowStyleSelector(const char* label) -{ - static int style_idx = -1; - if (ImGui::Combo(label, &style_idx, "Classic\0Dark\0Light\0")) - { - switch (style_idx) - { - case 0: ImGui::StyleColorsClassic(); break; - case 1: ImGui::StyleColorsDark(); break; - case 2: ImGui::StyleColorsLight(); break; - } - return true; - } - return false; -} - -// Demo helper function to select among loaded fonts. -// Here we use the regular BeginCombo()/EndCombo() api which is more the more flexible one. -void ImGui::ShowFontSelector(const char* label) -{ - ImGuiIO& io = ImGui::GetIO(); - ImFont* font_current = ImGui::GetFont(); - if (ImGui::BeginCombo(label, font_current->GetDebugName())) - { - for (int n = 0; n < io.Fonts->Fonts.Size; n++) - { - ImFont* font = io.Fonts->Fonts[n]; - ImGui::PushID((void*)font); - if (ImGui::Selectable(font->GetDebugName(), font == font_current)) - io.FontDefault = font; - ImGui::PopID(); - } - ImGui::EndCombo(); - } - ImGui::SameLine(); - HelpMarker( - "- Load additional fonts with io.Fonts->AddFontFromFileTTF().\n" - "- The font atlas is built when calling io.Fonts->GetTexDataAsXXXX() or io.Fonts->Build().\n" - "- Read FAQ and docs/FONTS.md for more details.\n" - "- If you need to add/remove fonts at runtime (e.g. for DPI change), do it before calling NewFrame()."); -} - -// [Internal] Display details for a single font, called by ShowStyleEditor(). -static void NodeFont(ImFont* font) -{ - ImGuiIO& io = ImGui::GetIO(); - ImGuiStyle& style = ImGui::GetStyle(); - bool font_details_opened = ImGui::TreeNode(font, "Font: \"%s\"\n%.2f px, %d glyphs, %d file(s)", - font->ConfigData ? font->ConfigData[0].Name : "", font->FontSize, font->Glyphs.Size, font->ConfigDataCount); - ImGui::SameLine(); if (ImGui::SmallButton("Set as default")) { io.FontDefault = font; } - if (!font_details_opened) - return; - - ImGui::PushFont(font); - ImGui::Text("The quick brown fox jumps over the lazy dog"); - ImGui::PopFont(); - ImGui::DragFloat("Font scale", &font->Scale, 0.005f, 0.3f, 2.0f, "%.1f"); // Scale only this font - ImGui::SameLine(); HelpMarker( - "Note than the default embedded font is NOT meant to be scaled.\n\n" - "Font are currently rendered into bitmaps at a given size at the time of building the atlas. " - "You may oversample them to get some flexibility with scaling. " - "You can also render at multiple sizes and select which one to use at runtime.\n\n" - "(Glimmer of hope: the atlas system will be rewritten in the future to make scaling more flexible.)"); - ImGui::Text("Ascent: %f, Descent: %f, Height: %f", font->Ascent, font->Descent, font->Ascent - font->Descent); - ImGui::Text("Fallback character: '%c' (U+%04X)", font->FallbackChar, font->FallbackChar); - ImGui::Text("Ellipsis character: '%c' (U+%04X)", font->EllipsisChar, font->EllipsisChar); - const int surface_sqrt = (int)sqrtf((float)font->MetricsTotalSurface); - ImGui::Text("Texture Area: about %d px ~%dx%d px", font->MetricsTotalSurface, surface_sqrt, surface_sqrt); - for (int config_i = 0; config_i < font->ConfigDataCount; config_i++) - if (font->ConfigData) - if (const ImFontConfig* cfg = &font->ConfigData[config_i]) - ImGui::BulletText("Input %d: \'%s\', Oversample: (%d,%d), PixelSnapH: %d, Offset: (%.1f,%.1f)", - config_i, cfg->Name, cfg->OversampleH, cfg->OversampleV, cfg->PixelSnapH, cfg->GlyphOffset.x, cfg->GlyphOffset.y); - if (ImGui::TreeNode("Glyphs", "Glyphs (%d)", font->Glyphs.Size)) - { - // Display all glyphs of the fonts in separate pages of 256 characters - const ImU32 glyph_col = ImGui::GetColorU32(ImGuiCol_Text); - for (unsigned int base = 0; base <= IM_UNICODE_CODEPOINT_MAX; base += 256) - { - // Skip ahead if a large bunch of glyphs are not present in the font (test in chunks of 4k) - // This is only a small optimization to reduce the number of iterations when IM_UNICODE_MAX_CODEPOINT - // is large // (if ImWchar==ImWchar32 we will do at least about 272 queries here) - if (!(base & 4095) && font->IsGlyphRangeUnused(base, base + 4095)) - { - base += 4096 - 256; - continue; - } - - int count = 0; - for (unsigned int n = 0; n < 256; n++) - if (font->FindGlyphNoFallback((ImWchar)(base + n))) - count++; - if (count <= 0) - continue; - if (!ImGui::TreeNode((void*)(intptr_t)base, "U+%04X..U+%04X (%d %s)", base, base + 255, count, count > 1 ? "glyphs" : "glyph")) - continue; - float cell_size = font->FontSize * 1; - float cell_spacing = style.ItemSpacing.y; - ImVec2 base_pos = ImGui::GetCursorScreenPos(); - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - for (unsigned int n = 0; n < 256; n++) - { - // We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions - // available here and thus cannot easily generate a zero-terminated UTF-8 encoded string. - ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size + cell_spacing), base_pos.y + (n / 16) * (cell_size + cell_spacing)); - ImVec2 cell_p2(cell_p1.x + cell_size, cell_p1.y + cell_size); - const ImFontGlyph* glyph = font->FindGlyphNoFallback((ImWchar)(base + n)); - draw_list->AddRect(cell_p1, cell_p2, glyph ? IM_COL32(255, 255, 255, 100) : IM_COL32(255, 255, 255, 50)); - if (glyph) - font->RenderChar(draw_list, cell_size, cell_p1, glyph_col, (ImWchar)(base + n)); - if (glyph && ImGui::IsMouseHoveringRect(cell_p1, cell_p2)) - { - ImGui::BeginTooltip(); - ImGui::Text("Codepoint: U+%04X", base + n); - ImGui::Separator(); - ImGui::Text("Visible: %d", glyph->Visible); - ImGui::Text("AdvanceX: %.1f", glyph->AdvanceX); - ImGui::Text("Pos: (%.2f,%.2f)->(%.2f,%.2f)", glyph->X0, glyph->Y0, glyph->X1, glyph->Y1); - ImGui::Text("UV: (%.3f,%.3f)->(%.3f,%.3f)", glyph->U0, glyph->V0, glyph->U1, glyph->V1); - ImGui::EndTooltip(); - } - } - ImGui::Dummy(ImVec2((cell_size + cell_spacing) * 16, (cell_size + cell_spacing) * 16)); - ImGui::TreePop(); - } - ImGui::TreePop(); - } - ImGui::TreePop(); -} - -void ImGui::ShowStyleEditor(ImGuiStyle* ref) -{ - // You can pass in a reference ImGuiStyle structure to compare to, revert to and save to - // (without a reference style pointer, we will use one compared locally as a reference) - ImGuiStyle& style = ImGui::GetStyle(); - static ImGuiStyle ref_saved_style; - - // Default to using internal storage as reference - static bool init = true; - if (init && ref == NULL) - ref_saved_style = style; - init = false; - if (ref == NULL) - ref = &ref_saved_style; - - ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.50f); - - if (ImGui::ShowStyleSelector("Colors##Selector")) - ref_saved_style = style; - ImGui::ShowFontSelector("Fonts##Selector"); - - // Simplified Settings (expose floating-pointer border sizes as boolean representing 0.0f or 1.0f) - if (ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f")) - style.GrabRounding = style.FrameRounding; // Make GrabRounding always the same value as FrameRounding - { bool border = (style.WindowBorderSize > 0.0f); if (ImGui::Checkbox("WindowBorder", &border)) { style.WindowBorderSize = border ? 1.0f : 0.0f; } } - ImGui::SameLine(); - { bool border = (style.FrameBorderSize > 0.0f); if (ImGui::Checkbox("FrameBorder", &border)) { style.FrameBorderSize = border ? 1.0f : 0.0f; } } - ImGui::SameLine(); - { bool border = (style.PopupBorderSize > 0.0f); if (ImGui::Checkbox("PopupBorder", &border)) { style.PopupBorderSize = border ? 1.0f : 0.0f; } } - - // Save/Revert button - if (ImGui::Button("Save Ref")) - *ref = ref_saved_style = style; - ImGui::SameLine(); - if (ImGui::Button("Revert Ref")) - style = *ref; - ImGui::SameLine(); - HelpMarker( - "Save/Revert in local non-persistent storage. Default Colors definition are not affected. " - "Use \"Export\" below to save them somewhere."); - - ImGui::Separator(); - - if (ImGui::BeginTabBar("##tabs", ImGuiTabBarFlags_None)) - { - if (ImGui::BeginTabItem("Sizes")) - { - ImGui::Text("Main"); - ImGui::SliderFloat2("WindowPadding", (float*)&style.WindowPadding, 0.0f, 20.0f, "%.0f"); - ImGui::SliderFloat2("FramePadding", (float*)&style.FramePadding, 0.0f, 20.0f, "%.0f"); - 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, 30.0f, "%.0f"); - ImGui::SliderFloat("ScrollbarSize", &style.ScrollbarSize, 1.0f, 20.0f, "%.0f"); - ImGui::SliderFloat("GrabMinSize", &style.GrabMinSize, 1.0f, 20.0f, "%.0f"); - ImGui::Text("Borders"); - ImGui::SliderFloat("WindowBorderSize", &style.WindowBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui::SliderFloat("ChildBorderSize", &style.ChildBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui::SliderFloat("PopupBorderSize", &style.PopupBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui::SliderFloat("FrameBorderSize", &style.FrameBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui::SliderFloat("TabBorderSize", &style.TabBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui::Text("Rounding"); - ImGui::SliderFloat("WindowRounding", &style.WindowRounding, 0.0f, 12.0f, "%.0f"); - ImGui::SliderFloat("ChildRounding", &style.ChildRounding, 0.0f, 12.0f, "%.0f"); - ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f"); - ImGui::SliderFloat("PopupRounding", &style.PopupRounding, 0.0f, 12.0f, "%.0f"); - ImGui::SliderFloat("ScrollbarRounding", &style.ScrollbarRounding, 0.0f, 12.0f, "%.0f"); - ImGui::SliderFloat("GrabRounding", &style.GrabRounding, 0.0f, 12.0f, "%.0f"); - ImGui::SliderFloat("LogSliderDeadzone", &style.LogSliderDeadzone, 0.0f, 12.0f, "%.0f"); - ImGui::SliderFloat("TabRounding", &style.TabRounding, 0.0f, 12.0f, "%.0f"); - ImGui::Text("Alignment"); - ImGui::SliderFloat2("WindowTitleAlign", (float*)&style.WindowTitleAlign, 0.0f, 1.0f, "%.2f"); - int window_menu_button_position = style.WindowMenuButtonPosition + 1; - if (ImGui::Combo("WindowMenuButtonPosition", (int*)&window_menu_button_position, "None\0Left\0Right\0")) - style.WindowMenuButtonPosition = window_menu_button_position - 1; - ImGui::Combo("ColorButtonPosition", (int*)&style.ColorButtonPosition, "Left\0Right\0"); - ImGui::SliderFloat2("ButtonTextAlign", (float*)&style.ButtonTextAlign, 0.0f, 1.0f, "%.2f"); - ImGui::SameLine(); HelpMarker("Alignment applies when a button is larger than its text content."); - ImGui::SliderFloat2("SelectableTextAlign", (float*)&style.SelectableTextAlign, 0.0f, 1.0f, "%.2f"); - ImGui::SameLine(); HelpMarker("Alignment applies when a selectable is larger than its text content."); - ImGui::Text("Safe Area Padding"); - ImGui::SameLine(); HelpMarker("Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured)."); - ImGui::SliderFloat2("DisplaySafeAreaPadding", (float*)&style.DisplaySafeAreaPadding, 0.0f, 30.0f, "%.0f"); - ImGui::EndTabItem(); - } - - if (ImGui::BeginTabItem("Colors")) - { - static int output_dest = 0; - static bool output_only_modified = true; - if (ImGui::Button("Export")) - { - if (output_dest == 0) - ImGui::LogToClipboard(); - else - ImGui::LogToTTY(); - ImGui::LogText("ImVec4* colors = ImGui::GetStyle().Colors;" IM_NEWLINE); - for (int i = 0; i < ImGuiCol_COUNT; i++) - { - const ImVec4& col = style.Colors[i]; - const char* name = ImGui::GetStyleColorName(i); - if (!output_only_modified || memcmp(&col, &ref->Colors[i], sizeof(ImVec4)) != 0) - ImGui::LogText("colors[ImGuiCol_%s]%*s= ImVec4(%.2ff, %.2ff, %.2ff, %.2ff);" IM_NEWLINE, - name, 23 - (int)strlen(name), "", col.x, col.y, col.z, col.w); - } - ImGui::LogFinish(); - } - ImGui::SameLine(); ImGui::SetNextItemWidth(120); ImGui::Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0"); - ImGui::SameLine(); ImGui::Checkbox("Only Modified Colors", &output_only_modified); - - static ImGuiTextFilter filter; - filter.Draw("Filter colors", ImGui::GetFontSize() * 16); - - static ImGuiColorEditFlags alpha_flags = 0; - if (ImGui::RadioButton("Opaque", alpha_flags == ImGuiColorEditFlags_None)) { alpha_flags = ImGuiColorEditFlags_None; } ImGui::SameLine(); - if (ImGui::RadioButton("Alpha", alpha_flags == ImGuiColorEditFlags_AlphaPreview)) { alpha_flags = ImGuiColorEditFlags_AlphaPreview; } ImGui::SameLine(); - if (ImGui::RadioButton("Both", alpha_flags == ImGuiColorEditFlags_AlphaPreviewHalf)) { alpha_flags = ImGuiColorEditFlags_AlphaPreviewHalf; } ImGui::SameLine(); - HelpMarker( - "In the color list:\n" - "Left-click on color square to open color picker,\n" - "Right-click to open edit options menu."); - - ImGui::BeginChild("##colors", ImVec2(0, 0), true, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NavFlattened); - ImGui::PushItemWidth(-160); - for (int i = 0; i < ImGuiCol_COUNT; i++) - { - const char* name = ImGui::GetStyleColorName(i); - if (!filter.PassFilter(name)) - continue; - ImGui::PushID(i); - ImGui::ColorEdit4("##color", (float*)&style.Colors[i], ImGuiColorEditFlags_AlphaBar | alpha_flags); - if (memcmp(&style.Colors[i], &ref->Colors[i], sizeof(ImVec4)) != 0) - { - // Tips: in a real user application, you may want to merge and use an icon font into the main font, - // so instead of "Save"/"Revert" you'd use icons! - // Read the FAQ and docs/FONTS.md about using icon fonts. It's really easy and super convenient! - ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button("Save")) { ref->Colors[i] = style.Colors[i]; } - ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button("Revert")) { style.Colors[i] = ref->Colors[i]; } - } - ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); - ImGui::TextUnformatted(name); - ImGui::PopID(); - } - ImGui::PopItemWidth(); - ImGui::EndChild(); - - ImGui::EndTabItem(); - } - - if (ImGui::BeginTabItem("Fonts")) - { - ImGuiIO& io = ImGui::GetIO(); - ImFontAtlas* atlas = io.Fonts; - HelpMarker("Read FAQ and docs/FONTS.md for details on font loading."); - ImGui::PushItemWidth(120); - for (int i = 0; i < atlas->Fonts.Size; i++) - { - ImFont* font = atlas->Fonts[i]; - ImGui::PushID(font); - NodeFont(font); - ImGui::PopID(); - } - if (ImGui::TreeNode("Atlas texture", "Atlas texture (%dx%d pixels)", atlas->TexWidth, atlas->TexHeight)) - { - ImVec4 tint_col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); - ImVec4 border_col = ImVec4(1.0f, 1.0f, 1.0f, 0.5f); - ImGui::Image(atlas->TexID, ImVec2((float)atlas->TexWidth, (float)atlas->TexHeight), ImVec2(0, 0), ImVec2(1, 1), tint_col, border_col); - ImGui::TreePop(); - } - - // Post-baking font scaling. Note that this is NOT the nice way of scaling fonts, read below. - // (we enforce hard clamping manually as by default DragFloat/SliderFloat allows CTRL+Click text to get out of bounds). - const float MIN_SCALE = 0.3f; - const float MAX_SCALE = 2.0f; - HelpMarker( - "Those are old settings provided for convenience.\n" - "However, the _correct_ way of scaling your UI is currently to reload your font at the designed size, " - "rebuild the font atlas, and call style.ScaleAllSizes() on a reference ImGuiStyle structure.\n" - "Using those settings here will give you poor quality results."); - static float window_scale = 1.0f; - if (ImGui::DragFloat("window scale", &window_scale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", ImGuiSliderFlags_AlwaysClamp)) // Scale only this window - ImGui::SetWindowFontScale(window_scale); - ImGui::DragFloat("global scale", &io.FontGlobalScale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", ImGuiSliderFlags_AlwaysClamp); // Scale everything - ImGui::PopItemWidth(); - - ImGui::EndTabItem(); - } - - if (ImGui::BeginTabItem("Rendering")) - { - ImGui::Checkbox("Anti-aliased lines", &style.AntiAliasedLines); - ImGui::SameLine(); - HelpMarker("When disabling anti-aliasing lines, you'll probably want to disable borders in your style as well."); - - ImGui::Checkbox("Anti-aliased lines use texture", &style.AntiAliasedLinesUseTex); - ImGui::SameLine(); - HelpMarker("Faster lines using texture data. Require backend to render with bilinear filtering (not point/nearest filtering)."); - - ImGui::Checkbox("Anti-aliased fill", &style.AntiAliasedFill); - ImGui::PushItemWidth(100); - ImGui::DragFloat("Curve Tessellation Tolerance", &style.CurveTessellationTol, 0.02f, 0.10f, 10.0f, "%.2f"); - if (style.CurveTessellationTol < 0.10f) style.CurveTessellationTol = 0.10f; - - // When editing the "Circle Segment Max Error" value, draw a preview of its effect on auto-tessellated circles. - ImGui::DragFloat("Circle Segment Max Error", &style.CircleSegmentMaxError, 0.01f, 0.10f, 10.0f, "%.2f"); - if (ImGui::IsItemActive()) - { - ImGui::SetNextWindowPos(ImGui::GetCursorScreenPos()); - ImGui::BeginTooltip(); - ImVec2 p = ImGui::GetCursorScreenPos(); - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - float RAD_MIN = 10.0f, RAD_MAX = 80.0f; - float off_x = 10.0f; - for (int n = 0; n < 7; n++) - { - const float rad = RAD_MIN + (RAD_MAX - RAD_MIN) * (float)n / (7.0f - 1.0f); - draw_list->AddCircle(ImVec2(p.x + off_x + rad, p.y + RAD_MAX), rad, ImGui::GetColorU32(ImGuiCol_Text), 0); - off_x += 10.0f + rad * 2.0f; - } - ImGui::Dummy(ImVec2(off_x, RAD_MAX * 2.0f)); - ImGui::EndTooltip(); - } - ImGui::SameLine(); - HelpMarker("When drawing circle primitives with \"num_segments == 0\" tesselation will be calculated automatically."); - - ImGui::DragFloat("Global Alpha", &style.Alpha, 0.005f, 0.20f, 1.0f, "%.2f"); // Not exposing zero here so user doesn't "lose" the UI (zero alpha clips all widgets). But application code could have a toggle to switch between zero and non-zero. - ImGui::PopItemWidth(); - - ImGui::EndTabItem(); - } - - ImGui::EndTabBar(); - } - - ImGui::PopItemWidth(); -} - -//----------------------------------------------------------------------------- -// [SECTION] Example App: Main Menu Bar / ShowExampleAppMainMenuBar() -//----------------------------------------------------------------------------- -// - ShowExampleAppMainMenuBar() -// - ShowExampleMenuFile() -//----------------------------------------------------------------------------- - -// Demonstrate creating a "main" fullscreen menu bar and populating it. -// Note the difference between BeginMainMenuBar() and BeginMenuBar(): -// - BeginMenuBar() = menu-bar inside current window (which needs the ImGuiWindowFlags_MenuBar flag!) -// - BeginMainMenuBar() = helper to create menu-bar-sized window at the top of the main viewport + call BeginMenuBar() into it. -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(); - } -} - -// Note that shortcuts are currently provided for display only -// (future version will add explicit flags to BeginMenu() to request processing shortcuts) -static void ShowExampleMenuFile() -{ - ImGui::MenuItem("(demo 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; - static int n = 0; - ImGui::SliderFloat("Value", &f, 0.0f, 1.0f); - ImGui::InputFloat("Input", &f, 0.1f); - ImGui::Combo("Combo", &n, "Yes\0No\0Maybe\0\0"); - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu("Colors")) - { - float sz = ImGui::GetTextLineHeight(); - for (int i = 0; i < ImGuiCol_COUNT; i++) - { - const char* name = ImGui::GetStyleColorName((ImGuiCol)i); - ImVec2 p = ImGui::GetCursorScreenPos(); - ImGui::GetWindowDrawList()->AddRectFilled(p, ImVec2(p.x + sz, p.y + sz), ImGui::GetColorU32((ImGuiCol)i)); - ImGui::Dummy(ImVec2(sz, sz)); - ImGui::SameLine(); - ImGui::MenuItem(name); - } - ImGui::EndMenu(); - } - - // Here we demonstrate appending again to the "Options" menu (which we already created above) - // Of course in this demo it is a little bit silly that this function calls BeginMenu("Options") twice. - // In a real code-base using it would make senses to use this feature from very different code locations. - if (ImGui::BeginMenu("Options")) // <-- Append! - { - static bool b = true; - ImGui::Checkbox("SomeOption", &b); - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu("Disabled", false)) // Disabled - { - IM_ASSERT(0); - } - if (ImGui::MenuItem("Checked", NULL, true)) {} - if (ImGui::MenuItem("Quit", "Alt+F4")) {} -} - -//----------------------------------------------------------------------------- -// [SECTION] Example App: Debug Console / ShowExampleAppConsole() -//----------------------------------------------------------------------------- - -// Demonstrate creating a simple console window, with scrolling, filtering, completion and history. -// For the console example, we are using a more C++ like approach of declaring a class to hold both data and functions. -struct ExampleAppConsole -{ - char InputBuf[256]; - ImVector Items; - ImVector Commands; - ImVector History; - int HistoryPos; // -1: new line, 0..History.Size-1 browsing history. - ImGuiTextFilter Filter; - bool AutoScroll; - bool ScrollToBottom; - - ExampleAppConsole() - { - ClearLog(); - memset(InputBuf, 0, sizeof(InputBuf)); - HistoryPos = -1; - - // "CLASSIFY" is here to provide the test case where "C"+[tab] completes to "CL" and display multiple matches. - Commands.push_back("HELP"); - Commands.push_back("HISTORY"); - Commands.push_back("CLEAR"); - Commands.push_back("CLASSIFY"); - AutoScroll = true; - ScrollToBottom = false; - AddLog("Welcome to Dear ImGui!"); - } - ~ExampleAppConsole() - { - ClearLog(); - for (int i = 0; i < History.Size; i++) - free(History[i]); - } - - // Portable helpers - static int Stricmp(const char* s1, const char* s2) { int d; while ((d = toupper(*s2) - toupper(*s1)) == 0 && *s1) { s1++; s2++; } return d; } - static int Strnicmp(const char* s1, const char* s2, int n) { int d = 0; while (n > 0 && (d = toupper(*s2) - toupper(*s1)) == 0 && *s1) { s1++; s2++; n--; } return d; } - static char* Strdup(const char* s) { size_t len = strlen(s) + 1; void* buf = malloc(len); IM_ASSERT(buf); return (char*)memcpy(buf, (const void*)s, len); } - static void Strtrim(char* s) { char* str_end = s + strlen(s); while (str_end > s && str_end[-1] == ' ') str_end--; *str_end = 0; } - - void ClearLog() - { - for (int i = 0; i < Items.Size; i++) - free(Items[i]); - Items.clear(); - } - - void AddLog(const char* fmt, ...) IM_FMTARGS(2) - { - // FIXME-OPT - char buf[1024]; - va_list args; - va_start(args, fmt); - vsnprintf(buf, IM_ARRAYSIZE(buf), fmt, args); - buf[IM_ARRAYSIZE(buf)-1] = 0; - va_end(args); - Items.push_back(Strdup(buf)); - } - - void Draw(const char* title, bool* p_open) - { - ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); - if (!ImGui::Begin(title, p_open)) - { - ImGui::End(); - return; - } - - // As a specific feature guaranteed by the library, after calling Begin() the last Item represent the title bar. - // So e.g. IsItemHovered() will return true when hovering the title bar. - // Here we create a context menu only available from the title bar. - if (ImGui::BeginPopupContextItem()) - { - if (ImGui::MenuItem("Close Console")) - *p_open = false; - ImGui::EndPopup(); - } - - ImGui::TextWrapped( - "This example implements a console with basic coloring, completion (TAB key) and history (Up/Down keys). A more elaborate " - "implementation may want to store entries along with extra data such as timestamp, emitter, etc."); - ImGui::TextWrapped("Enter 'HELP' for help."); - - // TODO: display items starting from the bottom - - if (ImGui::SmallButton("Add Debug Text")) { AddLog("%d some text", Items.Size); AddLog("some more text"); AddLog("display very important message here!"); } - ImGui::SameLine(); - if (ImGui::SmallButton("Add Debug Error")) { AddLog("[error] something went wrong"); } - ImGui::SameLine(); - if (ImGui::SmallButton("Clear")) { ClearLog(); } - ImGui::SameLine(); - bool copy_to_clipboard = ImGui::SmallButton("Copy"); - //static float t = 0.0f; if (ImGui::GetTime() - t > 0.02f) { t = ImGui::GetTime(); AddLog("Spam %f", t); } - - ImGui::Separator(); - - // Options menu - if (ImGui::BeginPopup("Options")) - { - ImGui::Checkbox("Auto-scroll", &AutoScroll); - ImGui::EndPopup(); - } - - // Options, Filter - if (ImGui::Button("Options")) - ImGui::OpenPopup("Options"); - ImGui::SameLine(); - Filter.Draw("Filter (\"incl,-excl\") (\"error\")", 180); - ImGui::Separator(); - - // Reserve enough left-over height for 1 separator + 1 input text - const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); - ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), false, ImGuiWindowFlags_HorizontalScrollbar); - if (ImGui::BeginPopupContextWindow()) - { - if (ImGui::Selectable("Clear")) ClearLog(); - ImGui::EndPopup(); - } - - // Display every line as a separate entry so we can change their color or add custom widgets. - // If you only want raw text you can use ImGui::TextUnformatted(log.begin(), log.end()); - // NB- if you have thousands of entries this approach may be too inefficient and may require user-side clipping - // to only process visible items. The clipper will automatically measure the height of your first item and then - // "seek" to display only items in the visible area. - // To use the clipper we can replace your standard loop: - // for (int i = 0; i < Items.Size; i++) - // With: - // ImGuiListClipper clipper; - // clipper.Begin(Items.Size); - // while (clipper.Step()) - // for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) - // - That your items are evenly spaced (same height) - // - That you have cheap random access to your elements (you can access them given their index, - // without processing all the ones before) - // You cannot this code as-is if a filter is active because it breaks the 'cheap random-access' property. - // We would need random-access on the post-filtered list. - // A typical application wanting coarse clipping and filtering may want to pre-compute an array of indices - // or offsets of items that passed the filtering test, recomputing this array when user changes the filter, - // and appending newly elements as they are inserted. This is left as a task to the user until we can manage - // to improve this example code! - // If your items are of variable height: - // - Split them into same height items would be simpler and facilitate random-seeking into your list. - // - Consider using manual call to IsRectVisible() and skipping extraneous decoration from your items. - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1)); // Tighten spacing - if (copy_to_clipboard) - ImGui::LogToClipboard(); - for (int i = 0; i < Items.Size; i++) - { - const char* item = Items[i]; - if (!Filter.PassFilter(item)) - continue; - - // Normally you would store more information in your item than just a string. - // (e.g. make Items[] an array of structure, store color/type etc.) - ImVec4 color; - bool has_color = false; - if (strstr(item, "[error]")) { color = ImVec4(1.0f, 0.4f, 0.4f, 1.0f); has_color = true; } - else if (strncmp(item, "# ", 2) == 0) { color = ImVec4(1.0f, 0.8f, 0.6f, 1.0f); has_color = true; } - if (has_color) - ImGui::PushStyleColor(ImGuiCol_Text, color); - ImGui::TextUnformatted(item); - if (has_color) - ImGui::PopStyleColor(); - } - if (copy_to_clipboard) - ImGui::LogFinish(); - - if (ScrollToBottom || (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY())) - ImGui::SetScrollHereY(1.0f); - ScrollToBottom = false; - - ImGui::PopStyleVar(); - ImGui::EndChild(); - ImGui::Separator(); - - // Command-line - bool reclaim_focus = false; - ImGuiInputTextFlags input_text_flags = ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory; - if (ImGui::InputText("Input", InputBuf, IM_ARRAYSIZE(InputBuf), input_text_flags, &TextEditCallbackStub, (void*)this)) - { - char* s = InputBuf; - Strtrim(s); - if (s[0]) - ExecCommand(s); - strcpy(s, ""); - reclaim_focus = true; - } - - // Auto-focus on window apparition - ImGui::SetItemDefaultFocus(); - if (reclaim_focus) - ImGui::SetKeyboardFocusHere(-1); // Auto focus previous widget - - ImGui::End(); - } - - void ExecCommand(const char* command_line) - { - AddLog("# %s\n", command_line); - - // Insert into history. First find match and delete it so it can be pushed to the back. - // This isn't trying to be smart or optimal. - HistoryPos = -1; - for (int i = History.Size - 1; i >= 0; i--) - if (Stricmp(History[i], command_line) == 0) - { - free(History[i]); - History.erase(History.begin() + i); - break; - } - History.push_back(Strdup(command_line)); - - // Process command - if (Stricmp(command_line, "CLEAR") == 0) - { - ClearLog(); - } - else if (Stricmp(command_line, "HELP") == 0) - { - AddLog("Commands:"); - for (int i = 0; i < Commands.Size; i++) - AddLog("- %s", Commands[i]); - } - else if (Stricmp(command_line, "HISTORY") == 0) - { - int first = History.Size - 10; - for (int i = first > 0 ? first : 0; i < History.Size; i++) - AddLog("%3d: %s\n", i, History[i]); - } - else - { - AddLog("Unknown command: '%s'\n", command_line); - } - - // On command input, we scroll to bottom even if AutoScroll==false - ScrollToBottom = true; - } - - // In C++11 you'd be better off using lambdas for this sort of forwarding callbacks - static int TextEditCallbackStub(ImGuiInputTextCallbackData* data) - { - ExampleAppConsole* console = (ExampleAppConsole*)data->UserData; - return console->TextEditCallback(data); - } - - int TextEditCallback(ImGuiInputTextCallbackData* data) - { - //AddLog("cursor: %d, selection: %d-%d", data->CursorPos, data->SelectionStart, data->SelectionEnd); - switch (data->EventFlag) - { - case ImGuiInputTextFlags_CallbackCompletion: - { - // Example of TEXT COMPLETION - - // Locate beginning of current word - const char* word_end = data->Buf + data->CursorPos; - const char* word_start = word_end; - while (word_start > data->Buf) - { - const char c = word_start[-1]; - if (c == ' ' || c == '\t' || c == ',' || c == ';') - break; - word_start--; - } - - // Build a list of candidates - ImVector candidates; - for (int i = 0; i < Commands.Size; i++) - if (Strnicmp(Commands[i], word_start, (int)(word_end - word_start)) == 0) - candidates.push_back(Commands[i]); - - if (candidates.Size == 0) - { - // No match - AddLog("No match for \"%.*s\"!\n", (int)(word_end - word_start), word_start); - } - else if (candidates.Size == 1) - { - // Single match. Delete the beginning of the word and replace it entirely so we've got nice casing. - data->DeleteChars((int)(word_start - data->Buf), (int)(word_end - word_start)); - data->InsertChars(data->CursorPos, candidates[0]); - data->InsertChars(data->CursorPos, " "); - } - else - { - // Multiple matches. Complete as much as we can.. - // So inputing "C"+Tab will complete to "CL" then display "CLEAR" and "CLASSIFY" as matches. - int match_len = (int)(word_end - word_start); - for (;;) - { - int c = 0; - bool all_candidates_matches = true; - for (int i = 0; i < candidates.Size && all_candidates_matches; i++) - if (i == 0) - c = toupper(candidates[i][match_len]); - else if (c == 0 || c != toupper(candidates[i][match_len])) - all_candidates_matches = false; - if (!all_candidates_matches) - break; - match_len++; - } - - if (match_len > 0) - { - data->DeleteChars((int)(word_start - data->Buf), (int)(word_end - word_start)); - data->InsertChars(data->CursorPos, candidates[0], candidates[0] + match_len); - } - - // List matches - AddLog("Possible matches:\n"); - for (int i = 0; i < candidates.Size; i++) - AddLog("- %s\n", candidates[i]); - } - - break; - } - case ImGuiInputTextFlags_CallbackHistory: - { - // Example of HISTORY - const int prev_history_pos = HistoryPos; - if (data->EventKey == ImGuiKey_UpArrow) - { - if (HistoryPos == -1) - HistoryPos = History.Size - 1; - else if (HistoryPos > 0) - HistoryPos--; - } - else if (data->EventKey == ImGuiKey_DownArrow) - { - if (HistoryPos != -1) - if (++HistoryPos >= History.Size) - HistoryPos = -1; - } - - // A better implementation would preserve the data on the current input line along with cursor position. - if (prev_history_pos != HistoryPos) - { - const char* history_str = (HistoryPos >= 0) ? History[HistoryPos] : ""; - data->DeleteChars(0, data->BufTextLen); - data->InsertChars(0, history_str); - } - } - } - return 0; - } -}; - -static void ShowExampleAppConsole(bool* p_open) -{ - static ExampleAppConsole console; - console.Draw("Example: Console", p_open); -} - -//----------------------------------------------------------------------------- -// [SECTION] Example App: Debug Log / ShowExampleAppLog() -//----------------------------------------------------------------------------- - -// Usage: -// static ExampleAppLog my_log; -// my_log.AddLog("Hello %d world\n", 123); -// my_log.Draw("title"); -struct ExampleAppLog -{ - ImGuiTextBuffer Buf; - ImGuiTextFilter Filter; - ImVector LineOffsets; // Index to lines offset. We maintain this with AddLog() calls. - bool AutoScroll; // Keep scrolling if already at the bottom. - - ExampleAppLog() - { - AutoScroll = true; - Clear(); - } - - void Clear() - { - Buf.clear(); - LineOffsets.clear(); - LineOffsets.push_back(0); - } - - void AddLog(const char* fmt, ...) IM_FMTARGS(2) - { - int old_size = Buf.size(); - va_list args; - va_start(args, fmt); - Buf.appendfv(fmt, args); - va_end(args); - for (int new_size = Buf.size(); old_size < new_size; old_size++) - if (Buf[old_size] == '\n') - LineOffsets.push_back(old_size + 1); - } - - void Draw(const char* title, bool* p_open = NULL) - { - if (!ImGui::Begin(title, p_open)) - { - ImGui::End(); - return; - } - - // Options menu - if (ImGui::BeginPopup("Options")) - { - ImGui::Checkbox("Auto-scroll", &AutoScroll); - ImGui::EndPopup(); - } - - // Main window - if (ImGui::Button("Options")) - ImGui::OpenPopup("Options"); - ImGui::SameLine(); - bool clear = ImGui::Button("Clear"); - ImGui::SameLine(); - bool copy = ImGui::Button("Copy"); - ImGui::SameLine(); - Filter.Draw("Filter", -100.0f); - - ImGui::Separator(); - ImGui::BeginChild("scrolling", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar); - - if (clear) - Clear(); - if (copy) - ImGui::LogToClipboard(); - - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); - const char* buf = Buf.begin(); - const char* buf_end = Buf.end(); - if (Filter.IsActive()) - { - // In this example we don't use the clipper when Filter is enabled. - // This is because we don't have a random access on the result on our filter. - // A real application processing logs with ten of thousands of entries may want to store the result of - // search/filter.. especially if the filtering function is not trivial (e.g. reg-exp). - for (int line_no = 0; line_no < LineOffsets.Size; line_no++) - { - const char* line_start = buf + LineOffsets[line_no]; - const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end; - if (Filter.PassFilter(line_start, line_end)) - ImGui::TextUnformatted(line_start, line_end); - } - } - else - { - // The simplest and easy way to display the entire buffer: - // ImGui::TextUnformatted(buf_begin, buf_end); - // And it'll just work. TextUnformatted() has specialization for large blob of text and will fast-forward - // to skip non-visible lines. Here we instead demonstrate using the clipper to only process lines that are - // within the visible area. - // If you have tens of thousands of items and their processing cost is non-negligible, coarse clipping them - // on your side is recommended. Using ImGuiListClipper requires - // - A) random access into your data - // - B) items all being the same height, - // both of which we can handle since we an array pointing to the beginning of each line of text. - // When using the filter (in the block of code above) we don't have random access into the data to display - // anymore, which is why we don't use the clipper. Storing or skimming through the search result would make - // it possible (and would be recommended if you want to search through tens of thousands of entries). - ImGuiListClipper clipper; - clipper.Begin(LineOffsets.Size); - while (clipper.Step()) - { - for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++) - { - const char* line_start = buf + LineOffsets[line_no]; - const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end; - ImGui::TextUnformatted(line_start, line_end); - } - } - clipper.End(); - } - ImGui::PopStyleVar(); - - if (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) - ImGui::SetScrollHereY(1.0f); - - ImGui::EndChild(); - ImGui::End(); - } -}; - -// Demonstrate creating a simple log window with basic filtering. -static void ShowExampleAppLog(bool* p_open) -{ - static ExampleAppLog log; - - // For the demo: add a debug button _BEFORE_ the normal log window contents - // We take advantage of a rarely used feature: multiple calls to Begin()/End() are appending to the _same_ window. - // Most of the contents of the window will be added by the log.Draw() call. - ImGui::SetNextWindowSize(ImVec2(500, 400), ImGuiCond_FirstUseEver); - ImGui::Begin("Example: Log", p_open); - if (ImGui::SmallButton("[Debug] Add 5 entries")) - { - static int counter = 0; - const char* categories[3] = { "info", "warn", "error" }; - const char* words[] = { "Bumfuzzled", "Cattywampus", "Snickersnee", "Abibliophobia", "Absquatulate", "Nincompoop", "Pauciloquent" }; - for (int n = 0; n < 5; n++) - { - const char* category = categories[counter % IM_ARRAYSIZE(categories)]; - const char* word = words[counter % IM_ARRAYSIZE(words)]; - log.AddLog("[%05d] [%s] Hello, current time is %.1f, here's a word: '%s'\n", - ImGui::GetFrameCount(), category, ImGui::GetTime(), word); - counter++; - } - } - ImGui::End(); - - // Actually call in the regular Log helper (which will Begin() into the same window as we just did) - log.Draw("Example: Log", p_open); -} - -//----------------------------------------------------------------------------- -// [SECTION] Example App: Simple Layout / ShowExampleAppLayout() -//----------------------------------------------------------------------------- - -// Demonstrate create a window with multiple child windows. -static void ShowExampleAppLayout(bool* p_open) -{ - ImGui::SetNextWindowSize(ImVec2(500, 440), ImGuiCond_FirstUseEver); - if (ImGui::Begin("Example: Simple layout", p_open, ImGuiWindowFlags_MenuBar)) - { - if (ImGui::BeginMenuBar()) - { - if (ImGui::BeginMenu("File")) - { - if (ImGui::MenuItem("Close")) *p_open = false; - ImGui::EndMenu(); - } - ImGui::EndMenuBar(); - } - - // Left - static int selected = 0; - { - ImGui::BeginChild("left pane", ImVec2(150, 0), true); - for (int i = 0; i < 100; i++) - { - char label[128]; - sprintf(label, "MyObject %d", i); - if (ImGui::Selectable(label, selected == i)) - selected = i; - } - ImGui::EndChild(); - } - ImGui::SameLine(); - - // Right - { - ImGui::BeginGroup(); - ImGui::BeginChild("item view", ImVec2(0, -ImGui::GetFrameHeightWithSpacing())); // Leave room for 1 line below us - ImGui::Text("MyObject: %d", selected); - ImGui::Separator(); - if (ImGui::BeginTabBar("##Tabs", ImGuiTabBarFlags_None)) - { - if (ImGui::BeginTabItem("Description")) - { - ImGui::TextWrapped("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("Details")) - { - ImGui::Text("ID: 0123456789"); - ImGui::EndTabItem(); - } - ImGui::EndTabBar(); - } - ImGui::EndChild(); - if (ImGui::Button("Revert")) {} - ImGui::SameLine(); - if (ImGui::Button("Save")) {} - ImGui::EndGroup(); - } - } - ImGui::End(); -} - -//----------------------------------------------------------------------------- -// [SECTION] Example App: Property Editor / ShowExampleAppPropertyEditor() -//----------------------------------------------------------------------------- - -static void ShowPlaceholderObject(const char* prefix, int uid) -{ - // Use object uid as identifier. Most commonly you could also use the object pointer as a base ID. - ImGui::PushID(uid); - - // Text and Tree nodes are less high than framed widgets, using AlignTextToFramePadding() we add vertical spacing to make the tree lines equal high. - ImGui::AlignTextToFramePadding(); - bool node_open = ImGui::TreeNode("Object", "%s_%u", prefix, uid); - ImGui::NextColumn(); - ImGui::AlignTextToFramePadding(); - ImGui::Text("my sailor is rich"); - ImGui::NextColumn(); - if (node_open) - { - static float placeholder_members[8] = { 0.0f, 0.0f, 1.0f, 3.1416f, 100.0f, 999.0f }; - for (int i = 0; i < 8; i++) - { - ImGui::PushID(i); // Use field index as identifier. - if (i < 2) - { - ShowPlaceholderObject("Child", 424242); - } - else - { - // Here we use a TreeNode to highlight on hover (we could use e.g. Selectable as well) - ImGui::AlignTextToFramePadding(); - ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_Bullet; - ImGui::TreeNodeEx("Field", flags, "Field_%d", i); - ImGui::NextColumn(); - ImGui::SetNextItemWidth(-1); - if (i >= 5) - ImGui::InputFloat("##value", &placeholder_members[i], 1.0f); - else - ImGui::DragFloat("##value", &placeholder_members[i], 0.01f); - ImGui::NextColumn(); - } - ImGui::PopID(); - } - ImGui::TreePop(); - } - ImGui::PopID(); -} - -// Demonstrate create a simple property editor. -static void ShowExampleAppPropertyEditor(bool* p_open) -{ - ImGui::SetNextWindowSize(ImVec2(430, 450), ImGuiCond_FirstUseEver); - if (!ImGui::Begin("Example: Property editor", p_open)) - { - ImGui::End(); - return; - } - - HelpMarker( - "This example shows how you may implement a property editor using two columns.\n" - "All objects/fields data are dummies here.\n" - "Remember that in many simple cases, you can use ImGui::SameLine(xxx) to position\n" - "your cursor horizontally instead of using the Columns() API."); - - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 2)); - ImGui::Columns(2); - ImGui::Separator(); - - // Iterate placeholder objects (all the same data) - for (int obj_i = 0; obj_i < 3; obj_i++) - ShowPlaceholderObject("Object", obj_i); - - ImGui::Columns(1); - ImGui::Separator(); - ImGui::PopStyleVar(); - ImGui::End(); -} - -//----------------------------------------------------------------------------- -// [SECTION] Example App: Long Text / ShowExampleAppLongText() -//----------------------------------------------------------------------------- - -// Demonstrate/test rendering huge amount of text, and the incidence of clipping. -static void ShowExampleAppLongText(bool* p_open) -{ - ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); - if (!ImGui::Begin("Example: Long text display", p_open)) - { - ImGui::End(); - return; - } - - static int test_type = 0; - static ImGuiTextBuffer log; - static int lines = 0; - ImGui::Text("Printing unusually long amount of text."); - ImGui::Combo("Test type", &test_type, - "Single call to TextUnformatted()\0" - "Multiple calls to Text(), clipped\0" - "Multiple calls to Text(), not clipped (slow)\0"); - ImGui::Text("Buffer contents: %d lines, %d bytes", lines, log.size()); - if (ImGui::Button("Clear")) { log.clear(); lines = 0; } - ImGui::SameLine(); - if (ImGui::Button("Add 1000 lines")) - { - for (int i = 0; i < 1000; i++) - log.appendf("%i The quick brown fox jumps over the lazy dog\n", lines + i); - lines += 1000; - } - ImGui::BeginChild("Log"); - switch (test_type) - { - case 0: - // Single call to TextUnformatted() with a big buffer - ImGui::TextUnformatted(log.begin(), log.end()); - break; - case 1: - { - // Multiple calls to Text(), manually coarsely clipped - demonstrate how to use the ImGuiListClipper helper. - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); - ImGuiListClipper clipper; - clipper.Begin(lines); - while (clipper.Step()) - for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) - ImGui::Text("%i The quick brown fox jumps over the lazy dog", i); - ImGui::PopStyleVar(); - break; - } - case 2: - // Multiple calls to Text(), not clipped (slow) - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); - for (int i = 0; i < lines; i++) - ImGui::Text("%i The quick brown fox jumps over the lazy dog", i); - ImGui::PopStyleVar(); - break; - } - ImGui::EndChild(); - ImGui::End(); -} - -//----------------------------------------------------------------------------- -// [SECTION] Example App: Auto Resize / ShowExampleAppAutoResize() -//----------------------------------------------------------------------------- - -// Demonstrate creating a window which gets auto-resized according to its content. -static void ShowExampleAppAutoResize(bool* p_open) -{ - if (!ImGui::Begin("Example: Auto-resizing window", p_open, ImGuiWindowFlags_AlwaysAutoResize)) - { - ImGui::End(); - return; - } - - static int lines = 10; - ImGui::TextUnformatted( - "Window will resize every-frame to the size of its content.\n" - "Note that you probably don't want to query the window size to\n" - "output your content because that would create a feedback loop."); - ImGui::SliderInt("Number of lines", &lines, 1, 20); - for (int i = 0; i < lines; i++) - ImGui::Text("%*sThis is line %d", i * 4, "", i); // Pad with space to extend size horizontally - ImGui::End(); -} - -//----------------------------------------------------------------------------- -// [SECTION] Example App: Constrained Resize / ShowExampleAppConstrainedResize() -//----------------------------------------------------------------------------- - -// Demonstrate creating a window with custom resize constraints. -static void ShowExampleAppConstrainedResize(bool* p_open) -{ - struct CustomConstraints - { - // Helper functions to demonstrate programmatic constraints - static void Square(ImGuiSizeCallbackData* data) { data->DesiredSize.x = data->DesiredSize.y = IM_MAX(data->DesiredSize.x, data->DesiredSize.y); } - static void Step(ImGuiSizeCallbackData* data) { float step = (float)(int)(intptr_t)data->UserData; data->DesiredSize = ImVec2((int)(data->DesiredSize.x / step + 0.5f) * step, (int)(data->DesiredSize.y / step + 0.5f) * step); } - }; - - const char* test_desc[] = - { - "Resize vertical only", - "Resize horizontal only", - "Width > 100, Height > 100", - "Width 400-500", - "Height 400-500", - "Custom: Always Square", - "Custom: Fixed Steps (100)", - }; - - static bool auto_resize = false; - static int type = 0; - static int display_lines = 10; - if (type == 0) ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 0), ImVec2(-1, FLT_MAX)); // Vertical only - if (type == 1) ImGui::SetNextWindowSizeConstraints(ImVec2(0, -1), ImVec2(FLT_MAX, -1)); // Horizontal only - if (type == 2) ImGui::SetNextWindowSizeConstraints(ImVec2(100, 100), ImVec2(FLT_MAX, FLT_MAX)); // Width > 100, Height > 100 - if (type == 3) ImGui::SetNextWindowSizeConstraints(ImVec2(400, -1), ImVec2(500, -1)); // Width 400-500 - if (type == 4) ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 400), ImVec2(-1, 500)); // Height 400-500 - if (type == 5) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Square); // Always Square - if (type == 6) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Step, (void*)(intptr_t)100); // Fixed Step - - ImGuiWindowFlags flags = auto_resize ? ImGuiWindowFlags_AlwaysAutoResize : 0; - if (ImGui::Begin("Example: Constrained Resize", p_open, flags)) - { - if (ImGui::IsWindowDocked()) - ImGui::Text("Warning: Sizing Constraints won't work if the window is docked!"); - if (ImGui::Button("200x200")) { ImGui::SetWindowSize(ImVec2(200, 200)); } ImGui::SameLine(); - if (ImGui::Button("500x500")) { ImGui::SetWindowSize(ImVec2(500, 500)); } ImGui::SameLine(); - if (ImGui::Button("800x200")) { ImGui::SetWindowSize(ImVec2(800, 200)); } - ImGui::SetNextItemWidth(200); - ImGui::Combo("Constraint", &type, test_desc, IM_ARRAYSIZE(test_desc)); - ImGui::SetNextItemWidth(200); - ImGui::DragInt("Lines", &display_lines, 0.2f, 1, 100); - ImGui::Checkbox("Auto-resize", &auto_resize); - for (int i = 0; i < display_lines; i++) - ImGui::Text("%*sHello, sailor! Making this line long enough for the example.", i * 4, ""); - } - ImGui::End(); -} - -//----------------------------------------------------------------------------- -// [SECTION] Example App: Simple Overlay / ShowExampleAppSimpleOverlay() -//----------------------------------------------------------------------------- - -// Demonstrate creating a simple static window with no decoration -// + a context-menu to choose which corner of the screen to use. -static void ShowExampleAppSimpleOverlay(bool* p_open) -{ - // FIXME-VIEWPORT: Select a default viewport - const float DISTANCE = 10.0f; - static int corner = 0; - ImGuiIO& io = ImGui::GetIO(); - ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav; - if (corner != -1) - { - window_flags |= ImGuiWindowFlags_NoMove; - ImGuiViewport* viewport = ImGui::GetMainViewport(); - ImVec2 work_area_pos = viewport->GetWorkPos(); // Instead of using viewport->Pos we use GetWorkPos() to avoid menu bars, if any! - ImVec2 work_area_size = viewport->GetWorkSize(); - ImVec2 window_pos = ImVec2((corner & 1) ? (work_area_pos.x + work_area_size.x - DISTANCE) : (work_area_pos.x + DISTANCE), (corner & 2) ? (work_area_pos.y + work_area_size.y - DISTANCE) : (work_area_pos.y + DISTANCE)); - ImVec2 window_pos_pivot = ImVec2((corner & 1) ? 1.0f : 0.0f, (corner & 2) ? 1.0f : 0.0f); - ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot); - ImGui::SetNextWindowViewport(viewport->ID); - } - ImGui::SetNextWindowBgAlpha(0.35f); // Transparent background - if (ImGui::Begin("Example: Simple overlay", p_open, window_flags)) - { - ImGui::Text("Simple overlay\n" "in the corner of the screen.\n" "(right-click to change position)"); - ImGui::Separator(); - if (ImGui::IsMousePosValid()) - ImGui::Text("Mouse Position: (%.1f,%.1f)", io.MousePos.x, io.MousePos.y); - else - ImGui::Text("Mouse Position: "); - if (ImGui::BeginPopupContextWindow()) - { - if (ImGui::MenuItem("Custom", NULL, corner == -1)) corner = -1; - if (ImGui::MenuItem("Top-left", NULL, corner == 0)) corner = 0; - if (ImGui::MenuItem("Top-right", NULL, corner == 1)) corner = 1; - if (ImGui::MenuItem("Bottom-left", NULL, corner == 2)) corner = 2; - if (ImGui::MenuItem("Bottom-right", NULL, corner == 3)) corner = 3; - if (p_open && ImGui::MenuItem("Close")) *p_open = false; - ImGui::EndPopup(); - } - } - ImGui::End(); -} - -//----------------------------------------------------------------------------- -// [SECTION] Example App: Manipulating Window Titles / ShowExampleAppWindowTitles() -//----------------------------------------------------------------------------- - -// Demonstrate using "##" and "###" in identifiers to manipulate ID generation. -// This apply to all regular items as well. -// Read FAQ section "How can I have multiple widgets with the same label?" for details. -static void ShowExampleAppWindowTitles(bool*) -{ - // By default, Windows are uniquely identified by their title. - // You can use the "##" and "###" markers to manipulate the display/ID. - - // Using "##" to display same title but have unique identifier. - ImGui::SetNextWindowPos(ImVec2(100, 100), ImGuiCond_FirstUseEver); - ImGui::Begin("Same title as another window##1"); - ImGui::Text("This is window 1.\nMy title is the same as window 2, but my identifier is unique."); - ImGui::End(); - - ImGui::SetNextWindowPos(ImVec2(100, 200), ImGuiCond_FirstUseEver); - ImGui::Begin("Same title as another window##2"); - ImGui::Text("This is window 2.\nMy title is the same as window 1, but my identifier is unique."); - ImGui::End(); - - // Using "###" to display a changing title but keep a static identifier "AnimatedTitle" - char buf[128]; - sprintf(buf, "Animated title %c %d###AnimatedTitle", "|/-\\"[(int)(ImGui::GetTime() / 0.25f) & 3], ImGui::GetFrameCount()); - ImGui::SetNextWindowPos(ImVec2(100, 300), ImGuiCond_FirstUseEver); - ImGui::Begin(buf); - ImGui::Text("This window has a changing title."); - ImGui::End(); -} - -//----------------------------------------------------------------------------- -// [SECTION] Example App: Custom Rendering using ImDrawList API / ShowExampleAppCustomRendering() -//----------------------------------------------------------------------------- - -// Demonstrate using the low-level ImDrawList to draw custom shapes. -static void ShowExampleAppCustomRendering(bool* p_open) -{ - if (!ImGui::Begin("Example: Custom rendering", p_open)) - { - ImGui::End(); - return; - } - - // Tip: If you do a lot of custom rendering, you probably want to use your own geometrical types and benefit of - // overloaded operators, etc. Define IM_VEC2_CLASS_EXTRA in imconfig.h to create implicit conversions between your - // types and ImVec2/ImVec4. Dear ImGui defines overloaded operators but they are internal to imgui.cpp and not - // exposed outside (to avoid messing with your types) In this example we are not using the maths operators! - - if (ImGui::BeginTabBar("##TabBar")) - { - if (ImGui::BeginTabItem("Primitives")) - { - ImGui::PushItemWidth(-ImGui::GetFontSize() * 10); - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - - // Draw gradients - // (note that those are currently exacerbating our sRGB/Linear issues) - // Calling ImGui::GetColorU32() multiplies the given colors by the current Style Alpha, but you may pass the IM_COL32() directly as well.. - ImGui::Text("Gradients"); - ImVec2 gradient_size = ImVec2(ImGui::CalcItemWidth(), ImGui::GetFrameHeight()); - { - ImVec2 p0 = ImGui::GetCursorScreenPos(); - ImVec2 p1 = ImVec2(p0.x + gradient_size.x, p0.y + gradient_size.y); - ImU32 col_a = ImGui::GetColorU32(IM_COL32(0, 0, 0, 255)); - ImU32 col_b = ImGui::GetColorU32(IM_COL32(255, 255, 255, 255)); - draw_list->AddRectFilledMultiColor(p0, p1, col_a, col_b, col_b, col_a); - ImGui::InvisibleButton("##gradient1", gradient_size); - } - { - ImVec2 p0 = ImGui::GetCursorScreenPos(); - ImVec2 p1 = ImVec2(p0.x + gradient_size.x, p0.y + gradient_size.y); - ImU32 col_a = ImGui::GetColorU32(IM_COL32(0, 255, 0, 255)); - ImU32 col_b = ImGui::GetColorU32(IM_COL32(255, 0, 0, 255)); - draw_list->AddRectFilledMultiColor(p0, p1, col_a, col_b, col_b, col_a); - ImGui::InvisibleButton("##gradient2", gradient_size); - } - - // Draw a bunch of primitives - ImGui::Text("All primitives"); - static float sz = 36.0f; - static float thickness = 3.0f; - static int ngon_sides = 6; - static bool circle_segments_override = false; - static int circle_segments_override_v = 12; - static ImVec4 colf = ImVec4(1.0f, 1.0f, 0.4f, 1.0f); - ImGui::DragFloat("Size", &sz, 0.2f, 2.0f, 72.0f, "%.0f"); - ImGui::DragFloat("Thickness", &thickness, 0.05f, 1.0f, 8.0f, "%.02f"); - ImGui::SliderInt("N-gon sides", &ngon_sides, 3, 12); - ImGui::Checkbox("##circlesegmentoverride", &circle_segments_override); - ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); - if (ImGui::SliderInt("Circle segments", &circle_segments_override_v, 3, 40)) - circle_segments_override = true; - ImGui::ColorEdit4("Color", &colf.x); - - const ImVec2 p = ImGui::GetCursorScreenPos(); - const ImU32 col = ImColor(colf); - const float spacing = 10.0f; - const ImDrawCornerFlags corners_none = 0; - const ImDrawCornerFlags corners_all = ImDrawCornerFlags_All; - const ImDrawCornerFlags corners_tl_br = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_BotRight; - const int circle_segments = circle_segments_override ? circle_segments_override_v : 0; - float x = p.x + 4.0f; - float y = p.y + 4.0f; - for (int n = 0; n < 2; n++) - { - // First line uses a thickness of 1.0f, second line uses the configurable thickness - float th = (n == 0) ? 1.0f : thickness; - draw_list->AddNgon(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, ngon_sides, th); x += sz + spacing; // N-gon - draw_list->AddCircle(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, circle_segments, th); x += sz + spacing; // Circle - draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 0.0f, corners_none, th); x += sz + spacing; // Square - draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 10.0f, corners_all, th); x += sz + spacing; // Square with all rounded corners - draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 10.0f, corners_tl_br, th); x += sz + spacing; // Square with two rounded corners - draw_list->AddTriangle(ImVec2(x+sz*0.5f,y), ImVec2(x+sz, y+sz-0.5f), ImVec2(x, y+sz-0.5f), col, th);x += sz + spacing; // Triangle - //draw_list->AddTriangle(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col, th);x+= sz*0.4f + spacing; // Thin triangle - draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y), col, th); x += sz + spacing; // Horizontal line (note: drawing a filled rectangle will be faster!) - draw_list->AddLine(ImVec2(x, y), ImVec2(x, y + sz), col, th); x += spacing; // Vertical line (note: drawing a filled rectangle will be faster!) - draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y + sz), col, th); x += sz + spacing; // Diagonal line - draw_list->AddBezierCurve(ImVec2(x, y), ImVec2(x + sz*1.3f, y + sz*0.3f), ImVec2(x + sz - sz*1.3f, y + sz - sz*0.3f), ImVec2(x + sz, y + sz), col, th); - x = p.x + 4; - y += sz + spacing; - } - draw_list->AddNgonFilled(ImVec2(x + sz * 0.5f, y + sz * 0.5f), sz*0.5f, col, ngon_sides); x += sz + spacing; // N-gon - draw_list->AddCircleFilled(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, circle_segments); x += sz + spacing; // Circle - draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + sz), col); x += sz + spacing; // Square - draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 10.0f); x += sz + spacing; // Square with all rounded corners - draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 10.0f, corners_tl_br); x += sz + spacing; // Square with two rounded corners - draw_list->AddTriangleFilled(ImVec2(x+sz*0.5f,y), ImVec2(x+sz, y+sz-0.5f), ImVec2(x, y+sz-0.5f), col); x += sz + spacing; // Triangle - //draw_list->AddTriangleFilled(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col); x += sz*0.4f + spacing; // Thin triangle - draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + thickness), col); x += sz + spacing; // Horizontal line (faster than AddLine, but only handle integer thickness) - draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + thickness, y + sz), col); x += spacing * 2.0f;// Vertical line (faster than AddLine, but only handle integer thickness) - draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + 1, y + 1), col); x += sz; // Pixel (faster than AddLine) - draw_list->AddRectFilledMultiColor(ImVec2(x, y), ImVec2(x + sz, y + sz), IM_COL32(0, 0, 0, 255), IM_COL32(255, 0, 0, 255), IM_COL32(255, 255, 0, 255), IM_COL32(0, 255, 0, 255)); - - ImGui::Dummy(ImVec2((sz + spacing) * 8.8f, (sz + spacing) * 3.0f)); - ImGui::PopItemWidth(); - ImGui::EndTabItem(); - } - - if (ImGui::BeginTabItem("Canvas")) - { - static ImVector points; - static ImVec2 scrolling(0.0f, 0.0f); - static bool opt_enable_grid = true; - static bool opt_enable_context_menu = true; - static bool adding_line = false; - - ImGui::Checkbox("Enable grid", &opt_enable_grid); - ImGui::Checkbox("Enable context menu", &opt_enable_context_menu); - ImGui::Text("Mouse Left: drag to add lines,\nMouse Right: drag to scroll, click for context menu."); - - // Typically you would use a BeginChild()/EndChild() pair to benefit from a clipping region + own scrolling. - // Here we demonstrate that this can be replaced by simple offsetting + custom drawing + PushClipRect/PopClipRect() calls. - // To use a child window instead we could use, e.g: - // ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); // Disable padding - // ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(50, 50, 50, 255)); // Set a background color - // ImGui::BeginChild("canvas", ImVec2(0.0f, 0.0f), true, ImGuiWindowFlags_NoMove); - // ImGui::PopStyleColor(); - // ImGui::PopStyleVar(); - // [...] - // ImGui::EndChild(); - - // Using InvisibleButton() as a convenience 1) it will advance the layout cursor and 2) allows us to use IsItemHovered()/IsItemActive() - ImVec2 canvas_p0 = ImGui::GetCursorScreenPos(); // ImDrawList API uses screen coordinates! - ImVec2 canvas_sz = ImGui::GetContentRegionAvail(); // Resize canvas to what's available - if (canvas_sz.x < 50.0f) canvas_sz.x = 50.0f; - if (canvas_sz.y < 50.0f) canvas_sz.y = 50.0f; - ImVec2 canvas_p1 = ImVec2(canvas_p0.x + canvas_sz.x, canvas_p0.y + canvas_sz.y); - - // Draw border and background color - ImGuiIO& io = ImGui::GetIO(); - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - draw_list->AddRectFilled(canvas_p0, canvas_p1, IM_COL32(50, 50, 50, 255)); - draw_list->AddRect(canvas_p0, canvas_p1, IM_COL32(255, 255, 255, 255)); - - // This will catch our interactions - ImGui::InvisibleButton("canvas", canvas_sz, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); - const bool is_hovered = ImGui::IsItemHovered(); // Hovered - const bool is_active = ImGui::IsItemActive(); // Held - const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y); // Lock scrolled origin - const ImVec2 mouse_pos_in_canvas(io.MousePos.x - origin.x, io.MousePos.y - origin.y); - - // Add first and second point - if (is_hovered && !adding_line && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) - { - points.push_back(mouse_pos_in_canvas); - points.push_back(mouse_pos_in_canvas); - adding_line = true; - } - if (adding_line) - { - points.back() = mouse_pos_in_canvas; - if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) - adding_line = false; - } - - // Pan (we use a zero mouse threshold when there's no context menu) - // You may decide to make that threshold dynamic based on whether the mouse is hovering something etc. - const float mouse_threshold_for_pan = opt_enable_context_menu ? -1.0f : 0.0f; - if (is_active && ImGui::IsMouseDragging(ImGuiMouseButton_Right, mouse_threshold_for_pan)) - { - scrolling.x += io.MouseDelta.x; - scrolling.y += io.MouseDelta.y; - } - - // Context menu (under default mouse threshold) - ImVec2 drag_delta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Right); - if (opt_enable_context_menu && ImGui::IsMouseReleased(ImGuiMouseButton_Right) && drag_delta.x == 0.0f && drag_delta.y == 0.0f) - ImGui::OpenPopupOnItemClick("context"); - if (ImGui::BeginPopup("context")) - { - if (adding_line) - points.resize(points.size() - 2); - adding_line = false; - if (ImGui::MenuItem("Remove one", NULL, false, points.Size > 0)) { points.resize(points.size() - 2); } - if (ImGui::MenuItem("Remove all", NULL, false, points.Size > 0)) { points.clear(); } - ImGui::EndPopup(); - } - - // Draw grid + all lines in the canvas - draw_list->PushClipRect(canvas_p0, canvas_p1, true); - if (opt_enable_grid) - { - const float GRID_STEP = 64.0f; - for (float x = fmodf(scrolling.x, GRID_STEP); x < canvas_sz.x; x += GRID_STEP) - draw_list->AddLine(ImVec2(canvas_p0.x + x, canvas_p0.y), ImVec2(canvas_p0.x + x, canvas_p1.y), IM_COL32(200, 200, 200, 40)); - for (float y = fmodf(scrolling.y, GRID_STEP); y < canvas_sz.y; y += GRID_STEP) - draw_list->AddLine(ImVec2(canvas_p0.x, canvas_p0.y + y), ImVec2(canvas_p1.x, canvas_p0.y + y), IM_COL32(200, 200, 200, 40)); - } - for (int n = 0; n < points.Size; n += 2) - draw_list->AddLine(ImVec2(origin.x + points[n].x, origin.y + points[n].y), ImVec2(origin.x + points[n + 1].x, origin.y + points[n + 1].y), IM_COL32(255, 255, 0, 255), 2.0f); - draw_list->PopClipRect(); - - ImGui::EndTabItem(); - } - - if (ImGui::BeginTabItem("BG/FG draw lists")) - { - static bool draw_bg = true; - static bool draw_fg = true; - ImGui::Checkbox("Draw in Background draw list", &draw_bg); - ImGui::SameLine(); HelpMarker("The Background draw list will be rendered below every Dear ImGui windows."); - ImGui::Checkbox("Draw in Foreground draw list", &draw_fg); - ImGui::SameLine(); HelpMarker("The Foreground draw list will be rendered over every Dear ImGui windows."); - ImVec2 window_pos = ImGui::GetWindowPos(); - ImVec2 window_size = ImGui::GetWindowSize(); - ImVec2 window_center = ImVec2(window_pos.x + window_size.x * 0.5f, window_pos.y + window_size.y * 0.5f); - if (draw_bg) - ImGui::GetBackgroundDrawList()->AddCircle(window_center, window_size.x * 0.6f, IM_COL32(255, 0, 0, 200), 0, 10 + 4); - if (draw_fg) - ImGui::GetForegroundDrawList()->AddCircle(window_center, window_size.y * 0.6f, IM_COL32(0, 255, 0, 200), 0, 10); - ImGui::EndTabItem(); - } - - ImGui::EndTabBar(); - } - - ImGui::End(); -} - -//----------------------------------------------------------------------------- -// [SECTION] Example App: Docking, DockSpace / ShowExampleAppDockSpace() -//----------------------------------------------------------------------------- - -// Demonstrate using DockSpace() to create an explicit docking node within an existing window. -// Note that you dock windows into each others _without_ a dockspace, by just clicking on -// a window title bar and moving it (+ hold SHIFT if io.ConfigDockingWithShift is set). -// DockSpace() and DockSpaceOverViewport() are only useful to construct a central docking -// location for your application. -void ShowExampleAppDockSpace(bool* p_open) -{ - // In 99% case you should be able to just call DockSpaceOverViewport() and ignore all the code below! - // In this specific demo, we are not using DockSpaceOverViewport() because: - // - we allow the host window to be floating/moveable instead of filling the viewport (when opt_fullscreen == false) - // - we allow the host window to have padding (when opt_padding == true) - // - we have a local menu bar in the host window (vs. you could use BeginMainMenuBar() + DockSpaceOverViewport() in your code!) - // TL;DR; this demo is more complicated than what you would normally use. - // If we removed all the options we are showcasing, this demo would become: - // void ShowExampleAppDockSpace() - // { - // ImGui::DockSpaceOverViewport(ImGui::GetMainViewport()); - // } - - static bool opt_fullscreen = true; - static bool opt_padding = false; - static ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_None; - - // We are using the ImGuiWindowFlags_NoDocking flag to make the parent window not dockable into, - // because it would be confusing to have two docking targets within each others. - ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking; - if (opt_fullscreen) - { - ImGuiViewport* viewport = ImGui::GetMainViewport(); - ImGui::SetNextWindowPos(viewport->GetWorkPos()); - ImGui::SetNextWindowSize(viewport->GetWorkSize()); - ImGui::SetNextWindowViewport(viewport->ID); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); - window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove; - window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; - } - else - { - dockspace_flags &= ~ImGuiDockNodeFlags_PassthruCentralNode; - } - - // When using ImGuiDockNodeFlags_PassthruCentralNode, DockSpace() will render our background - // and handle the pass-thru hole, so we ask Begin() to not render a background. - if (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode) - window_flags |= ImGuiWindowFlags_NoBackground; - - // Important: note that we proceed even if Begin() returns false (aka window is collapsed). - // This is because we want to keep our DockSpace() active. If a DockSpace() is inactive, - // all active windows docked into it will lose their parent and become undocked. - // We cannot preserve the docking relationship between an active window and an inactive docking, otherwise - // any change of dockspace/settings would lead to windows being stuck in limbo and never being visible. - if (!opt_padding) - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); - ImGui::Begin("DockSpace Demo", p_open, window_flags); - if (!opt_padding) - ImGui::PopStyleVar(); - - if (opt_fullscreen) - ImGui::PopStyleVar(2); - - // DockSpace - ImGuiIO& io = ImGui::GetIO(); - if (io.ConfigFlags & ImGuiConfigFlags_DockingEnable) - { - ImGuiID dockspace_id = ImGui::GetID("MyDockSpace"); - ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags); - } - else - { - ShowDockingDisabledMessage(); - } - - if (ImGui::BeginMenuBar()) - { - if (ImGui::BeginMenu("Options")) - { - // Disabling fullscreen would allow the window to be moved to the front of other windows, - // which we can't undo at the moment without finer window depth/z control. - ImGui::MenuItem("Fullscreen", NULL, &opt_fullscreen); - ImGui::MenuItem("Padding", NULL, &opt_padding); - ImGui::Separator(); - - if (ImGui::MenuItem("Flag: NoSplit", "", (dockspace_flags & ImGuiDockNodeFlags_NoSplit) != 0)) { dockspace_flags ^= ImGuiDockNodeFlags_NoSplit; } - if (ImGui::MenuItem("Flag: NoResize", "", (dockspace_flags & ImGuiDockNodeFlags_NoResize) != 0)) { dockspace_flags ^= ImGuiDockNodeFlags_NoResize; } - if (ImGui::MenuItem("Flag: NoDockingInCentralNode", "", (dockspace_flags & ImGuiDockNodeFlags_NoDockingInCentralNode) != 0)) { dockspace_flags ^= ImGuiDockNodeFlags_NoDockingInCentralNode; } - if (ImGui::MenuItem("Flag: AutoHideTabBar", "", (dockspace_flags & ImGuiDockNodeFlags_AutoHideTabBar) != 0)) { dockspace_flags ^= ImGuiDockNodeFlags_AutoHideTabBar; } - if (ImGui::MenuItem("Flag: PassthruCentralNode", "", (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode) != 0, opt_fullscreen)) { dockspace_flags ^= ImGuiDockNodeFlags_PassthruCentralNode; } - ImGui::Separator(); - - if (ImGui::MenuItem("Close", NULL, false, p_open != NULL)) - *p_open = false; - ImGui::EndMenu(); - } - HelpMarker( - "When docking is enabled, you can ALWAYS dock MOST window into another! Try it now!" "\n\n" - " > if io.ConfigDockingWithShift==false (default):" "\n" - " drag windows from title bar to dock" "\n" - " > if io.ConfigDockingWithShift==true:" "\n" - " drag windows from anywhere and hold Shift to dock" "\n\n" - "This demo app has nothing to do with it!" "\n\n" - "This demo app only demonstrate the use of ImGui::DockSpace() which allows you to manually create a docking node _within_ another window. This is useful so you can decorate your main application window (e.g. with a menu bar)." "\n\n" - "ImGui::DockSpace() comes with one hard constraint: it needs to be submitted _before_ any window which may be docked into it. Therefore, if you use a dock spot as the central point of your application, you'll probably want it to be part of the very first window you are submitting to imgui every frame." "\n\n" - "(NB: because of this constraint, the implicit \"Debug\" window can not be docked into an explicit DockSpace() node, because that window is submitted as part of the NewFrame() call. An easy workaround is that you can create your own implicit \"Debug##2\" window after calling DockSpace() and leave it in the window stack for anyone to use.)" - ); - - ImGui::EndMenuBar(); - } - - ImGui::End(); -} - -//----------------------------------------------------------------------------- -// [SECTION] Example App: Documents Handling / ShowExampleAppDocuments() -//----------------------------------------------------------------------------- - -// Simplified structure to mimic a Document model -struct MyDocument -{ - const char* Name; // Document title - bool Open; // Set when open (we keep an array of all available documents to simplify demo code!) - bool OpenPrev; // Copy of Open from last update. - bool Dirty; // Set when the document has been modified - bool WantClose; // Set when the document - ImVec4 Color; // An arbitrary variable associated to the document - - MyDocument(const char* name, bool open = true, const ImVec4& color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f)) - { - Name = name; - Open = OpenPrev = open; - Dirty = false; - WantClose = false; - Color = color; - } - void DoOpen() { Open = true; } - void DoQueueClose() { WantClose = true; } - void DoForceClose() { Open = false; Dirty = false; } - void DoSave() { Dirty = false; } - - // Display placeholder contents for the Document - static void DisplayContents(MyDocument* doc) - { - ImGui::PushID(doc); - ImGui::Text("Document \"%s\"", doc->Name); - ImGui::PushStyleColor(ImGuiCol_Text, doc->Color); - ImGui::TextWrapped("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."); - ImGui::PopStyleColor(); - if (ImGui::Button("Modify", ImVec2(100, 0))) - doc->Dirty = true; - ImGui::SameLine(); - if (ImGui::Button("Save", ImVec2(100, 0))) - doc->DoSave(); - ImGui::ColorEdit3("color", &doc->Color.x); // Useful to test drag and drop and hold-dragged-to-open-tab behavior. - ImGui::PopID(); - } - - // Display context menu for the Document - static void DisplayContextMenu(MyDocument* doc) - { - if (!ImGui::BeginPopupContextItem()) - return; - - char buf[256]; - sprintf(buf, "Save %s", doc->Name); - if (ImGui::MenuItem(buf, "CTRL+S", false, doc->Open)) - doc->DoSave(); - if (ImGui::MenuItem("Close", "CTRL+W", false, doc->Open)) - doc->DoQueueClose(); - ImGui::EndPopup(); - } -}; - -struct ExampleAppDocuments -{ - ImVector Documents; - - ExampleAppDocuments() - { - Documents.push_back(MyDocument("Lettuce", true, ImVec4(0.4f, 0.8f, 0.4f, 1.0f))); - Documents.push_back(MyDocument("Eggplant", true, ImVec4(0.8f, 0.5f, 1.0f, 1.0f))); - Documents.push_back(MyDocument("Carrot", true, ImVec4(1.0f, 0.8f, 0.5f, 1.0f))); - Documents.push_back(MyDocument("Tomato", false, ImVec4(1.0f, 0.3f, 0.4f, 1.0f))); - Documents.push_back(MyDocument("A Rather Long Title", false)); - Documents.push_back(MyDocument("Some Document", false)); - } -}; - -// [Optional] Notify the system of Tabs/Windows closure that happened outside the regular tab interface. -// If a tab has been closed programmatically (aka closed from another source such as the Checkbox() in the demo, -// as opposed to clicking on the regular tab closing button) and stops being submitted, it will take a frame for -// the tab bar to notice its absence. During this frame there will be a gap in the tab bar, and if the tab that has -// disappeared was the selected one, the tab bar will report no selected tab during the frame. This will effectively -// give the impression of a flicker for one frame. -// We call SetTabItemClosed() to manually notify the Tab Bar or Docking system of removed tabs to avoid this glitch. -// Note that this completely optional, and only affect tab bars with the ImGuiTabBarFlags_Reorderable flag. -static void NotifyOfDocumentsClosedElsewhere(ExampleAppDocuments& app) -{ - for (int doc_n = 0; doc_n < app.Documents.Size; doc_n++) - { - MyDocument* doc = &app.Documents[doc_n]; - if (!doc->Open && doc->OpenPrev) - ImGui::SetTabItemClosed(doc->Name); - doc->OpenPrev = doc->Open; - } -} - -void ShowExampleAppDocuments(bool* p_open) -{ - static ExampleAppDocuments app; - - // Options - enum Target - { - Target_None, - Target_Tab, // Create documents as local tab into a local tab bar - Target_DockSpaceAndWindow // Create documents as regular windows, and create an embedded dockspace - }; - static Target opt_target = Target_Tab; - static bool opt_reorderable = true; - static ImGuiTabBarFlags opt_fitting_flags = ImGuiTabBarFlags_FittingPolicyDefault_; - - // When (opt_target == Target_DockSpaceAndWindow) there is the possibily that one of our child Document window (e.g. "Eggplant") - // that we emit gets docked into the same spot as the parent window ("Example: Documents"). - // This would create a problematic feedback loop because selecting the "Eggplant" tab would make the "Example: Documents" tab - // not visible, which in turn would stop submitting the "Eggplant" window. - // We avoid this problem by submitting our documents window even if our parent window is not currently visible. - // Another solution may be to make the "Example: Documents" window use the ImGuiWindowFlags_NoDocking. - - bool window_contents_visible = ImGui::Begin("Example: Documents", p_open, ImGuiWindowFlags_MenuBar); - if (!window_contents_visible && opt_target != Target_DockSpaceAndWindow) - { - ImGui::End(); - return; - } - - // Menu - if (ImGui::BeginMenuBar()) - { - if (ImGui::BeginMenu("File")) - { - int open_count = 0; - for (int doc_n = 0; doc_n < app.Documents.Size; doc_n++) - open_count += app.Documents[doc_n].Open ? 1 : 0; - - if (ImGui::BeginMenu("Open", open_count < app.Documents.Size)) - { - for (int doc_n = 0; doc_n < app.Documents.Size; doc_n++) - { - MyDocument* doc = &app.Documents[doc_n]; - if (!doc->Open) - if (ImGui::MenuItem(doc->Name)) - doc->DoOpen(); - } - ImGui::EndMenu(); - } - if (ImGui::MenuItem("Close All Documents", NULL, false, open_count > 0)) - for (int doc_n = 0; doc_n < app.Documents.Size; doc_n++) - app.Documents[doc_n].DoQueueClose(); - if (ImGui::MenuItem("Exit", "Alt+F4")) {} - ImGui::EndMenu(); - } - ImGui::EndMenuBar(); - } - - // [Debug] List documents with one checkbox for each - for (int doc_n = 0; doc_n < app.Documents.Size; doc_n++) - { - MyDocument* doc = &app.Documents[doc_n]; - if (doc_n > 0) - ImGui::SameLine(); - ImGui::PushID(doc); - if (ImGui::Checkbox(doc->Name, &doc->Open)) - if (!doc->Open) - doc->DoForceClose(); - ImGui::PopID(); - } - ImGui::PushItemWidth(ImGui::GetFontSize() * 12); - ImGui::Combo("Output", (int*)&opt_target, "None\0TabBar+Tabs\0DockSpace+Window\0"); - ImGui::PopItemWidth(); - bool redock_all = false; - if (opt_target == Target_Tab) { ImGui::SameLine(); ImGui::Checkbox("Reorderable Tabs", &opt_reorderable); } - if (opt_target == Target_DockSpaceAndWindow) { ImGui::SameLine(); redock_all = ImGui::Button("Redock all"); } - - ImGui::Separator(); - - // Tabs - if (opt_target == Target_Tab) - { - ImGuiTabBarFlags tab_bar_flags = (opt_fitting_flags) | (opt_reorderable ? ImGuiTabBarFlags_Reorderable : 0); - if (ImGui::BeginTabBar("##tabs", tab_bar_flags)) - { - if (opt_reorderable) - NotifyOfDocumentsClosedElsewhere(app); - - // [DEBUG] Stress tests - //if ((ImGui::GetFrameCount() % 30) == 0) docs[1].Open ^= 1; // [DEBUG] Automatically show/hide a tab. Test various interactions e.g. dragging with this on. - //if (ImGui::GetIO().KeyCtrl) ImGui::SetTabItemSelected(docs[1].Name); // [DEBUG] Test SetTabItemSelected(), probably not very useful as-is anyway.. - - // Submit Tabs - for (int doc_n = 0; doc_n < app.Documents.Size; doc_n++) - { - MyDocument* doc = &app.Documents[doc_n]; - if (!doc->Open) - continue; - - ImGuiTabItemFlags tab_flags = (doc->Dirty ? ImGuiTabItemFlags_UnsavedDocument : 0); - bool visible = ImGui::BeginTabItem(doc->Name, &doc->Open, tab_flags); - - // Cancel attempt to close when unsaved add to save queue so we can display a popup. - if (!doc->Open && doc->Dirty) - { - doc->Open = true; - doc->DoQueueClose(); - } - - MyDocument::DisplayContextMenu(doc); - if (visible) - { - MyDocument::DisplayContents(doc); - ImGui::EndTabItem(); - } - } - - ImGui::EndTabBar(); - } - } - else if (opt_target == Target_DockSpaceAndWindow) - { - if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_DockingEnable) - { - NotifyOfDocumentsClosedElsewhere(app); - - // Create a DockSpace node where any window can be docked - ImGuiID dockspace_id = ImGui::GetID("MyDockSpace"); - ImGui::DockSpace(dockspace_id); - - // Create Windows - for (int doc_n = 0; doc_n < app.Documents.Size; doc_n++) - { - MyDocument* doc = &app.Documents[doc_n]; - if (!doc->Open) - continue; - - ImGui::SetNextWindowDockID(dockspace_id, redock_all ? ImGuiCond_Always : ImGuiCond_FirstUseEver); - ImGuiWindowFlags window_flags = (doc->Dirty ? ImGuiWindowFlags_UnsavedDocument : 0); - bool visible = ImGui::Begin(doc->Name, &doc->Open, window_flags); - - // Cancel attempt to close when unsaved add to save queue so we can display a popup. - if (!doc->Open && doc->Dirty) - { - doc->Open = true; - doc->DoQueueClose(); - } - - MyDocument::DisplayContextMenu(doc); - if (visible) - MyDocument::DisplayContents(doc); - - ImGui::End(); - } - } - else - { - ShowDockingDisabledMessage(); - } - } - - // Early out other contents - if (!window_contents_visible) - { - ImGui::End(); - return; - } - - // Update closing queue - static ImVector close_queue; - if (close_queue.empty()) - { - // Close queue is locked once we started a popup - for (int doc_n = 0; doc_n < app.Documents.Size; doc_n++) - { - MyDocument* doc = &app.Documents[doc_n]; - if (doc->WantClose) - { - doc->WantClose = false; - close_queue.push_back(doc); - } - } - } - - // Display closing confirmation UI - if (!close_queue.empty()) - { - int close_queue_unsaved_documents = 0; - for (int n = 0; n < close_queue.Size; n++) - if (close_queue[n]->Dirty) - close_queue_unsaved_documents++; - - if (close_queue_unsaved_documents == 0) - { - // Close documents when all are unsaved - for (int n = 0; n < close_queue.Size; n++) - close_queue[n]->DoForceClose(); - close_queue.clear(); - } - else - { - if (!ImGui::IsPopupOpen("Save?")) - ImGui::OpenPopup("Save?"); - if (ImGui::BeginPopupModal("Save?")) - { - ImGui::Text("Save change to the following items?"); - ImGui::SetNextItemWidth(-1.0f); - if (ImGui::ListBoxHeader("##", close_queue_unsaved_documents, 6)) - { - for (int n = 0; n < close_queue.Size; n++) - if (close_queue[n]->Dirty) - ImGui::Text("%s", close_queue[n]->Name); - ImGui::ListBoxFooter(); - } - - if (ImGui::Button("Yes", ImVec2(80, 0))) - { - for (int n = 0; n < close_queue.Size; n++) - { - if (close_queue[n]->Dirty) - close_queue[n]->DoSave(); - close_queue[n]->DoForceClose(); - } - close_queue.clear(); - ImGui::CloseCurrentPopup(); - } - ImGui::SameLine(); - if (ImGui::Button("No", ImVec2(80, 0))) - { - for (int n = 0; n < close_queue.Size; n++) - close_queue[n]->DoForceClose(); - close_queue.clear(); - ImGui::CloseCurrentPopup(); - } - ImGui::SameLine(); - if (ImGui::Button("Cancel", ImVec2(80, 0))) - { - close_queue.clear(); - ImGui::CloseCurrentPopup(); - } - ImGui::EndPopup(); - } - } - } - - ImGui::End(); -} - -// End of Demo code -#else - -void ImGui::ShowAboutWindow(bool*) {} -void ImGui::ShowDemoWindow(bool*) {} -void ImGui::ShowUserGuide() {} -void ImGui::ShowStyleEditor(ImGuiStyle*) {} - -#endif - -#endif // #ifndef IMGUI_DISABLE diff --git a/libs/ImGui/source/imgui_draw.cpp b/libs/ImGui/source/imgui_draw.cpp index 48ee2980d..ff8372f8b 100644 --- a/libs/ImGui/source/imgui_draw.cpp +++ b/libs/ImGui/source/imgui_draw.cpp @@ -60,12 +60,16 @@ Index of this file: #if __has_warning("-Wunknown-warning-option") #pragma clang diagnostic ignored "-Wunknown-warning-option" // warning: unknown warning group 'xxx' // not all warnings are known by all Clang versions and they tend to be rename-happy.. so ignoring warnings triggers new warnings on some configuration. Great! #endif +#if __has_warning("-Walloca") +#pragma clang diagnostic ignored "-Walloca" // warning: use of function '__builtin_alloca' is discouraged +#endif #pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' #pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse. #pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants ok. #pragma clang diagnostic ignored "-Wglobal-constructors" // warning: declaration requires a global destructor // similar to above, not sure what the exact difference is. #pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some standard header variations use #define NULL 0 +#pragma clang diagnostic ignored "-Walloca" // warning: use of function '__builtin_alloca' is discouraged #pragma clang diagnostic ignored "-Wcomma" // warning: possible misuse of comma operator here #pragma clang diagnostic ignored "-Wreserved-id-macro" // warning: macro name is a reserved identifier #pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using printf() is a misery with this as C++ va_arg ellipsis changes float to double. @@ -221,6 +225,11 @@ void ImGui::StyleColorsDark(ImGuiStyle* dst) colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); + colors[ImGuiCol_TableHeaderBg] = ImVec4(0.19f, 0.19f, 0.20f, 1.00f); + colors[ImGuiCol_TableBorderStrong] = ImVec4(0.31f, 0.31f, 0.35f, 1.00f); // Prefer using Alpha=1.0 here + colors[ImGuiCol_TableBorderLight] = ImVec4(0.23f, 0.23f, 0.25f, 1.00f); // Prefer using Alpha=1.0 here + colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.07f); colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); @@ -278,6 +287,11 @@ void ImGui::StyleColorsClassic(ImGuiStyle* dst) colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); + colors[ImGuiCol_TableHeaderBg] = ImVec4(0.27f, 0.27f, 0.38f, 1.00f); + colors[ImGuiCol_TableBorderStrong] = ImVec4(0.31f, 0.31f, 0.45f, 1.00f); // Prefer using Alpha=1.0 here + colors[ImGuiCol_TableBorderLight] = ImVec4(0.26f, 0.26f, 0.28f, 1.00f); // Prefer using Alpha=1.0 here + colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.07f); colors[ImGuiCol_TextSelectedBg] = ImVec4(0.00f, 0.00f, 1.00f, 0.35f); colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); colors[ImGuiCol_NavHighlight] = colors[ImGuiCol_HeaderHovered]; @@ -336,6 +350,11 @@ void ImGui::StyleColorsLight(ImGuiStyle* dst) colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.45f, 0.00f, 1.00f); + colors[ImGuiCol_TableHeaderBg] = ImVec4(0.78f, 0.87f, 0.98f, 1.00f); + colors[ImGuiCol_TableBorderStrong] = ImVec4(0.57f, 0.57f, 0.64f, 1.00f); // Prefer using Alpha=1.0 here + colors[ImGuiCol_TableBorderLight] = ImVec4(0.68f, 0.68f, 0.74f, 1.00f); // Prefer using Alpha=1.0 here + colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_TableRowBgAlt] = ImVec4(0.30f, 0.30f, 0.30f, 0.07f); colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); colors[ImGuiCol_DragDropTarget] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); colors[ImGuiCol_NavHighlight] = colors[ImGuiCol_HeaderHovered]; @@ -350,21 +369,12 @@ void ImGui::StyleColorsLight(ImGuiStyle* dst) ImDrawListSharedData::ImDrawListSharedData() { - Font = NULL; - FontSize = 0.0f; - CurveTessellationTol = 0.0f; - CircleSegmentMaxError = 0.0f; - ClipRectFullscreen = ImVec4(-8192.0f, -8192.0f, +8192.0f, +8192.0f); - InitialFlags = ImDrawListFlags_None; - - // Lookup tables + memset(this, 0, sizeof(*this)); for (int i = 0; i < IM_ARRAYSIZE(ArcFastVtx); i++) { const float a = ((float)i * 2 * IM_PI) / (float)IM_ARRAYSIZE(ArcFastVtx); ArcFastVtx[i] = ImVec2(ImCos(a), ImSin(a)); } - memset(CircleSegmentCounts, 0, sizeof(CircleSegmentCounts)); // This will be set by SetCircleSegmentMaxError() - TexUvLines = NULL; } void ImDrawListSharedData::SetCircleSegmentMaxError(float max_error) @@ -1432,10 +1442,14 @@ void ImDrawListSplitter::ClearFreeMemory() void ImDrawListSplitter::Split(ImDrawList* draw_list, int channels_count) { + IM_UNUSED(draw_list); IM_ASSERT(_Current == 0 && _Count <= 1 && "Nested channel splitting is not supported. Please use separate instances of ImDrawListSplitter."); int old_channels_count = _Channels.Size; if (old_channels_count < channels_count) + { + _Channels.reserve(channels_count); // Avoid over reserving since this is likely to stay stable _Channels.resize(channels_count); + } _Count = channels_count; // Channels[] (24/32 bytes each) hold storage that we'll swap with draw_list->_CmdBuffer/_IdxBuffer @@ -1453,12 +1467,6 @@ void ImDrawListSplitter::Split(ImDrawList* draw_list, int channels_count) _Channels[i]._CmdBuffer.resize(0); _Channels[i]._IdxBuffer.resize(0); } - if (_Channels[i]._CmdBuffer.Size == 0) - { - ImDrawCmd draw_cmd; - ImDrawCmd_HeaderCopy(&draw_cmd, &draw_list->_CmdHeader); // Copy ClipRect, TextureId, VtxOffset - _Channels[i]._CmdBuffer.push_back(draw_cmd); - } } } @@ -1548,8 +1556,10 @@ void ImDrawListSplitter::SetCurrentChannel(ImDrawList* draw_list, int idx) draw_list->_IdxWritePtr = draw_list->IdxBuffer.Data + draw_list->IdxBuffer.Size; // If current command is used with different settings we need to add a new command - ImDrawCmd* curr_cmd = &draw_list->CmdBuffer.Data[draw_list->CmdBuffer.Size - 1]; - if (curr_cmd->ElemCount == 0) + ImDrawCmd* curr_cmd = (draw_list->CmdBuffer.Size == 0) ? NULL : &draw_list->CmdBuffer.Data[draw_list->CmdBuffer.Size - 1]; + if (curr_cmd == NULL) + draw_list->AddDrawCmd(); + else if (curr_cmd->ElemCount == 0) ImDrawCmd_HeaderCopy(curr_cmd, &draw_list->_CmdHeader); // Copy ClipRect, TextureId, VtxOffset else if (ImDrawCmd_HeaderCompare(curr_cmd, &draw_list->_CmdHeader) != 0) draw_list->AddDrawCmd(); diff --git a/libs/ImGui/source/imgui_widgets.cpp b/libs/ImGui/source/imgui_widgets.cpp index bdff46643..6bc5c69e3 100644 --- a/libs/ImGui/source/imgui_widgets.cpp +++ b/libs/ImGui/source/imgui_widgets.cpp @@ -25,6 +25,7 @@ Index of this file: // [SECTION] Widgets: BeginTabBar, EndTabBar, etc. // [SECTION] Widgets: BeginTabItem, EndTabItem, etc. // [SECTION] Widgets: Columns, BeginColumns, EndColumns, etc. +// [SECTION] Widgets: BeginTable, EndTable, etc. */ @@ -108,6 +109,8 @@ static const ImU64 IM_U64_MAX = ULLONG_MAX; // (0xFFFFFFFFFFFFFFFFull); static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1); #endif +static const float TOOLTIP_DELAY = 0.50f; // Time before slow tooltips appears (FIXME: This is temporary until we merge in tooltip timer+priority work) + //------------------------------------------------------------------------- // [SECTION] Forward Declarations //------------------------------------------------------------------------- @@ -408,6 +411,7 @@ void ImGui::BulletTextV(const char* fmt, va_list args) // - Image() // - ImageButton() // - Checkbox() +// - CheckboxFlagsT() [Internal] // - CheckboxFlags() // - RadioButton() // - ProgressBar() @@ -1082,6 +1086,7 @@ bool ImGui::Checkbox(const char* label, bool* v) if (mixed_value) { // Undocumented tristate/mixed/indeterminate checkbox (#2644) + // This may seem awkwardly designed because the aim is to make ImGuiItemFlags_MixedValue supported by all widgets (not just checkbox) ImVec2 pad(ImMax(1.0f, IM_FLOOR(square_sz / 3.6f)), ImMax(1.0f, IM_FLOOR(square_sz / 3.6f))); window->DrawList->AddRectFilled(check_bb.Min + pad, check_bb.Max - pad, check_col, style.FrameRounding); } @@ -1100,35 +1105,45 @@ bool ImGui::Checkbox(const char* label, bool* v) return pressed; } -bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value) +template +bool ImGui::CheckboxFlagsT(const char* label, T* flags, T flags_value) { - bool v = ((*flags & flags_value) == flags_value); + bool all_on = (*flags & flags_value) == flags_value; + bool any_on = (*flags & flags_value) != 0; bool pressed; - if (v == false && (*flags & flags_value) != 0) + if (!all_on && any_on) { - // Mixed value (FIXME: find a way to expose neatly to Checkbox?) ImGuiWindow* window = GetCurrentWindow(); - const ImGuiItemFlags backup_item_flags = window->DC.ItemFlags; + ImGuiItemFlags backup_item_flags = window->DC.ItemFlags; window->DC.ItemFlags |= ImGuiItemFlags_MixedValue; - pressed = Checkbox(label, &v); + pressed = Checkbox(label, &all_on); window->DC.ItemFlags = backup_item_flags; } else { - // Regular checkbox - pressed = Checkbox(label, &v); + pressed = Checkbox(label, &all_on); + } if (pressed) { - if (v) + if (all_on) *flags |= flags_value; else *flags &= ~flags_value; } - return pressed; } +bool ImGui::CheckboxFlags(const char* label, int* flags, int flags_value) +{ + return CheckboxFlagsT(label, flags, flags_value); +} + +bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value) +{ + return CheckboxFlagsT(label, flags, flags_value); +} + bool ImGui::RadioButton(const char* label, bool active) { ImGuiWindow* window = GetCurrentWindow(); @@ -1341,7 +1356,9 @@ void ImGui::SeparatorEx(ImGuiSeparatorFlags flags) // Horizontal Separator float x1 = window->Pos.x; float x2 = window->Pos.x + window->Size.x; - if (!window->DC.GroupStack.empty()) + + // FIXME-WORKRECT: old hack (#205) until we decide of consistent behavior with WorkRect/Indent and Separator + if (g.GroupStack.Size > 0 && g.GroupStack.back().WindowID == window->ID) x1 += window->DC.Indent.x; ImGuiColumns* columns = (flags & ImGuiSeparatorFlags_SpanAllColumns) ? window->DC.CurrentColumns : NULL; @@ -4268,10 +4285,11 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ IM_ASSERT(callback_data.Buf == state->TextA.Data); // Invalid to modify those fields IM_ASSERT(callback_data.BufSize == state->BufCapacityA); IM_ASSERT(callback_data.Flags == flags); - if (callback_data.CursorPos != utf8_cursor_pos) { state->Stb.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); state->CursorFollow = true; } - if (callback_data.SelectionStart != utf8_selection_start) { state->Stb.select_start = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); } - if (callback_data.SelectionEnd != utf8_selection_end) { state->Stb.select_end = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); } - if (callback_data.BufDirty) + const bool buf_dirty = callback_data.BufDirty; + if (callback_data.CursorPos != utf8_cursor_pos || buf_dirty) { state->Stb.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); state->CursorFollow = true; } + if (callback_data.SelectionStart != utf8_selection_start || buf_dirty) { state->Stb.select_start = (callback_data.SelectionStart == callback_data.CursorPos) ? state->Stb.cursor : ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); } + if (callback_data.SelectionEnd != utf8_selection_end || buf_dirty) { state->Stb.select_end = (callback_data.SelectionEnd == callback_data.SelectionStart) ? state->Stb.select_start : ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); } + if (buf_dirty) { IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text! if (callback_data.BufTextLen > backup_current_text_length && is_resizable) @@ -5444,7 +5462,7 @@ void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags fl if (allow_opt_alpha_bar) { if (allow_opt_picker) Separator(); - CheckboxFlags("Alpha Bar", (unsigned int*)&g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar); + CheckboxFlags("Alpha Bar", &g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar); } EndPopup(); } @@ -5919,10 +5937,6 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; - const bool span_all_columns = (flags & ImGuiSelectableFlags_SpanAllColumns) != 0; - if (span_all_columns && window->DC.CurrentColumns) // FIXME-OPT: Avoid if vertically clipped. - PushColumnsBackground(); - // Submit label or explicit size to ItemSize(), whereas ItemAdd() will submit a larger/spanning rectangle. ImGuiID id = window->GetID(label); ImVec2 label_size = CalcTextSize(label, NULL, true); @@ -5933,6 +5947,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl // Fill horizontal space // We don't support (size < 0.0f) in Selectable() because the ItemSpacing extension would make explicitely right-aligned sizes not visibly match other widgets. + const bool span_all_columns = (flags & ImGuiSelectableFlags_SpanAllColumns) != 0; const float min_x = span_all_columns ? window->ParentWorkRect.Min.x : pos.x; const float max_x = span_all_columns ? window->ParentWorkRect.Max.x : window->WorkRect.Max.x; if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_SpanAvailWidth)) @@ -5946,7 +5961,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl ImRect bb(min_x, pos.y, text_max.x, text_max.y); if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0) { - const float spacing_x = style.ItemSpacing.x; + const float spacing_x = span_all_columns ? 0.0f : style.ItemSpacing.x; const float spacing_y = style.ItemSpacing.y; const float spacing_L = IM_FLOOR(spacing_x * 0.50f); const float spacing_U = IM_FLOOR(spacing_y * 0.50f); @@ -5957,6 +5972,15 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl } //if (g.IO.KeyCtrl) { GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(0, 255, 0, 255)); } + // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackground for every Selectable.. + const float backup_clip_rect_min_x = window->ClipRect.Min.x; + const float backup_clip_rect_max_x = window->ClipRect.Max.x; + if (span_all_columns) + { + window->ClipRect.Min.x = window->ParentWorkRect.Min.x; + window->ClipRect.Max.x = window->ParentWorkRect.Max.x; + } + bool item_add; if (flags & ImGuiSelectableFlags_Disabled) { @@ -5969,13 +5993,23 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl { item_add = ItemAdd(bb, id); } - if (!item_add) + + if (span_all_columns) { - if (span_all_columns && window->DC.CurrentColumns) - PopColumnsBackground(); - return false; + window->ClipRect.Min.x = backup_clip_rect_min_x; + window->ClipRect.Max.x = backup_clip_rect_max_x; } + if (!item_add) + return false; + + // FIXME: We can standardize the behavior of those two, we could also keep the fast path of override ClipRect + full push on render only, + // which would be advantageous since most selectable are not selected. + if (span_all_columns && window->DC.CurrentColumns) + PushColumnsBackground(); + else if (span_all_columns && g.CurrentTable) + PushTableBackground(); + // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries ImGuiButtonFlags button_flags = 0; if (flags & ImGuiSelectableFlags_NoHoldingActiveID) { button_flags |= ImGuiButtonFlags_NoHoldingActiveId; } @@ -6023,6 +6057,8 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl if (span_all_columns && window->DC.CurrentColumns) PopColumnsBackground(); + else if (span_all_columns && g.CurrentTable) + PopTableBackground(); if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); RenderTextClipped(text_min, text_max, label, NULL, &label_size, style.SelectableTextAlign, &bb); @@ -6384,13 +6420,6 @@ void ImGui::Value(const char* prefix, float v, const char* float_format) //------------------------------------------------------------------------- // Helpers for internal use -ImGuiMenuColumns::ImGuiMenuColumns() -{ - Spacing = Width = NextWidth = 0.0f; - memset(Pos, 0, sizeof(Pos)); - memset(NextWidths, 0, sizeof(NextWidths)); -} - void ImGuiMenuColumns::Update(int count, float spacing, bool clear) { IM_ASSERT(count == IM_ARRAYSIZE(Pos)); @@ -6490,7 +6519,7 @@ void ImGui::EndMenuBar() PopClipRect(); PopID(); window->DC.MenuBarOffset.x = window->DC.CursorPos.x - window->MenuBarRect().Min.x; // Save horizontal position so next append can reuse it. This is kinda equivalent to a per-layer CursorPos. - window->DC.GroupStack.back().EmitItem = false; + g.GroupStack.back().EmitItem = false; EndGroup(); // Restore position on layer 0 window->DC.LayoutType = ImGuiLayoutType_Vertical; window->DC.NavLayerCurrent = ImGuiNavLayer_Main; @@ -7444,7 +7473,7 @@ static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar) { target_order += select_dir; selected_order += select_dir; - tab_to_scroll_to = (target_order <= 0 || target_order >= tab_bar->Tabs.Size) ? tab_to_scroll_to : NULL; + tab_to_scroll_to = (target_order < 0 || target_order >= tab_bar->Tabs.Size) ? tab_to_scroll_to : NULL; } } } @@ -7545,7 +7574,7 @@ void ImGui::EndTabItem() IM_ASSERT(tab_bar->LastTabItemIdx >= 0); ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx]; if (!(tab->Flags & ImGuiTabItemFlags_NoPushId)) - window->IDStack.pop_back(); + PopID(); } bool ImGui::TabItemButton(const char* label, ImGuiTabItemFlags flags) @@ -7787,7 +7816,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, } #if 0 - if (hovered && g.HoveredIdNotActiveTimer > 0.50f && bb.GetWidth() < tab->ContentWidth) + if (hovered && g.HoveredIdNotActiveTimer > TOOLTIP_DELAY && bb.GetWidth() < tab->ContentWidth) { // Enlarge tab display when hovering bb.Max.x = bb.Min.x + IM_FLOOR(ImLerp(bb.GetWidth(), tab->ContentWidth, ImSaturate((g.HoveredIdNotActiveTimer - 0.40f) * 6.0f))); @@ -7829,7 +7858,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, // Tooltip (FIXME: Won't work over the close button because ItemOverlap systems messes up with HoveredIdTimer) // We test IsItemHovered() to discard e.g. when another item is active or drag and drop over the tab bar (which g.HoveredId ignores) - if (text_clipped && g.HoveredId == id && !held && g.HoveredIdNotActiveTimer > 0.50f && IsItemHovered()) + if (text_clipped && g.HoveredId == id && !held && g.HoveredIdNotActiveTimer > TOOLTIP_DELAY && IsItemHovered()) if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip) && !(tab->Flags & ImGuiTabItemFlags_NoTooltip)) SetTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label); @@ -7991,7 +8020,7 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, //------------------------------------------------------------------------- // - SetWindowClipRectBeforeSetChannel() [Internal] // - GetColumnIndex() -// - GetColumnCount() +// - GetColumnsCount() // - GetColumnOffset() // - GetColumnWidth() // - SetColumnOffset() @@ -8425,6 +8454,2961 @@ void ImGui::Columns(int columns_count, const char* id, bool border) BeginColumns(id, columns_count, flags); } + +//----------------------------------------------------------------------------- +// [SECTION] Widgets: BeginTable, EndTable, etc. +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Typical call flow: (root level is public API): +// - BeginTable() user begin into a table +// | BeginChild() - (if ScrollX/ScrollY is set) +// | TableBeginUpdateColumns() - apply resize/order requests, lock columns active state, order +// | - TableSetColumnWidth() - apply resizing width (for mouse resize, often requested by previous frame) +// | - TableUpdateColumnsWeightFromWidth()- recompute columns weights (of stretch columns) from their respective width +// - TableSetupColumn() user submit columns details (optional) +// - TableSetupScrollFreeze() user submit scroll freeze information (optional) +// - TableUpdateLayout() [Internal] automatically called by the FIRST call to TableNextRow() or TableHeadersRow(): lock all widths, columns positions, clipping rectangles +// | TableUpdateDrawChannels() - setup ImDrawList channels +// | TableUpdateBorders() - detect hovering columns for resize, ahead of contents submission +// | TableDrawContextMenu() - draw right-click context menu +//----------------------------------------------------------------------------- +// - TableHeadersRow() or TableHeader() user submit a headers row (optional) +// | TableSortSpecsClickColumn() - when left-clicked: alter sort order and sort direction +// | TableOpenContextMenu() - when right-clicked: trigger opening of the default context menu +// - TableGetSortSpecs() user queries updated sort specs (optional, generally after submitting headers) +// - TableNextRow() user begin into a new row (also automatically called by TableHeadersRow()) +// | TableEndRow() - finish existing row +// | TableBeginRow() - add a new row +// - TableSetColumnIndex() / TableNextColumn() user begin into a cell +// | TableEndCell() - close existing cell +// | TableBeginCell() - enter into current cell +// - [...] user emit contents +//----------------------------------------------------------------------------- +// - EndTable() user ends the table +// | TableDrawBorders() - draw outer borders, inner vertical borders +// | TableReorderDrawChannelsForMerge() - merge draw channels if clipping isn't required +// | EndChild() - (if ScrollX/ScrollY is set) +//----------------------------------------------------------------------------- + +// Configuration +static const int TABLE_DRAW_CHANNEL_BG0 = 0; +static const int TABLE_DRAW_CHANNEL_BG1_FROZEN = 1; +static const int TABLE_DRAW_CHANNEL_UNCLIPPED = 2; // When using ImGuiTableFlags_NoClip +static const float TABLE_BORDER_SIZE = 1.0f; // FIXME-TABLE: Currently hard-coded. +static const float TABLE_RESIZE_SEPARATOR_HALF_THICKNESS = 4.0f; // Extend outside inner borders. +static const float TABLE_RESIZE_SEPARATOR_FEEDBACK_TIMER = 0.06f; // Delay/timer before making the hover feedback (color+cursor) visible because tables/columns tends to be more cramped. + +// Helper +inline ImGuiTableFlags TableFixFlags(ImGuiTableFlags flags, ImGuiWindow* outer_window) +{ + // Adjust flags: set default sizing policy + if ((flags & ImGuiTableFlags_SizingPolicyMaskX_) == 0) + flags |= (flags & ImGuiTableFlags_ScrollX) ? ImGuiTableFlags_SizingPolicyFixedX : ImGuiTableFlags_SizingPolicyStretchX; + + // Adjust flags: MultiSortable automatically enable Sortable + if (flags & ImGuiTableFlags_MultiSortable) + flags |= ImGuiTableFlags_Sortable; + + // Adjust flags: disable saved settings if there's nothing to save + if ((flags & (ImGuiTableFlags_Resizable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Sortable)) == 0) + flags |= ImGuiTableFlags_NoSavedSettings; + + // Adjust flags: enforce borders when resizable + if (flags & ImGuiTableFlags_Resizable) + flags |= ImGuiTableFlags_BordersInnerV; + + // Adjust flags: disable NoHostExtendY if we have any scrolling going on + if ((flags & ImGuiTableFlags_NoHostExtendY) && (flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) != 0) + flags &= ~ImGuiTableFlags_NoHostExtendY; + + // Adjust flags: NoBordersInBodyUntilResize takes priority over NoBordersInBody + if (flags & ImGuiTableFlags_NoBordersInBodyUntilResize) + flags &= ~ImGuiTableFlags_NoBordersInBody; + + // Inherit _NoSavedSettings from top-level window (child windows always have _NoSavedSettings set) +#ifdef IMGUI_HAS_DOCK + ImGuiWindow* window_for_settings = outer_window->RootWindowDockStop; +#else + ImGuiWindow* window_for_settings = outer_window->RootWindow; +#endif + if (window_for_settings->Flags & ImGuiWindowFlags_NoSavedSettings) + flags |= ImGuiTableFlags_NoSavedSettings; + + return flags; +} + +ImGuiTable* ImGui::TableFindByID(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + return g.Tables.GetByKey(id); +} + +ImGuiTable::ImGuiTable() +{ + memset(this, 0, sizeof(*this)); + SettingsOffset = -1; + InstanceInteracted = -1; + LastFrameActive = -1; + LastResizedColumn = -1; + ContextPopupColumn = -1; + ReorderColumn = -1; + ResizedColumn = -1; + HoveredColumnBody = HoveredColumnBorder = -1; +} + +ImGuiTable::~ImGuiTable() +{ + IM_FREE(RawData); +} + +// (Read carefully because this is subtle but it does make sense!) +// About 'outer_size', its meaning needs to differ slightly depending of if we are using ScrollX/ScrollY flags: +// X: +// - outer_size.x < 0.0f -> right align from window/work-rect maximum x edge. +// - outer_size.x = 0.0f -> auto enlarge, use all available space. +// - outer_size.x > 0.0f -> fixed width +// Y with ScrollX/ScrollY: using a child window for scrolling: +// - outer_size.y < 0.0f -> bottom align +// - outer_size.y = 0.0f -> bottom align, consistent with BeginChild(). not recommended unless table is last item in parent window. +// - outer_size.y > 0.0f -> fixed child height. recommended when using Scrolling on any axis. +// Y without scrolling, we output table directly in parent window: +// - outer_size.y < 0.0f -> bottom align (will auto extend, unless NoHostExtendV is set) +// - outer_size.y = 0.0f -> zero minimum height (will auto extend, unless NoHostExtendV is set) +// - outer_size.y > 0.0f -> minimum height (will auto extend, unless NoHostExtendV is set) +// About 'inner_width': +// With ScrollX: +// - inner_width < 0.0f -> *illegal* fit in known width (right align from outer_size.x) <-- weird +// - inner_width = 0.0f -> fit in outer_width: Fixed size columns will take space they need (if avail, otherwise shrink down), Stretch columns becomes Fixed columns. +// - inner_width > 0.0f -> override scrolling width, generally to be larger than outer_size.x. Fixed column take space they need (if avail, otherwise shrink down), Stretch columns share remaining space! +// Without ScrollX: +// - inner_width -> *ignored* +// Details: +// - If you want to use Stretch columns with ScrollX, you generally need to specify 'inner_width' otherwise the concept +// of "available space" doesn't make sense. +// - Even if not really useful, we allow 'inner_width < outer_size.x' for consistency and to facilitate understanding +// of what the value does. +bool ImGui::BeginTable(const char* str_id, int columns_count, ImGuiTableFlags flags, const ImVec2& outer_size, float inner_width) +{ + ImGuiID id = GetID(str_id); + return BeginTableEx(str_id, id, columns_count, flags, outer_size, inner_width); +} + +// For reference, the total _allocation count_ for a table is: +// + 0 (for ImGuiTable instance, we are pooling allocations in g.Tables) +// + 1 (for table->RawData allocated below) +// + 1 (for table->ColumnsNames, if names are used) +// + 1 (for table->Splitter._Channels) +// + 2 * active_channels_count (for ImDrawCmd and ImDrawIdx buffers inside channels) +// Where active_channels_count is variable but often == columns_count or columns_count + 1, see TableUpdateDrawChannels() for details. +// Unused channels don't perform their +2 allocations. +static void TableBeginInitMemory(ImGuiTable* table, int columns_count) +{ + // Allocate single buffer for our arrays + ImSpanAllocator<3> span_allocator; + span_allocator.ReserveBytes(0, columns_count * sizeof(ImGuiTableColumn)); + span_allocator.ReserveBytes(1, columns_count * sizeof(ImS8)); + span_allocator.ReserveBytes(2, columns_count * sizeof(ImGuiTableCellData)); + table->RawData = IM_ALLOC(span_allocator.GetArenaSizeInBytes()); + span_allocator.SetArenaBasePtr(table->RawData); + span_allocator.GetSpan(0, &table->Columns); + span_allocator.GetSpan(1, &table->DisplayOrderToIndex); + span_allocator.GetSpan(2, &table->RowCellData); + + for (int n = 0; n < columns_count; n++) + { + table->Columns[n] = ImGuiTableColumn(); + table->Columns[n].DisplayOrder = table->DisplayOrderToIndex[n] = (ImS8)n; + } +} + +bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImGuiTableFlags flags, const ImVec2& outer_size, float inner_width) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* outer_window = GetCurrentWindow(); + if (outer_window->SkipItems) // Consistent with other tables + beneficial side effect that assert on miscalling EndTable() will be more visible. + return false; + + // Sanity checks + IM_ASSERT(columns_count > 0 && columns_count <= IMGUI_TABLE_MAX_COLUMNS && "Only 1..64 columns allowed!"); + if (flags & ImGuiTableFlags_ScrollX) + IM_ASSERT(inner_width >= 0.0f); + + // If an outer size is specified ahead we will be able to early out when not visible. Exact clipping rules may evolve. + const bool use_child_window = (flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) != 0; + const ImVec2 avail_size = GetContentRegionAvail(); + ImVec2 actual_outer_size = CalcItemSize(outer_size, ImMax(avail_size.x, 1.0f), use_child_window ? ImMax(avail_size.y, 1.0f) : 0.0f); + ImRect outer_rect(outer_window->DC.CursorPos, outer_window->DC.CursorPos + actual_outer_size); + if (use_child_window && IsClippedEx(outer_rect, 0, false)) + { + ItemSize(outer_rect); + return false; + } + + // Acquire storage for the table + ImGuiTable* table = g.Tables.GetOrAddByKey(id); + const bool table_is_new = (table->ID == 0); + const int instance_no = (table->LastFrameActive != g.FrameCount) ? 0 : table->InstanceCurrent + 1; + const ImGuiID instance_id = id + instance_no; + const ImGuiTableFlags table_last_flags = table->Flags; + if (instance_no > 0) + IM_ASSERT(table->ColumnsCount == columns_count && "BeginTable(): Cannot change columns count mid-frame while preserving same ID"); + + // Fix flags + flags = TableFixFlags(flags, outer_window); + + // Initialize + table->ID = id; + table->Flags = flags; + table->InstanceCurrent = (ImS16)instance_no; + table->LastFrameActive = g.FrameCount; + table->OuterWindow = table->InnerWindow = outer_window; + table->ColumnsCount = columns_count; + table->ColumnsNames.Buf.resize(0); + table->IsInitializing = false; + table->IsLayoutLocked = false; + table->InnerWidth = inner_width; + table->OuterRect = outer_rect; + table->WorkRect = outer_rect; + + // When not using a child window, WorkRect.Max will grow as we append contents. + if (use_child_window) + { + // Ensure no vertical scrollbar appears if we only want horizontal one, to make flag consistent + // (we have no other way to disable vertical scrollbar of a window while keeping the horizontal one showing) + ImVec2 override_content_size(FLT_MAX, FLT_MAX); + if ((flags & ImGuiTableFlags_ScrollX) && !(flags & ImGuiTableFlags_ScrollY)) + override_content_size.y = FLT_MIN; + + // Ensure specified width (when not specified, Stretched columns will act as if the width == OuterWidth and + // never lead to any scrolling). We don't handle inner_width < 0.0f, we could potentially use it to right-align + // based on the right side of the child window work rect, which would require knowing ahead if we are going to + // have decoration taking horizontal spaces (typically a vertical scrollbar). + if ((flags & ImGuiTableFlags_ScrollX) && inner_width > 0.0f) + override_content_size.x = inner_width; + + if (override_content_size.x != FLT_MAX || override_content_size.y != FLT_MAX) + SetNextWindowContentSize(ImVec2(override_content_size.x != FLT_MAX ? override_content_size.x : 0.0f, override_content_size.y != FLT_MAX ? override_content_size.y : 0.0f)); + + // Create scrolling region (without border and zero window padding) + ImGuiWindowFlags child_flags = (flags & ImGuiTableFlags_ScrollX) ? ImGuiWindowFlags_HorizontalScrollbar : ImGuiWindowFlags_None; + BeginChildEx(name, instance_id, table->OuterRect.GetSize(), false, child_flags); + table->InnerWindow = g.CurrentWindow; + table->WorkRect = table->InnerWindow->WorkRect; + table->OuterRect = table->InnerWindow->Rect(); + IM_ASSERT(table->InnerWindow->WindowPadding.x == 0.0f && table->InnerWindow->WindowPadding.y == 0.0f && table->InnerWindow->WindowBorderSize == 0.0f); + } + + // Push a standardized ID for both child-using and not-child-using tables + PushOverrideID(instance_id); + + // Backup a copy of host window members we will modify + ImGuiWindow* inner_window = table->InnerWindow; + table->HostIndentX = inner_window->DC.Indent.x; + table->HostClipRect = inner_window->ClipRect; + table->HostSkipItems = inner_window->SkipItems; + table->HostBackupWorkRect = inner_window->WorkRect; + table->HostBackupParentWorkRect = inner_window->ParentWorkRect; + table->HostBackupColumnsOffset = outer_window->DC.ColumnsOffset; + table->HostBackupCursorMaxPos = inner_window->DC.CursorMaxPos; + inner_window->ParentWorkRect = table->WorkRect; + + // Padding and Spacing + // - None ........Content..... Pad .....Content........ + // - PadOuter | Pad ..Content..... Pad .....Content.. Pad | + // - PadInner ........Content.. Pad | Pad ..Content........ + // - PadOuter+PadInner | Pad ..Content.. Pad | Pad ..Content.. Pad | + const bool pad_outer_x = (flags & ImGuiTableFlags_NoPadOuterX) ? false : (flags & ImGuiTableFlags_PadOuterX) ? true : (flags & ImGuiTableFlags_BordersOuterV) != 0; + const bool pad_inner_x = (flags & ImGuiTableFlags_NoPadInnerX) ? false : true; + const float inner_spacing_for_border = (flags & ImGuiTableFlags_BordersInnerV) ? TABLE_BORDER_SIZE : 0.0f; + const float inner_spacing_explicit = (pad_inner_x && (flags & ImGuiTableFlags_BordersInnerV) == 0) ? g.Style.CellPadding.x : 0.0f; + const float inner_padding_explicit = (pad_inner_x && (flags & ImGuiTableFlags_BordersInnerV) != 0) ? g.Style.CellPadding.x : 0.0f; + const float inner_spacing = inner_spacing_for_border + inner_spacing_explicit; + table->CellSpacingX1 = ImCeil(inner_spacing * 0.5f); + table->CellSpacingX2 = inner_spacing - table->CellSpacingX1; + table->CellPaddingX = inner_padding_explicit; + table->CellPaddingY = g.Style.CellPadding.y; + + const float outer_padding_for_border = (flags & ImGuiTableFlags_BordersOuterV) ? TABLE_BORDER_SIZE : 0.0f; + const float outer_padding_explicit = pad_outer_x ? g.Style.CellPadding.x : 0.0f; + table->OuterPaddingX = (outer_padding_for_border + outer_padding_explicit) - table->CellPaddingX; + + table->CurrentColumn = -1; + table->CurrentRow = -1; + table->RowBgColorCounter = 0; + table->LastRowFlags = ImGuiTableRowFlags_None; + table->InnerClipRect = (inner_window == outer_window) ? table->WorkRect : inner_window->ClipRect; + table->InnerClipRect.ClipWith(table->WorkRect); // We need this to honor inner_width + table->InnerClipRect.ClipWithFull(table->HostClipRect); + table->InnerClipRect.Max.y = (flags & ImGuiTableFlags_NoHostExtendY) ? ImMin(table->InnerClipRect.Max.y, inner_window->WorkRect.Max.y) : inner_window->ClipRect.Max.y; + + // Initial draw cmd starts with a BgClipRect that matches the one of its host, to facilitate merge draw commands by default. + // This is because all our cell highlight are manually clipped with BgClipRect + // Larger at first, if/after unfreezing will become same as tight + table->BgClipRect = table->InnerClipRect; + table->BgClipRectForDrawCmd = table->HostClipRect; + IM_ASSERT(table->BgClipRect.Min.y <= table->BgClipRect.Max.y); + + table->RowPosY1 = table->RowPosY2 = table->WorkRect.Min.y; // This is needed somehow + table->RowTextBaseline = 0.0f; // This will be cleared again by TableBeginRow() + table->FreezeRowsRequest = table->FreezeRowsCount = 0; // This will be setup by TableSetupScrollFreeze(), if any + table->FreezeColumnsRequest = table->FreezeColumnsCount = 0; + table->IsUnfrozen = true; + table->DeclColumnsCount = 0; + table->RightMostVisibleColumn = -1; + + // Using opaque colors facilitate overlapping elements of the grid + table->BorderColorStrong = GetColorU32(ImGuiCol_TableBorderStrong); + table->BorderColorLight = GetColorU32(ImGuiCol_TableBorderLight); + table->BorderX1 = table->InnerClipRect.Min.x;// +((table->Flags & ImGuiTableFlags_BordersOuter) ? 0.0f : -1.0f); + table->BorderX2 = table->InnerClipRect.Max.x;// +((table->Flags & ImGuiTableFlags_BordersOuter) ? 0.0f : +1.0f); + + // Make table current + const int table_idx = g.Tables.GetIndex(table); + g.CurrentTableStack.push_back(ImGuiPtrOrIndex(table_idx)); + g.CurrentTable = table; + outer_window->DC.CurrentTableIdx = table_idx; + if (inner_window != outer_window) // So EndChild() within the inner window can restore the table properly. + inner_window->DC.CurrentTableIdx = table_idx; + + if ((table_last_flags & ImGuiTableFlags_Reorderable) && (flags & ImGuiTableFlags_Reorderable) == 0) + table->IsResetDisplayOrderRequest = true; + + // Mark as used + if (table_idx >= g.TablesLastTimeActive.Size) + g.TablesLastTimeActive.resize(table_idx + 1, -1.0f); + g.TablesLastTimeActive[table_idx] = (float)g.Time; + table->MemoryCompacted = false; + + // Setup memory buffer (clear data if columns count changed) + const int stored_size = table->Columns.size(); + if (stored_size != 0 && stored_size != columns_count) + { + IM_FREE(table->RawData); + table->RawData = NULL; + } + if (table->RawData == NULL) + { + TableBeginInitMemory(table, columns_count); + if (table_is_new) + table->IsInitializing = true; + table->IsSortSpecsDirty = table->IsSettingsRequestLoad = true; + table->SettingsOffset = -1; + } + + // Load settings + if (table->IsSettingsRequestLoad) + TableLoadSettings(table); + + // Handle DPI/font resize + // This is designed to facilitate DPI changes with the assumption that e.g. style.CellPadding has been scaled as well. + // It will also react to changing fonts with mixed results. It doesn't need to be perfect but merely provide a decent transition. + // FIXME-DPI: Provide consistent standards for reference size. Perhaps using g.CurrentDpiScale would be more self explanatory. + // This is will lead us to non-rounded WidthRequest in columns, which should work but is a poorly tested path. + const float new_ref_scale_unit = g.FontSize; // g.Font->GetCharAdvance('A') ? + if (table->RefScale != 0.0f && table->RefScale != new_ref_scale_unit) + { + const float scale_factor = new_ref_scale_unit / table->RefScale; + //IMGUI_DEBUG_LOG("[table] %08X RefScaleUnit %.3f -> %.3f, scaling width by %.3f\n", table->ID, table->RefScaleUnit, new_ref_scale_unit, scale_factor); + for (int n = 0; n < columns_count; n++) + table->Columns[n].WidthRequest = table->Columns[n].WidthRequest * scale_factor; + } + table->RefScale = new_ref_scale_unit; + + // Disable output until user calls TableNextRow() or TableNextColumn() leading to the TableUpdateLayout() call.. + // This is not strictly necessary but will reduce cases were "out of table" output will be misleading to the user. + // Because we cannot safely assert in EndTable() when no rows have been created, this seems like our best option. + inner_window->SkipItems = true; + + // Update/lock which columns will be Visible for the frame + TableBeginUpdateColumns(table); + + return true; +} + +void ImGui::TableBeginUpdateColumns(ImGuiTable* table) +{ + // Handle resizing request + // (We process this at the first TableBegin of the frame) + // FIXME-TABLE: Preserve contents width _while resizing down_ until releasing. + // FIXME-TABLE: Contains columns if our work area doesn't allow for scrolling. + if (table->InstanceCurrent == 0) + { + if (table->ResizedColumn != -1 && table->ResizedColumnNextWidth != FLT_MAX) + TableSetColumnWidth(table->ResizedColumn, table->ResizedColumnNextWidth); + table->LastResizedColumn = table->ResizedColumn; + table->ResizedColumnNextWidth = FLT_MAX; + table->ResizedColumn = -1; + } + + // Handle reordering request + // Note: we don't clear ReorderColumn after handling the request. + if (table->InstanceCurrent == 0) + { + if (table->HeldHeaderColumn == -1 && table->ReorderColumn != -1) + table->ReorderColumn = -1; + table->HeldHeaderColumn = -1; + if (table->ReorderColumn != -1 && table->ReorderColumnDir != 0) + { + // We need to handle reordering across hidden columns. + // In the configuration below, moving C to the right of E will lead to: + // ... C [D] E ---> ... [D] E C (Column name/index) + // ... 2 3 4 ... 2 3 4 (Display order) + const int reorder_dir = table->ReorderColumnDir; + IM_ASSERT(reorder_dir == -1 || reorder_dir == +1); + IM_ASSERT(table->Flags & ImGuiTableFlags_Reorderable); + ImGuiTableColumn* src_column = &table->Columns[table->ReorderColumn]; + ImGuiTableColumn* dst_column = &table->Columns[(reorder_dir == -1) ? src_column->PrevVisibleColumn : src_column->NextVisibleColumn]; + IM_UNUSED(dst_column); + const int src_order = src_column->DisplayOrder; + const int dst_order = dst_column->DisplayOrder; + src_column->DisplayOrder = (ImS8)dst_order; + for (int order_n = src_order + reorder_dir; order_n != dst_order + reorder_dir; order_n += reorder_dir) + table->Columns[table->DisplayOrderToIndex[order_n]].DisplayOrder -= (ImS8)reorder_dir; + IM_ASSERT(dst_column->DisplayOrder == dst_order - reorder_dir); + + // Display order is stored in both columns->IndexDisplayOrder and table->DisplayOrder[], + // rebuild the later from the former. + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + table->DisplayOrderToIndex[table->Columns[column_n].DisplayOrder] = (ImS8)column_n; + table->ReorderColumnDir = 0; + table->IsSettingsDirty = true; + } + } + + // Handle display order reset request + if (table->IsResetDisplayOrderRequest) + { + for (int n = 0; n < table->ColumnsCount; n++) + table->DisplayOrderToIndex[n] = table->Columns[n].DisplayOrder = (ImS8)n; + table->IsResetDisplayOrderRequest = false; + table->IsSettingsDirty = true; + } + + // Setup and lock Visible state and order + table->ColumnsVisibleCount = 0; + table->IsDefaultDisplayOrder = true; + ImGuiTableColumn* last_visible_column = NULL; + bool want_column_auto_fit = false; + for (int order_n = 0; order_n < table->ColumnsCount; order_n++) + { + const int column_n = table->DisplayOrderToIndex[order_n]; + if (column_n != order_n) + table->IsDefaultDisplayOrder = false; + ImGuiTableColumn* column = &table->Columns[column_n]; + column->NameOffset = -1; + if (!(table->Flags & ImGuiTableFlags_Hideable) || (column->Flags & ImGuiTableColumnFlags_NoHide)) + column->IsVisibleNextFrame = true; + if (column->IsVisible != column->IsVisibleNextFrame) + { + column->IsVisible = column->IsVisibleNextFrame; + table->IsSettingsDirty = true; + if (!column->IsVisible && column->SortOrder != -1) + table->IsSortSpecsDirty = true; + } + if (column->SortOrder > 0 && !(table->Flags & ImGuiTableFlags_MultiSortable)) + table->IsSortSpecsDirty = true; + if (column->AutoFitQueue != 0x00) + want_column_auto_fit = true; + + ImU64 index_mask = (ImU64)1 << column_n; + ImU64 display_order_mask = (ImU64)1 << column->DisplayOrder; + if (column->IsVisible) + { + column->PrevVisibleColumn = column->NextVisibleColumn = -1; + if (last_visible_column) + { + last_visible_column->NextVisibleColumn = (ImS8)column_n; + column->PrevVisibleColumn = (ImS8)table->Columns.index_from_ptr(last_visible_column); + } + column->IndexWithinVisibleSet = (ImS8)table->ColumnsVisibleCount; + table->ColumnsVisibleCount++; + table->VisibleMaskByIndex |= index_mask; + table->VisibleMaskByDisplayOrder |= display_order_mask; + last_visible_column = column; + } + else + { + column->IndexWithinVisibleSet = -1; + table->VisibleMaskByIndex &= ~index_mask; + table->VisibleMaskByDisplayOrder &= ~display_order_mask; + } + IM_ASSERT(column->IndexWithinVisibleSet <= column->DisplayOrder); + } + table->VisibleUnclippedMaskByIndex = table->VisibleMaskByIndex; // Columns will be masked out by TableUpdateLayout() when Clipped + table->RightMostVisibleColumn = (ImS8)(last_visible_column ? table->Columns.index_from_ptr(last_visible_column) : -1); + + // Disable child window clipping while fitting columns. This is not strictly necessary but makes it possible to avoid + // the column fitting to wait until the first visible frame of the child container (may or not be a good thing). + if (want_column_auto_fit && table->OuterWindow != table->InnerWindow) + table->InnerWindow->SkipItems = false; + + if (want_column_auto_fit) + table->IsSettingsDirty = true; +} + +// Adjust flags: default width mode + stretch columns are not allowed when auto extending +static ImGuiTableColumnFlags TableFixColumnFlags(ImGuiTable* table, ImGuiTableColumnFlags flags) +{ + // Sizing Policy + if ((flags & ImGuiTableColumnFlags_WidthMask_) == 0) + { + // FIXME-TABLE: Inconsistent to promote columns to WidthAlwaysAutoResize + if (table->Flags & ImGuiTableFlags_SizingPolicyFixedX) + flags |= ((table->Flags & ImGuiTableFlags_Resizable) && !(flags & ImGuiTableColumnFlags_NoResize)) ? ImGuiTableColumnFlags_WidthFixed : ImGuiTableColumnFlags_WidthAlwaysAutoResize; + else + flags |= ImGuiTableColumnFlags_WidthStretch; + } + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiTableColumnFlags_WidthMask_)); // Check that only 1 of each set is used. + if ((flags & ImGuiTableColumnFlags_WidthAlwaysAutoResize))// || ((flags & ImGuiTableColumnFlags_WidthStretch) && (table->Flags & ImGuiTableFlags_SizingPolicyStretchX))) + flags |= ImGuiTableColumnFlags_NoResize; + //if ((flags & ImGuiTableColumnFlags_WidthStretch) && (table->Flags & ImGuiTableFlags_SizingPolicyFixedX)) + // flags = (flags & ~ImGuiTableColumnFlags_WidthMask_) | ImGuiTableColumnFlags_WidthFixed; + + // Sorting + if ((flags & ImGuiTableColumnFlags_NoSortAscending) && (flags & ImGuiTableColumnFlags_NoSortDescending)) + flags |= ImGuiTableColumnFlags_NoSort; + + // Alignment + //if ((flags & ImGuiTableColumnFlags_AlignMask_) == 0) + // flags |= ImGuiTableColumnFlags_AlignCenter; + //IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiTableColumnFlags_AlignMask_)); // Check that only 1 of each set is used. + + return flags; +} + +static void TableFixColumnSortDirection(ImGuiTableColumn* column) +{ + // Initial sort state + if (column->SortDirection == ImGuiSortDirection_None) + column->SortDirection = (column->Flags & ImGuiTableColumnFlags_PreferSortDescending) ? (ImS8)ImGuiSortDirection_Descending : (ImS8)(ImGuiSortDirection_Ascending); + + // Handle NoSortAscending/NoSortDescending + if (column->SortDirection == ImGuiSortDirection_Ascending && (column->Flags & ImGuiTableColumnFlags_NoSortAscending)) + column->SortDirection = ImGuiSortDirection_Descending; + else if (column->SortDirection == ImGuiSortDirection_Descending && (column->Flags & ImGuiTableColumnFlags_NoSortDescending)) + column->SortDirection = ImGuiSortDirection_Ascending; +} + +// Minimum column content width (without padding) +static float TableGetMinColumnWidth() +{ + ImGuiContext& g = *GImGui; + //return g.Style.ColumnsMinSpacing; // FIXME-TABLE + return g.Style.FramePadding.x * 1.0f; +} + +// Layout columns for the frame +// Runs on the first call to TableNextRow(), to give a chance for TableSetupColumn() to be called first. +// FIXME-TABLE: Our width (and therefore our WorkRect) will be minimal in the first frame for WidthAlwaysAutoResize +// columns, increase feedback side-effect with widgets relying on WorkRect.Max.x. Maybe provide a default distribution +// for WidthAlwaysAutoResize columns? +void ImGui::TableUpdateLayout(ImGuiTable* table) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(table->IsLayoutLocked == false); + + table->HoveredColumnBody = -1; + table->HoveredColumnBorder = -1; + + // Compute offset, clip rect for the frame + // (can't make auto padding larger than what WorkRect knows about so right-alignment matches) + const ImRect work_rect = table->WorkRect; + const float min_column_width = TableGetMinColumnWidth(); + const float min_column_distance = min_column_width + table->CellPaddingX * 2.0f; + + int count_fixed = 0; + float sum_weights_stretched = 0.0f; // Sum of all weights for weighted columns. + float sum_width_fixed_requests = 0.0f; // Sum of all width for fixed and auto-resize columns, excluding width contributed by Stretch columns. + table->LeftMostStretchedColumnDisplayOrder = -1; + for (int order_n = 0; order_n < table->ColumnsCount; order_n++) + { + if (!(table->VisibleMaskByDisplayOrder & ((ImU64)1 << order_n))) + continue; + const int column_n = table->DisplayOrderToIndex[order_n]; + ImGuiTableColumn* column = &table->Columns[column_n]; + + // Adjust flags: default width mode + weighted columns are not allowed when auto extending + // FIXME-TABLE: Clarify why we need to do this again here and not just in TableSetupColumn() + column->Flags = TableFixColumnFlags(table, column->FlagsIn); + if ((column->Flags & ImGuiTableColumnFlags_IndentMask_) == 0) + column->Flags |= (column_n == 0) ? ImGuiTableColumnFlags_IndentEnable : ImGuiTableColumnFlags_IndentDisable; + + // We have a unusual edge case where if the user doesn't call TableGetSortSpecs() but has sorting enabled + // or varying sorting flags, we still want the sorting arrows to honor those flags. + if (table->Flags & ImGuiTableFlags_Sortable) + TableFixColumnSortDirection(column); + + // Calculate "ideal" column width for nothing to be clipped. + // Combine width from regular rows + width from headers unless requested not to. + const float content_width_body = (float)ImMax(column->ContentMaxXFrozen, column->ContentMaxXUnfrozen) - column->WorkMinX; + const float content_width_headers = (float)column->ContentMaxXHeadersIdeal - column->WorkMinX; + float width_auto = content_width_body; + if (!(table->Flags & ImGuiTableFlags_NoHeadersWidth) && !(column->Flags & ImGuiTableColumnFlags_NoHeaderWidth)) + width_auto = ImMax(width_auto, content_width_headers); + width_auto = ImMax(width_auto, min_column_width); + + // Non-resizable columns also submit their requested width + if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && column->InitStretchWeightOrWidth > 0.0f) + if (!(table->Flags & ImGuiTableFlags_Resizable) || !(column->Flags & ImGuiTableColumnFlags_NoResize)) + width_auto = ImMax(width_auto, column->InitStretchWeightOrWidth); + + column->WidthAuto = width_auto; + + if (column->Flags & (ImGuiTableColumnFlags_WidthAlwaysAutoResize | ImGuiTableColumnFlags_WidthFixed)) + { + // Latch initial size for fixed columns and update it constantly for auto-resizing column (unless clipped!) + if ((column->AutoFitQueue != 0x00) || ((column->Flags & ImGuiTableColumnFlags_WidthAlwaysAutoResize) && !column->IsClipped)) + column->WidthRequest = width_auto; + + // FIXME-TABLE: Increase minimum size during init frame to avoid biasing auto-fitting widgets + // (e.g. TextWrapped) too much. Otherwise what tends to happen is that TextWrapped would output a very + // large height (= first frame scrollbar display very off + clipper would skip lots of items). + // This is merely making the side-effect less extreme, but doesn't properly fixes it. + // FIXME: Move this to ->WidthGiven to avoid temporary lossyless? + if (column->AutoFitQueue > 0x01 && table->IsInitializing) + column->WidthRequest = ImMax(column->WidthRequest, min_column_width * 4.0f); + + count_fixed += 1; + sum_width_fixed_requests += column->WidthRequest; + } + else + { + IM_ASSERT(column->Flags & ImGuiTableColumnFlags_WidthStretch); + const int init_size = (column->StretchWeight < 0.0f); + if (init_size) + column->StretchWeight = 1.0f; + sum_weights_stretched += column->StretchWeight; + if (table->LeftMostStretchedColumnDisplayOrder == -1) + table->LeftMostStretchedColumnDisplayOrder = (ImS8)column->DisplayOrder; + } + sum_width_fixed_requests += table->CellPaddingX * 2.0f; + } + + // Layout + const float width_spacings = (table->OuterPaddingX * 2.0f) + (table->CellSpacingX1 + table->CellSpacingX2) * (table->ColumnsVisibleCount - 1); + float width_avail; + if ((table->Flags & ImGuiTableFlags_ScrollX) && table->InnerWidth == 0.0f) + width_avail = table->InnerClipRect.GetWidth() - width_spacings; + else + width_avail = work_rect.GetWidth() - width_spacings; + const float width_avail_for_stretched_columns = width_avail - sum_width_fixed_requests; + float width_remaining_for_stretched_columns = width_avail_for_stretched_columns; + + // Apply final width based on requested widths + // Mark some columns as not resizable + int count_resizable = 0; + table->ColumnsTotalWidth = width_spacings; + table->ColumnsAutoFitWidth = width_spacings; + for (int order_n = 0; order_n < table->ColumnsCount; order_n++) + { + if (!(table->VisibleMaskByDisplayOrder & ((ImU64)1 << order_n))) + continue; + ImGuiTableColumn* column = &table->Columns[table->DisplayOrderToIndex[order_n]]; + + // Allocate width for stretched/weighted columns + if (column->Flags & ImGuiTableColumnFlags_WidthStretch) + { + // StretchWeight gets converted into WidthRequest + float weight_ratio = column->StretchWeight / sum_weights_stretched; + column->WidthRequest = IM_FLOOR(ImMax(width_avail_for_stretched_columns * weight_ratio, min_column_width) + 0.01f); + width_remaining_for_stretched_columns -= column->WidthRequest; + + // [Resize Rule 2] Resizing from right-side of a stretch column preceding a fixed column + // needs to forward resizing to left-side of fixed column. We also need to copy the NoResize flag.. + if (column->NextVisibleColumn != -1) + if (ImGuiTableColumn* next_column = &table->Columns[column->NextVisibleColumn]) + if (next_column->Flags & ImGuiTableColumnFlags_WidthFixed) + column->Flags |= (next_column->Flags & ImGuiTableColumnFlags_NoDirectResize_); + } + + // [Resize Rule 1] The right-most Visible column is not resizable if there is at least one Stretch column + // (see comments in TableResizeColumn()) + if (column->NextVisibleColumn == -1 && table->LeftMostStretchedColumnDisplayOrder != -1) + column->Flags |= ImGuiTableColumnFlags_NoDirectResize_; + + if (!(column->Flags & ImGuiTableColumnFlags_NoResize)) + count_resizable++; + + // Assign final width, record width in case we will need to shrink + column->WidthGiven = ImFloor(ImMax(column->WidthRequest, min_column_width)); + table->ColumnsTotalWidth += column->WidthGiven + table->CellPaddingX * 2.0f; + table->ColumnsAutoFitWidth += column->WidthAuto + table->CellPaddingX * 2.0f; + } + + // Redistribute remainder width due to rounding (remainder width is < 1.0f * number of Stretch column). + // Using right-to-left distribution (more likely to match resizing cursor), could be adjusted depending + // on where the mouse cursor is and/or relative weights. + if (width_remaining_for_stretched_columns >= 1.0f && !(table->Flags & ImGuiTableFlags_PreciseStretchWidths)) + for (int order_n = table->ColumnsCount - 1; sum_weights_stretched > 0.0f && width_remaining_for_stretched_columns >= 1.0f && order_n >= 0; order_n--) + { + if (!(table->VisibleMaskByDisplayOrder & ((ImU64)1 << order_n))) + continue; + ImGuiTableColumn* column = &table->Columns[table->DisplayOrderToIndex[order_n]]; + if (!(column->Flags & ImGuiTableColumnFlags_WidthStretch)) + continue; + column->WidthRequest += 1.0f; + column->WidthGiven += 1.0f; + width_remaining_for_stretched_columns -= 1.0f; + } + + // Detect hovered column + const ImRect mouse_hit_rect(table->OuterRect.Min.x, table->OuterRect.Min.y, table->OuterRect.Max.x, ImMax(table->OuterRect.Max.y, table->OuterRect.Min.y + table->LastOuterHeight)); + const bool is_hovering_table = ItemHoverable(mouse_hit_rect, 0); + + // Setup final position, offset and clipping rectangles + int visible_n = 0; + float offset_x = (table->FreezeColumnsCount > 0) ? table->OuterRect.Min.x : work_rect.Min.x; + offset_x += table->OuterPaddingX; + offset_x -= table->CellSpacingX1; + ImRect host_clip_rect = table->InnerClipRect; + for (int order_n = 0; order_n < table->ColumnsCount; order_n++) + { + const int column_n = table->DisplayOrderToIndex[order_n]; + ImGuiTableColumn* column = &table->Columns[column_n]; + + column->NavLayerCurrent = (ImS8)((table->FreezeRowsCount > 0 || column_n < table->FreezeColumnsCount) ? ImGuiNavLayer_Menu : ImGuiNavLayer_Main); + + if (table->FreezeColumnsCount > 0 && table->FreezeColumnsCount == visible_n) + offset_x += work_rect.Min.x - table->OuterRect.Min.x; + + if ((table->VisibleMaskByDisplayOrder & ((ImU64)1 << order_n)) == 0) + { + // Hidden column: clear a few fields and we are done with it for the remainder of the function. + // We set a zero-width clip rect but set Min.y/Max.y properly to not interfere with the clipper. + column->MinX = column->MaxX = column->WorkMinX = column->ClipRect.Min.x = column->ClipRect.Max.x = offset_x; + column->WidthGiven = 0.0f; + column->ClipRect.Min.y = work_rect.Min.y; + column->ClipRect.Max.y = FLT_MAX; + column->ClipRect.ClipWithFull(host_clip_rect); + column->IsClipped = column->IsSkipItems = true; + continue; + } + + float max_x = FLT_MAX; + if (table->Flags & ImGuiTableFlags_ScrollX) + { + // Frozen columns can't reach beyond visible width else scrolling will naturally break. + if (order_n < table->FreezeColumnsRequest) + max_x = table->InnerClipRect.Max.x - (table->FreezeColumnsRequest - order_n) * min_column_distance - table->OuterPaddingX; + } + else if ((table->Flags & ImGuiTableFlags_NoKeepColumnsVisible) == 0) + { + // If horizontal scrolling if disabled, we apply a final lossless shrinking of columns in order to make + // sure they are all visible. Because of this we also know that all of the columns will always fit in + // table->WorkRect and therefore in table->InnerRect (because ScrollX is off) + max_x = table->WorkRect.Max.x - (table->ColumnsVisibleCount - (column->IndexWithinVisibleSet + 1)) * min_column_distance - table->OuterPaddingX; + } + if (offset_x + column->WidthGiven + table->CellPaddingX * 2.0f + table->CellSpacingX1 > max_x) + column->WidthGiven = ImMax(max_x - offset_x - table->CellPaddingX * 2.0f - table->CellSpacingX1, min_column_width); + + // Lock all our positions + column->MinX = offset_x; + column->MaxX = offset_x + column->WidthGiven + table->CellSpacingX1 + table->CellSpacingX2 + table->CellPaddingX * 2.0f; + column->WorkMinX = column->MinX + table->CellPaddingX + table->CellSpacingX1; + column->WorkMaxX = column->MaxX - table->CellPaddingX - table->CellSpacingX2; // Expected max + column->ClipRect.Min.x = column->MinX; + column->ClipRect.Min.y = work_rect.Min.y; + column->ClipRect.Max.x = column->MaxX; + column->ClipRect.Max.y = FLT_MAX; + column->ClipRect.ClipWithFull(host_clip_rect); + + column->IsClipped = (column->ClipRect.Max.x <= column->ClipRect.Min.x) && (column->AutoFitQueue & 1) == 0 && (column->CannotSkipItemsQueue & 1) == 0; + if (column->IsClipped) + table->VisibleUnclippedMaskByIndex &= ~((ImU64)1 << column_n); // Columns with the _WidthAlwaysAutoResize sizing policy will never be updated then. + + column->IsSkipItems = !column->IsVisible || table->HostSkipItems; + + // Detect hovered column + if (is_hovering_table && g.IO.MousePos.x >= column->ClipRect.Min.x && g.IO.MousePos.x < column->ClipRect.Max.x) + table->HoveredColumnBody = (ImS8)column_n; + + // Alignment + // FIXME-TABLE: This align based on the whole column width, not per-cell, and therefore isn't useful in + // many cases (to be able to honor this we might be able to store a log of cells width, per row, for + // visible rows, but nav/programmatic scroll would have visible artifacts.) + //if (column->Flags & ImGuiTableColumnFlags_AlignRight) + // column->WorkMinX = ImMax(column->WorkMinX, column->MaxX - column->ContentWidthRowsUnfrozen); + //else if (column->Flags & ImGuiTableColumnFlags_AlignCenter) + // column->WorkMinX = ImLerp(column->WorkMinX, ImMax(column->StartX, column->MaxX - column->ContentWidthRowsUnfrozen), 0.5f); + + // Reset content width variables + column->ContentMaxXFrozen = column->ContentMaxXUnfrozen = column->WorkMinX; + column->ContentMaxXHeadersUsed = column->ContentMaxXHeadersIdeal = column->WorkMinX; + + // Don't decrement auto-fit counters until container window got a chance to submit its items + if (table->HostSkipItems == false) + { + column->AutoFitQueue >>= 1; + column->CannotSkipItemsQueue >>= 1; + } + + if (visible_n < table->FreezeColumnsCount) + host_clip_rect.Min.x = ImMax(host_clip_rect.Min.x, column->MaxX + 2.0f); + + offset_x += column->WidthGiven + table->CellSpacingX1 + table->CellSpacingX2 + table->CellPaddingX * 2.0f; + visible_n++; + } + + // Detect/store when we are hovering the unused space after the right-most column (so e.g. context menus can react on it) + if (is_hovering_table && table->HoveredColumnBody == -1) + { + float unused_x1 = table->WorkRect.Min.x; + if (table->RightMostVisibleColumn != -1) + unused_x1 = ImMax(unused_x1, table->Columns[table->RightMostVisibleColumn].ClipRect.Max.x); + if (g.IO.MousePos.x >= unused_x1) + table->HoveredColumnBody = (ImS8)table->ColumnsCount; + } + + // Clear Resizable flag if none of our column are actually resizable (either via an explicit _NoResize flag, + // either because of using _WidthAlwaysAutoResize/_WidthStretch). + // This will hide the resizing option from the context menu. + if (count_resizable == 0 && (table->Flags & ImGuiTableFlags_Resizable)) + table->Flags &= ~ImGuiTableFlags_Resizable; + + // Allocate draw channels + TableUpdateDrawChannels(table); + + // Borders + if (table->Flags & ImGuiTableFlags_Resizable) + TableUpdateBorders(table); + + // Reset fields after we used them in TableSetupResize() + table->LastFirstRowHeight = 0.0f; + table->IsLayoutLocked = true; + table->IsUsingHeaders = false; + + // Context menu + if (table->IsContextPopupOpen && table->InstanceCurrent == table->InstanceInteracted) + { + const ImGuiID context_menu_id = ImHashStr("##ContextMenu", 0, table->ID); + if (BeginPopupEx(context_menu_id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings)) + { + TableDrawContextMenu(table); + EndPopup(); + } + else + { + table->IsContextPopupOpen = false; + } + } + + // Initial state + ImGuiWindow* inner_window = table->InnerWindow; + if (table->Flags & ImGuiTableFlags_NoClip) + table->DrawSplitter.SetCurrentChannel(inner_window->DrawList, TABLE_DRAW_CHANNEL_UNCLIPPED); + else + inner_window->DrawList->PushClipRect(inner_window->ClipRect.Min, inner_window->ClipRect.Max, false); + + // Sanitize and build sort specs before we have a change to use them for display. + // This path will only be exercised when sort specs are modified before header rows (e.g. init or visibility change) + if (table->IsSortSpecsDirty && (table->Flags & ImGuiTableFlags_Sortable)) + TableSortSpecsBuild(table); +} + +// Process interaction on resizing borders. Actual size change will be applied in EndTable() +// - Set table->HoveredColumnBorder with a short delay/timer to reduce feedback noise +// - Submit ahead of table contents and header, use ImGuiButtonFlags_AllowItemOverlap to prioritize widgets +// overlapping the same area. +void ImGui::TableUpdateBorders(ImGuiTable* table) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(table->Flags & ImGuiTableFlags_Resizable); + + // At this point OuterRect height may be zero or under actual final height, so we rely on temporal coherency and + // use the final height from last frame. Because this is only affecting _interaction_ with columns, it is not + // really problematic (whereas the actual visual will be displayed in EndTable() and using the current frame height). + // Actual columns highlight/render will be performed in EndTable() and not be affected. + const float hit_half_width = TABLE_RESIZE_SEPARATOR_HALF_THICKNESS; + const float hit_y1 = table->OuterRect.Min.y; + const float hit_y2_body = ImMax(table->OuterRect.Max.y, hit_y1 + table->LastOuterHeight); + const float hit_y2_head = hit_y1 + table->LastFirstRowHeight; + + for (int order_n = 0; order_n < table->ColumnsCount; order_n++) + { + if (!(table->VisibleMaskByDisplayOrder & ((ImU64)1 << order_n))) + continue; + + const int column_n = table->DisplayOrderToIndex[order_n]; + ImGuiTableColumn* column = &table->Columns[column_n]; + if (column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoDirectResize_)) + continue; + + // ImGuiTableFlags_NoBordersInBodyUntilResize will be honored in TableDrawBorders() + const float border_y2_hit = (table->Flags & ImGuiTableFlags_NoBordersInBody) ? hit_y2_head : hit_y2_body; + if ((table->Flags & ImGuiTableFlags_NoBordersInBody) && table->IsUsingHeaders == false) + continue; + + ImGuiID column_id = TableGetColumnResizeID(table, column_n, table->InstanceCurrent); + ImRect hit_rect(column->MaxX - hit_half_width, hit_y1, column->MaxX + hit_half_width, border_y2_hit); + //GetForegroundDrawList()->AddRect(hit_rect.Min, hit_rect.Max, IM_COL32(255, 0, 0, 100)); + KeepAliveID(column_id); + + bool hovered = false, held = false; + bool pressed = ButtonBehavior(hit_rect, column_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnDoubleClick); + if (pressed && IsMouseDoubleClicked(0) && !(column->Flags & ImGuiTableColumnFlags_WidthStretch)) + { + // FIXME-TABLE: Double-clicking on column edge could auto-fit Stretch column? + TableSetColumnAutofit(table, column_n); + ClearActiveID(); + held = hovered = false; + } + if (held) + { + table->ResizedColumn = (ImS8)column_n; + table->InstanceInteracted = table->InstanceCurrent; + } + if ((hovered && g.HoveredIdTimer > TABLE_RESIZE_SEPARATOR_FEEDBACK_TIMER) || held) + { + table->HoveredColumnBorder = (ImS8)column_n; + SetMouseCursor(ImGuiMouseCursor_ResizeEW); + } + } +} + +void ImGui::EndTable() +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + IM_ASSERT(table != NULL && "Only call EndTable() if BeginTable() returns true!"); + + // This assert would be very useful to catch a common error... unfortunately it would probably trigger in some + // cases, and for consistency user may sometimes output empty tables (and still benefit from e.g. outer border) + //IM_ASSERT(table->IsLayoutLocked && "Table unused: never called TableNextRow(), is that the intent?"); + + // If the user never got to call TableNextRow() or TableNextColumn(), we call layout ourselves to ensure all our + // code paths are consistent (instead of just hoping that TableBegin/TableEnd will work), get borders drawn, etc. + if (!table->IsLayoutLocked) + TableUpdateLayout(table); + + const ImGuiTableFlags flags = table->Flags; + ImGuiWindow* inner_window = table->InnerWindow; + ImGuiWindow* outer_window = table->OuterWindow; + IM_ASSERT(inner_window == g.CurrentWindow); + IM_ASSERT(outer_window == inner_window || outer_window == inner_window->ParentWindow); + + if (table->IsInsideRow) + TableEndRow(table); + + // Context menu in columns body + if (flags & ImGuiTableFlags_ContextMenuInBody) + if (table->HoveredColumnBody != -1 && !ImGui::IsAnyItemHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Right)) + TableOpenContextMenu((int)table->HoveredColumnBody); + + // Finalize table height + inner_window->DC.CursorMaxPos = table->HostBackupCursorMaxPos; + if (inner_window != outer_window) + { + table->OuterRect.Max.y = ImMax(table->OuterRect.Max.y, inner_window->Pos.y + inner_window->Size.y); + inner_window->DC.CursorMaxPos.y = table->RowPosY2; + } + else if (!(flags & ImGuiTableFlags_NoHostExtendY)) + { + table->OuterRect.Max.y = ImMax(table->OuterRect.Max.y, inner_window->DC.CursorPos.y); + inner_window->DC.CursorMaxPos.y = table->RowPosY2; + } + table->WorkRect.Max.y = ImMax(table->WorkRect.Max.y, table->OuterRect.Max.y); + table->LastOuterHeight = table->OuterRect.GetHeight(); + + if (!(flags & ImGuiTableFlags_NoClip)) + inner_window->DrawList->PopClipRect(); + inner_window->ClipRect = inner_window->DrawList->_ClipRectStack.back(); + + // Draw borders + if ((flags & ImGuiTableFlags_Borders) != 0) + TableDrawBorders(table); + table->DrawSplitter.SetCurrentChannel(inner_window->DrawList, 0); + + // Store content width reference for each column (before attempting to merge draw calls) + const float backup_outer_cursor_pos_x = outer_window->DC.CursorPos.x; + const float backup_outer_max_pos_x = outer_window->DC.CursorMaxPos.x; + const float backup_inner_max_pos_x = inner_window->DC.CursorMaxPos.x; + float max_pos_x = backup_inner_max_pos_x; + if (table->RightMostVisibleColumn != -1) + max_pos_x = ImMax(max_pos_x, table->Columns[table->RightMostVisibleColumn].MaxX); + +#if 0 + // Strip out dummy channel draw calls + // We have no way to prevent user submitting direct ImDrawList calls into a hidden column (but ImGui:: calls will be clipped out) + // FIXME-TABLE: The problem with this approach is we are effectively making it harder for users watching metrics to spot wasted vertices. + if (table->DummyDrawChannel != (ImU8)-1) + { + ImDrawChannel* dummy_channel = &table->DrawSplitter._Channels[table->DummyDrawChannel]; + dummy_channel->_CmdBuffer.resize(0); + dummy_channel->_IdxBuffer.resize(0); + } +#endif + + // Flatten channels and merge draw calls + if ((table->Flags & ImGuiTableFlags_NoClip) == 0) + TableReorderDrawChannelsForMerge(table); + table->DrawSplitter.Merge(inner_window->DrawList); + + if (!(table->Flags & ImGuiTableFlags_ScrollX) && inner_window != outer_window) + { + inner_window->Scroll.x = 0.0f; + } + else if (table->LastResizedColumn != -1 && table->ResizedColumn == -1 && inner_window->ScrollbarX && table->InstanceInteracted == table->InstanceCurrent) + { + // When releasing a column being resized, scroll to keep the resulting column in sight + const float neighbor_width_to_keep_visible = TableGetMinColumnWidth() + table->CellPaddingX * 2.0f; + ImGuiTableColumn* column = &table->Columns[table->LastResizedColumn]; + if (column->MaxX < table->InnerClipRect.Min.x) + SetScrollFromPosX(inner_window, column->MaxX - inner_window->Pos.x - neighbor_width_to_keep_visible, 1.0f); + else if (column->MaxX > table->InnerClipRect.Max.x) + SetScrollFromPosX(inner_window, column->MaxX - inner_window->Pos.x + neighbor_width_to_keep_visible, 1.0f); + } + + // Apply resizing/dragging at the end of the frame + if (table->ResizedColumn != -1) + { + ImGuiTableColumn* column = &table->Columns[table->ResizedColumn]; + const float new_x2 = (g.IO.MousePos.x - g.ActiveIdClickOffset.x + TABLE_RESIZE_SEPARATOR_HALF_THICKNESS); + const float new_width = ImFloor(new_x2 - column->MinX - table->CellSpacingX1 - table->CellPaddingX * 2.0f); + table->ResizedColumnNextWidth = new_width; + } + + // Layout in outer window + IM_ASSERT_USER_ERROR(inner_window->IDStack.back() == table->ID + table->InstanceCurrent, "Mismatching PushID/PopID!"); + PopID(); + inner_window->WorkRect = table->HostBackupWorkRect; + inner_window->ParentWorkRect = table->HostBackupParentWorkRect; + inner_window->SkipItems = table->HostSkipItems; + outer_window->DC.CursorPos = table->OuterRect.Min; + outer_window->DC.ColumnsOffset = table->HostBackupColumnsOffset; + if (inner_window != outer_window) + { + EndChild(); + } + else + { + ImVec2 item_size = table->OuterRect.GetSize(); + item_size.x = table->ColumnsTotalWidth; + ItemSize(item_size); + } + + // Override EndChild/ItemSize max extent with our own to enable auto-resize on the X axis when possible + // FIXME-TABLE: This can be improved (e.g. for Fixed columns we don't want to auto AutoFitWidth? or propagate window auto-fit to table?) + if (table->Flags & ImGuiTableFlags_ScrollX) + { + inner_window->DC.CursorMaxPos.x = max_pos_x; // Set contents width for scrolling + outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos_x, backup_outer_cursor_pos_x + table->ColumnsTotalWidth + inner_window->ScrollbarSizes.x); // For auto-fit + } + else + { + outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos_x, table->WorkRect.Min.x + table->ColumnsAutoFitWidth); // For auto-fit + } + + // Save settings + if (table->IsSettingsDirty) + TableSaveSettings(table); + + // Clear or restore current table, if any + IM_ASSERT(g.CurrentWindow == outer_window && g.CurrentTable == table); + g.CurrentTableStack.pop_back(); + g.CurrentTable = g.CurrentTableStack.Size ? g.Tables.GetByIndex(g.CurrentTableStack.back().Index) : NULL; + outer_window->DC.CurrentTableIdx = g.CurrentTable ? g.Tables.GetIndex(g.CurrentTable) : -1; +} + +// FIXME-TABLE: This is a mess, need to redesign how we render borders. +void ImGui::TableDrawBorders(ImGuiTable* table) +{ + ImGuiWindow* inner_window = table->InnerWindow; + ImGuiWindow* outer_window = table->OuterWindow; + table->DrawSplitter.SetCurrentChannel(inner_window->DrawList, TABLE_DRAW_CHANNEL_BG0); + if (inner_window->Hidden || !table->HostClipRect.Overlaps(table->InnerClipRect)) + return; + ImDrawList* inner_drawlist = inner_window->DrawList; + ImDrawList* outer_drawlist = outer_window->DrawList; + + // Draw inner border and resizing feedback + const float border_size = TABLE_BORDER_SIZE; + const float draw_y1 = table->OuterRect.Min.y; + const float draw_y2_body = table->OuterRect.Max.y; + const float draw_y2_head = table->IsUsingHeaders ? ((table->FreezeRowsCount >= 1 ? table->OuterRect.Min.y : table->WorkRect.Min.y) + table->LastFirstRowHeight) : draw_y1; + + if (table->Flags & ImGuiTableFlags_BordersInnerV) + { + for (int order_n = 0; order_n < table->ColumnsCount; order_n++) + { + if (!(table->VisibleMaskByDisplayOrder & ((ImU64)1 << order_n))) + continue; + + const int column_n = table->DisplayOrderToIndex[order_n]; + ImGuiTableColumn* column = &table->Columns[column_n]; + const bool is_hovered = (table->HoveredColumnBorder == column_n); + const bool is_resized = (table->ResizedColumn == column_n) && (table->InstanceInteracted == table->InstanceCurrent); + const bool is_resizable = (column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoDirectResize_)) == 0; + + if (column->MaxX > table->InnerClipRect.Max.x && !is_resized)// && is_hovered) + continue; + if (column->NextVisibleColumn == -1 && !is_resizable) + continue; + if (column->MaxX <= column->ClipRect.Min.x) // FIXME-TABLE FIXME-STYLE: Assume BorderSize==1, this is problematic if we want to increase the border size.. + continue; + + // Draw in outer window so right-most column won't be clipped + // Always draw full height border when being resized/hovered, or on the delimitation of frozen column scrolling. + ImU32 col; + float draw_y2; + if (is_hovered || is_resized || (table->FreezeColumnsCount != -1 && table->FreezeColumnsCount == order_n + 1)) + { + draw_y2 = draw_y2_body; + col = is_resized ? GetColorU32(ImGuiCol_SeparatorActive) : is_hovered ? GetColorU32(ImGuiCol_SeparatorHovered) : table->BorderColorStrong; + } + else + { + draw_y2 = (table->Flags & (ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_NoBordersInBodyUntilResize)) ? draw_y2_head : draw_y2_body; + col = (table->Flags & (ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_NoBordersInBodyUntilResize)) ? table->BorderColorStrong : table->BorderColorLight; + } + + if (draw_y2 > draw_y1) + inner_drawlist->AddLine(ImVec2(column->MaxX, draw_y1), ImVec2(column->MaxX, draw_y2), col, border_size); + } + } + + // Draw outer border + if (table->Flags & ImGuiTableFlags_BordersOuter) + { + // Display outer border offset by 1 which is a simple way to display it without adding an extra draw call + // (Without the offset, in outer_window it would be rendered behind cells, because child windows are above their + // parent. In inner_window, it won't reach out over scrollbars. Another weird solution would be to display part + // of it in inner window, and the part that's over scrollbars in the outer window..) + // Either solution currently won't allow us to use a larger border size: the border would clipped. + ImRect outer_border = table->OuterRect; + const ImU32 outer_col = table->BorderColorStrong; + if (inner_window != outer_window) // FIXME-TABLE + outer_border.Expand(1.0f); + if ((table->Flags & ImGuiTableFlags_BordersOuter) == ImGuiTableFlags_BordersOuter) + { + outer_drawlist->AddRect(outer_border.Min, outer_border.Max, outer_col, 0.0f, ~0, border_size); + } + else if (table->Flags & ImGuiTableFlags_BordersOuterV) + { + // FIXME-TABLE: could use AddRect or explicit VLine/HLine helper? + outer_drawlist->AddLine(outer_border.Min, ImVec2(outer_border.Min.x, outer_border.Max.y), outer_col, border_size); + outer_drawlist->AddLine(ImVec2(outer_border.Max.x, outer_border.Min.y), outer_border.Max, outer_col, border_size); + } + else if (table->Flags & ImGuiTableFlags_BordersOuterH) + { + outer_drawlist->AddLine(outer_border.Min, ImVec2(outer_border.Max.x, outer_border.Min.y), outer_col, border_size); + outer_drawlist->AddLine(ImVec2(outer_border.Min.x, outer_border.Max.y), outer_border.Max, outer_col, border_size); + } + } + if ((table->Flags & ImGuiTableFlags_BordersInnerH) && table->RowPosY2 < table->OuterRect.Max.y) + { + // Draw bottom-most row border + // FIXME-TABLE: could use AddRect or explicit VLine/HLine helper? + const float border_y = table->RowPosY2; + if (border_y >= table->BgClipRect.Min.y && border_y < table->BgClipRect.Max.y) + inner_drawlist->AddLine(ImVec2(table->BorderX1, border_y), ImVec2(table->BorderX2, border_y), table->BorderColorLight, border_size); + } +} + +static void TableUpdateColumnsWeightFromWidth(ImGuiTable* table) +{ + IM_ASSERT(table->LeftMostStretchedColumnDisplayOrder != -1); + + // Measure existing quantity + float visible_weight = 0.0f; + float visible_width = 0.0f; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + if (!column->IsVisible || !(column->Flags & ImGuiTableColumnFlags_WidthStretch)) + continue; + visible_weight += column->StretchWeight; + visible_width += column->WidthRequest; + } + IM_ASSERT(visible_weight > 0.0f && visible_width > 0.0f); + + // Apply new weights + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + if (!column->IsVisible || !(column->Flags & ImGuiTableColumnFlags_WidthStretch)) + continue; + column->StretchWeight = ((column->WidthRequest + 0.0f) / visible_width) * visible_weight; + } +} + +// 'width' = inner column width, without padding +void ImGui::TableSetColumnWidth(int column_n, float width) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + IM_ASSERT(table != NULL && table->IsLayoutLocked == false); + IM_ASSERT(column_n >= 0 && column_n < table->ColumnsCount); + ImGuiTableColumn* column_0 = &table->Columns[column_n]; + float column_0_width = width; + + // Constraints + const float min_width = TableGetMinColumnWidth(); + float max_width_0 = FLT_MAX; + if (!(table->Flags & ImGuiTableFlags_ScrollX)) + max_width_0 = (table->WorkRect.Max.x - column_0->MinX) - (table->ColumnsVisibleCount - (column_0->IndexWithinVisibleSet + 1)) * min_width; + column_0_width = ImClamp(column_0_width, min_width, max_width_0); + + // Compare both requested and actual given width to avoid overwriting requested width when column is stuck (minimum size, bounded) + if (column_0->WidthGiven == column_0_width || column_0->WidthRequest == column_0_width) + return; + + ImGuiTableColumn* column_1 = (column_0->NextVisibleColumn != -1) ? &table->Columns[column_0->NextVisibleColumn] : NULL; + + // In this surprisingly not simple because of how we support mixing Fixed and Stretch columns. + // When forwarding resize from Wn| to Fn+1| we need to be considerate of the _NoResize flag on Fn+1. + // FIXME-TABLE: Find a way to rewrite all of this so interactions feel more consistent for the user. + // Scenarios: + // - F1 F2 F3 resize from F1| or F2| --> ok: alter ->WidthRequested of Fixed column. Subsequent columns will be offset. + // - F1 F2 F3 resize from F3| --> ok: alter ->WidthRequested of Fixed column. If active, ScrollX extent can be altered. + // - F1 F2 W3 resize from F1| or F2| --> ok: alter ->WidthRequested of Fixed column. If active, ScrollX extent can be altered, but it doesn't make much sense as the Stretch column will always be minimal size. + // - F1 F2 W3 resize from W3| --> ok: no-op (disabled by Resize Rule 1) + // - W1 W2 W3 resize from W1| or W2| --> FIXME + // - W1 W2 W3 resize from W3| --> ok: no-op (disabled by Resize Rule 1) + // - W1 F2 F3 resize from F3| --> ok: no-op (disabled by Resize Rule 1) + // - W1 F2 resize from F2| --> ok: no-op (disabled by Resize Rule 1) + // - W1 W2 F3 resize from W1| or W2| --> ok + // - W1 F2 W3 resize from W1| or F2| --> FIXME + // - F1 W2 F3 resize from W2| --> ok + // - W1 F2 F3 resize from W1| --> ok: equivalent to resizing |F2. F3 will not move. (forwarded by Resize Rule 2) + // - W1 F2 F3 resize from F2| --> FIXME should resize F2, F3 and not have effect on W1 (Stretch columns are _before_ the Fixed column). + + // Rules: + // - [Resize Rule 1] Can't resize from right of right-most visible column if there is any Stretch column. Implemented in TableUpdateLayout(). + // - [Resize Rule 2] Resizing from right-side of a Stretch column before a fixed column forward sizing to left-side of fixed column. + // - [Resize Rule 3] If we are are followed by a fixed column and we have a Stretch column before, we need to ensure that our left border won't move. + table->IsSettingsDirty = true; + if (column_0->Flags & ImGuiTableColumnFlags_WidthFixed) + { + // [Resize Rule 3] If we are are followed by a fixed column and we have a Stretch column before, we need to ensure + // that our left border won't move, which we can do by making sure column_a/column_b resizes cancels each others. + if (column_1 && (column_1->Flags & ImGuiTableColumnFlags_WidthFixed)) + if (table->LeftMostStretchedColumnDisplayOrder != -1 && table->LeftMostStretchedColumnDisplayOrder < column_0->DisplayOrder) + { + // (old_a + old_b == new_a + new_b) --> (new_a == old_a + old_b - new_b) + float column_1_width = ImMax(column_1->WidthRequest - (column_0_width - column_0->WidthRequest), min_width); + column_0_width = column_0->WidthRequest + column_1->WidthRequest - column_1_width; + column_1->WidthRequest = column_1_width; + } + + // Apply + //IMGUI_DEBUG_LOG("TableSetColumnWidth(%d, %.1f->%.1f)\n", column_0_idx, column_0->WidthRequested, column_0_width); + column_0->WidthRequest = column_0_width; + } + else if (column_0->Flags & ImGuiTableColumnFlags_WidthStretch) + { + // [Resize Rule 2] + if (column_1 && (column_1->Flags & ImGuiTableColumnFlags_WidthFixed)) + { + float off = (column_0->WidthGiven - column_0_width); + float column_1_width = column_1->WidthGiven + off; + column_1->WidthRequest = ImMax(min_width, column_1_width); + return; + } + + // (old_a + old_b == new_a + new_b) --> (new_a == old_a + old_b - new_b) + float column_1_width = ImMax(column_1->WidthRequest - (column_0_width - column_0->WidthRequest), min_width); + column_0_width = column_0->WidthRequest + column_1->WidthRequest - column_1_width; + column_1->WidthRequest = column_1_width; + column_0->WidthRequest = column_0_width; + TableUpdateColumnsWeightFromWidth(table); + } +} + +// Public wrapper +void ImGui::TableSetColumnVisible(int column_n, bool visible) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + IM_ASSERT(table != NULL && table->IsLayoutLocked == false); + IM_ASSERT(column_n >= 0 && column_n < table->ColumnsCount); + table->Columns[column_n].IsVisibleNextFrame = visible; +} + +// Allocate draw channels. Called by TableUpdateLayout() +// - We allocate them following storage order instead of display order so reordering columns won't needlessly +// increase overall dormant memory cost. +// - We isolate headers draw commands in their own channels instead of just altering clip rects. +// This is in order to facilitate merging of draw commands. +// - After crossing FreezeRowsCount, all columns see their current draw channel changed to a second set of channels. +// - We only use the dummy draw channel so we can push a null clipping rectangle into it without affecting other +// channels, while simplifying per-row/per-cell overhead. It will be empty and discarded when merged. +// - We allocate 1 or 2 background draw channels. This is because we know PushTableBackground() is only used for +// horizontal spanning. If we allowed vertical spanning we'd need one background draw channel per merge group (1-4). +// Draw channel allocation (before merging): +// - NoClip --> 2+D+1 channels: bg0 + bg1 + foreground (same clip rect == 1 draw call) (FIXME-TABLE: could merge bg1 and foreground?) +// - Clip --> 2+D+N channels +// - FreezeRows --> 2+D+N*2 (unless scrolling value is zero) +// - FreezeRows || FreezeColunns --> 3+D+N*2 (unless scrolling value is zero) +// Where D is 1 if any column is clipped or hidden (dummy channel) otherwise 0. +void ImGui::TableUpdateDrawChannels(ImGuiTable* table) +{ + const int freeze_row_multiplier = (table->FreezeRowsCount > 0) ? 2 : 1; + const int channels_for_row = (table->Flags & ImGuiTableFlags_NoClip) ? 1 : table->ColumnsVisibleCount; + const int channels_for_bg = 1 + 1 * freeze_row_multiplier; + const int channels_for_dummy = (table->ColumnsVisibleCount < table->ColumnsCount || table->VisibleUnclippedMaskByIndex != table->VisibleMaskByIndex) ? +1 : 0; + const int channels_total = channels_for_bg + (channels_for_row * freeze_row_multiplier) + channels_for_dummy; + table->DrawSplitter.Split(table->InnerWindow->DrawList, channels_total); + table->DummyDrawChannel = (ImU8)((channels_for_dummy > 0) ? channels_total - 1 : -1); + table->Bg1DrawChannelCurrent = TABLE_DRAW_CHANNEL_BG1_FROZEN; + table->Bg1DrawChannelUnfrozen = (ImU8)((table->FreezeRowsCount > 0) ? 2 + channels_for_row : TABLE_DRAW_CHANNEL_BG1_FROZEN); + + int draw_channel_current = 2; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + if (!column->IsClipped) + { + column->DrawChannelFrozen = (ImU8)(draw_channel_current); + column->DrawChannelUnfrozen = (ImU8)(draw_channel_current + (table->FreezeRowsCount > 0 ? channels_for_row + 1 : 0)); + if (!(table->Flags & ImGuiTableFlags_NoClip)) + draw_channel_current++; + } + else + { + column->DrawChannelFrozen = column->DrawChannelUnfrozen = table->DummyDrawChannel; + } + column->DrawChannelCurrent = column->DrawChannelFrozen; + } +} + +// This function reorder draw channels based on matching clip rectangle, to facilitate merging them. Called by EndTable(). +// +// Columns where the contents didn't stray off their local clip rectangle can be merged. To achieve +// this we merge their clip rect and make them contiguous in the channel list, so they can be merged +// by the call to DrawSplitter.Merge() following to the call to this function. +// +// We reorder draw commands by arranging them into a maximum of 4 distinct groups: +// +// 1 group: 2 groups: 2 groups: 4 groups: +// [ 0. ] no freeze [ 0. ] row freeze [ 01 ] col freeze [ 01 ] row+col freeze +// [ .. ] or no scroll [ 2. ] and v-scroll [ .. ] and h-scroll [ 23 ] and v+h-scroll +// +// Each column itself can use 1 channel (row freeze disabled) or 2 channels (row freeze enabled). +// When the contents of a column didn't stray off its limit, we move its channels into the corresponding group +// based on its position (within frozen rows/columns groups or not). +// At the end of the operation our 1-4 groups will each have a ImDrawCmd using the same ClipRect. +// +// This function assume that each column are pointing to a distinct draw channel, +// otherwise merge_group->ChannelsCount will not match set bit count of merge_group->ChannelsMask. +// +// Column channels will not be merged into one of the 1-4 groups in the following cases: +// - The contents stray off its clipping rectangle (we only compare the MaxX value, not the MinX value). +// Direct ImDrawList calls won't be taken into account by default, if you use them make sure the ImGui:: bounds +// matches, by e.g. calling SetCursorScreenPos(). +// - The channel uses more than one draw command itself. We drop all our attempt at merging stuff here.. +// we could do better but it's going to be rare and probably not worth the hassle. +// Columns for which the draw channel(s) haven't been merged with other will use their own ImDrawCmd. +// +// This function is particularly tricky to understand.. take a breath. +void ImGui::TableReorderDrawChannelsForMerge(ImGuiTable* table) +{ + ImGuiContext& g = *GImGui; + ImDrawListSplitter* splitter = &table->DrawSplitter; + const bool has_freeze_v = (table->FreezeRowsCount > 0); + const bool has_freeze_h = (table->FreezeColumnsCount > 0); + + // Track which groups we are going to attempt to merge, and which channels goes into each group. + struct MergeGroup + { + ImRect ClipRect; + int ChannelsCount; + ImBitArray ChannelsMask; + }; + int merge_group_mask = 0x00; + MergeGroup merge_groups[4]; + memset(merge_groups, 0, sizeof(merge_groups)); + + // 1. Scan channels and take note of those which can be merged + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + if (!(table->VisibleUnclippedMaskByIndex & ((ImU64)1 << column_n))) + continue; + ImGuiTableColumn* column = &table->Columns[column_n]; + + const int merge_group_sub_count = has_freeze_v ? 2 : 1; + for (int merge_group_sub_n = 0; merge_group_sub_n < merge_group_sub_count; merge_group_sub_n++) + { + const int channel_no = (merge_group_sub_n == 0) ? column->DrawChannelFrozen : column->DrawChannelUnfrozen; + + // Don't attempt to merge if there are multiple draw calls within the column + ImDrawChannel* src_channel = &splitter->_Channels[channel_no]; + if (src_channel->_CmdBuffer.Size > 0 && src_channel->_CmdBuffer.back().ElemCount == 0) + src_channel->_CmdBuffer.pop_back(); + if (src_channel->_CmdBuffer.Size != 1) + continue; + + // Find out the width of this merge group and check if it will fit in our column + // (note that we assume that rendering didn't stray on the left direction. we should need a CursorMinPos to detect it) + if (!(column->Flags & ImGuiTableColumnFlags_NoClipX)) + { + float content_max_x; + if (!has_freeze_v) + content_max_x = ImMax(column->ContentMaxXUnfrozen, column->ContentMaxXHeadersUsed); // No row freeze + else if (merge_group_sub_n == 0) + content_max_x = ImMax(column->ContentMaxXFrozen, column->ContentMaxXHeadersUsed); // Row freeze: use width before freeze + else + content_max_x = column->ContentMaxXUnfrozen; // Row freeze: use width after freeze + if (content_max_x > column->ClipRect.Max.x) + continue; + } + + const int merge_group_n = (has_freeze_h && column_n < table->FreezeColumnsCount ? 0 : 1) + (has_freeze_v && merge_group_sub_n == 0 ? 0 : 2); + IM_ASSERT(channel_no < IMGUI_TABLE_MAX_DRAW_CHANNELS); + MergeGroup* merge_group = &merge_groups[merge_group_n]; + if (merge_group->ChannelsCount == 0) + merge_group->ClipRect = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX); + merge_group->ChannelsMask.SetBit(channel_no); + merge_group->ChannelsCount++; + merge_group->ClipRect.Add(src_channel->_CmdBuffer[0].ClipRect); + merge_group_mask |= (1 << merge_group_n); + } + + // Invalidate current draw channel + // (we don't clear DrawChannelFrozen/DrawChannelUnfrozen solely to facilitate debugging/later inspection of data) + column->DrawChannelCurrent = (ImU8)-1; + } + + // [DEBUG] Display merge groups +#if 0 + if (g.IO.KeyShift) + for (int merge_group_n = 0; merge_group_n < IM_ARRAYSIZE(merge_groups); merge_group_n++) + { + MergeGroup* merge_group = &merge_groups[merge_group_n]; + if (merge_group->ChannelsCount == 0) + continue; + char buf[32]; + ImFormatString(buf, 32, "MG%d:%d", merge_group_n, merge_group->ChannelsCount); + ImVec2 text_pos = merge_group->ClipRect.Min + ImVec2(4, 4); + ImVec2 text_size = CalcTextSize(buf, NULL); + GetForegroundDrawList()->AddRectFilled(text_pos, text_pos + text_size, IM_COL32(0, 0, 0, 255)); + GetForegroundDrawList()->AddText(text_pos, IM_COL32(255, 255, 0, 255), buf, NULL); + GetForegroundDrawList()->AddRect(merge_group->ClipRect.Min, merge_group->ClipRect.Max, IM_COL32(255, 255, 0, 255)); + } +#endif + + // 2. Rewrite channel list in our preferred order + if (merge_group_mask != 0) + { + // We skip channel 0 (Bg0) and 1 (Bg1 frozen) from the shuffling since they won't move - see channels allocation in TableUpdateDrawChannels(). + const int LEADING_DRAW_CHANNELS = 2; + g.DrawChannelsTempMergeBuffer.resize(splitter->_Count - LEADING_DRAW_CHANNELS); // Use shared temporary storage so the allocation gets amortized + ImDrawChannel* dst_tmp = g.DrawChannelsTempMergeBuffer.Data; + ImBitArray remaining_mask; // We need 132-bit of storage + remaining_mask.ClearBits(); + remaining_mask.SetBitRange(LEADING_DRAW_CHANNELS, splitter->_Count - 1); + remaining_mask.ClearBit(table->Bg1DrawChannelUnfrozen); + IM_ASSERT(has_freeze_v == false || table->Bg1DrawChannelUnfrozen != TABLE_DRAW_CHANNEL_BG1_FROZEN); + int remaining_count = splitter->_Count - (has_freeze_v ? LEADING_DRAW_CHANNELS + 1 : LEADING_DRAW_CHANNELS); + //ImRect host_rect = (table->InnerWindow == table->OuterWindow) ? table->InnerClipRect : table->HostClipRect; + ImRect host_rect = table->HostClipRect; + for (int merge_group_n = 0; merge_group_n < IM_ARRAYSIZE(merge_groups); merge_group_n++) + { + if (int merge_channels_count = merge_groups[merge_group_n].ChannelsCount) + { + MergeGroup* merge_group = &merge_groups[merge_group_n]; + ImRect merge_clip_rect = merge_group->ClipRect; + + // Extend outer-most clip limits to match those of host, so draw calls can be merged even if + // outer-most columns have some outer padding offsetting them from their parent ClipRect. + // The principal cases this is dealing with are: + // - On a same-window table (not scrolling = single group), all fitting columns ClipRect -> will extend and match host ClipRect -> will merge + // - Columns can use padding and have left-most ClipRect.Min.x and right-most ClipRect.Max.x != from host ClipRect -> will extend and match host ClipRect -> will merge + // FIXME-TABLE FIXME-WORKRECT: We are wasting a merge opportunity on tables without scrolling if column doesn't fit + // within host clip rect, solely because of the half-padding difference between window->WorkRect and window->InnerClipRect. + if ((merge_group_n & 1) == 0 || !has_freeze_h) + merge_clip_rect.Min.x = ImMin(merge_clip_rect.Min.x, host_rect.Min.x); + if ((merge_group_n & 2) == 0 || !has_freeze_v) + merge_clip_rect.Min.y = ImMin(merge_clip_rect.Min.y, host_rect.Min.y); + if ((merge_group_n & 1) != 0) + merge_clip_rect.Max.x = ImMax(merge_clip_rect.Max.x, host_rect.Max.x); + if ((merge_group_n & 2) != 0 && (table->Flags & ImGuiTableFlags_NoHostExtendY) == 0) + merge_clip_rect.Max.y = ImMax(merge_clip_rect.Max.y, host_rect.Max.y); +#if 0 + GetOverlayDrawList()->AddRect(merge_group->ClipRect.Min, merge_group->ClipRect.Max, IM_COL32(255, 0, 0, 200), 0.0f, ~0, 1.0f); + GetOverlayDrawList()->AddLine(merge_group->ClipRect.Min, merge_clip_rect.Min, IM_COL32(255, 100, 0, 200)); + GetOverlayDrawList()->AddLine(merge_group->ClipRect.Max, merge_clip_rect.Max, IM_COL32(255, 100, 0, 200)); +#endif + remaining_count -= merge_group->ChannelsCount; + for (int n = 0; n < IM_ARRAYSIZE(remaining_mask.Storage); n++) + remaining_mask.Storage[n] &= ~merge_group->ChannelsMask.Storage[n]; + for (int n = 0; n < splitter->_Count && merge_channels_count != 0; n++) + { + // Copy + overwrite new clip rect + if (!merge_group->ChannelsMask.TestBit(n)) + continue; + merge_group->ChannelsMask.ClearBit(n); + merge_channels_count--; + + ImDrawChannel* channel = &splitter->_Channels[n]; + IM_ASSERT(channel->_CmdBuffer.Size == 1 && merge_clip_rect.Contains(ImRect(channel->_CmdBuffer[0].ClipRect))); + channel->_CmdBuffer[0].ClipRect = merge_clip_rect.ToVec4(); + memcpy(dst_tmp++, channel, sizeof(ImDrawChannel)); + } + } + + // Make sure Bg1DrawChannelUnfrozen appears in the middle of our groups (whereas Bg0 and Bg1 frozen are fixed to 0 and 1) + if (merge_group_n == 1 && has_freeze_v) + memcpy(dst_tmp++, &splitter->_Channels[table->Bg1DrawChannelUnfrozen], sizeof(ImDrawChannel)); + } + + // Append unmergeable channels that we didn't reorder at the end of the list + for (int n = 0; n < splitter->_Count && remaining_count != 0; n++) + { + if (!remaining_mask.TestBit(n)) + continue; + ImDrawChannel* channel = &splitter->_Channels[n]; + memcpy(dst_tmp++, channel, sizeof(ImDrawChannel)); + remaining_count--; + } + IM_ASSERT(dst_tmp == g.DrawChannelsTempMergeBuffer.Data + g.DrawChannelsTempMergeBuffer.Size); + memcpy(splitter->_Channels.Data + LEADING_DRAW_CHANNELS, g.DrawChannelsTempMergeBuffer.Data, (splitter->_Count - LEADING_DRAW_CHANNELS) * sizeof(ImDrawChannel)); + } +} + +// We use a default parameter of 'init_width_or_weight == -1', +// - with ImGuiTableColumnFlags_WidthFixed, width <= 0 --> init width == auto +// - with ImGuiTableColumnFlags_WidthFixed, width > 0 --> init width == manual +// - with ImGuiTableColumnFlags_WidthStretch, weight < 0 --> init weight == 1.0f +// - with ImGuiTableColumnFlags_WidthStretch, weight >= 0 --> init weight == custom +// Widths are specified _without_ CellPadding. So if you specify a width of 100.0f the column will be 100.0f+Padding*2.0f and you can fit a 100.0-wide item in it. +void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, float init_width_or_weight, ImGuiID user_id) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + IM_ASSERT(table != NULL && "Need to call TableSetupColumn() after BeginTable()!"); + IM_ASSERT(table->IsLayoutLocked == false && "Need to call call TableSetupColumn() before first row!"); + IM_ASSERT(table->DeclColumnsCount >= 0 && table->DeclColumnsCount < table->ColumnsCount && "Called TableSetupColumn() too many times!"); + + ImGuiTableColumn* column = &table->Columns[table->DeclColumnsCount]; + table->DeclColumnsCount++; + + // When passing a width automatically enforce WidthFixed policy + // (vs TableFixColumnFlags would default to WidthAlwaysAutoResize) + // (we write to FlagsIn which is a little misleading, another solution would be to pass init_width_or_weight to TableFixColumnFlags) + if ((flags & ImGuiTableColumnFlags_WidthMask_) == 0) + if ((table->Flags & ImGuiTableFlags_SizingPolicyFixedX) && (init_width_or_weight > 0.0f)) + flags |= ImGuiTableColumnFlags_WidthFixed; + + column->UserID = user_id; + column->FlagsIn = flags; + column->Flags = TableFixColumnFlags(table, column->FlagsIn); + flags = column->Flags; + + // Initialize defaults + if (flags & ImGuiTableColumnFlags_WidthStretch) + { + IM_ASSERT(init_width_or_weight != 0.0f && "Need to provide a valid weight!"); + if (init_width_or_weight < 0.0f) + init_width_or_weight = 1.0f; + } + column->InitStretchWeightOrWidth = init_width_or_weight; + if (table->IsInitializing && column->WidthRequest < 0.0f && column->StretchWeight < 0.0f) + { + // Init width or weight + if ((flags & ImGuiTableColumnFlags_WidthFixed) && init_width_or_weight > 0.0f) + { + // Disable auto-fit if a default fixed width has been specified + column->WidthRequest = init_width_or_weight; + column->AutoFitQueue = 0x00; + } + if (flags & ImGuiTableColumnFlags_WidthStretch) + column->StretchWeight = init_width_or_weight; + else + column->StretchWeight = 1.0f; + } + if (table->IsInitializing) + { + // Init default visibility/sort state + if ((flags & ImGuiTableColumnFlags_DefaultHide) && (table->SettingsLoadedFlags & ImGuiTableFlags_Hideable) == 0) + column->IsVisible = column->IsVisibleNextFrame = false; + if (flags & ImGuiTableColumnFlags_DefaultSort && (table->SettingsLoadedFlags & ImGuiTableFlags_Sortable) == 0) + { + column->SortOrder = 0; // Multiple columns using _DefaultSort will be reordered when building the sort specs. + column->SortDirection = (column->Flags & ImGuiTableColumnFlags_PreferSortDescending) ? (ImS8)ImGuiSortDirection_Descending : (ImU8)(ImGuiSortDirection_Ascending); + } + } + + // Store name (append with zero-terminator in contiguous buffer) + IM_ASSERT(column->NameOffset == -1); + if (label != NULL && label[0] != 0) + { + column->NameOffset = (ImS16)table->ColumnsNames.size(); + table->ColumnsNames.append(label, label + strlen(label) + 1); + } +} + +void ImGui::TableSetupScrollFreeze(int columns, int rows) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + IM_ASSERT(table != NULL && "Need to call TableSetupColumn() after BeginTable()!"); + IM_ASSERT(table->IsLayoutLocked == false && "Need to call TableSetupColumn() before first row!"); + IM_ASSERT(columns >= 0 && columns < IMGUI_TABLE_MAX_COLUMNS); + IM_ASSERT(rows >= 0 && rows < 128); // Arbitrary limit + + table->FreezeColumnsRequest = (table->Flags & ImGuiTableFlags_ScrollX) ? (ImS8)columns : 0; + table->FreezeColumnsCount = (table->InnerWindow->Scroll.x != 0.0f) ? table->FreezeColumnsRequest : 0; + table->FreezeRowsRequest = (table->Flags & ImGuiTableFlags_ScrollY) ? (ImS8)rows : 0; + table->FreezeRowsCount = (table->InnerWindow->Scroll.y != 0.0f) ? table->FreezeRowsRequest : 0; + table->IsUnfrozen = (table->FreezeRowsCount == 0); // Make sure this is set before TableUpdateLayout() so ImGuiListClipper can benefit from it.b +} + +// Starts into the first cell of a new row +void ImGui::TableNextRow(ImGuiTableRowFlags row_flags, float row_min_height) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + + if (table->CurrentRow == -1) + TableUpdateLayout(table); + if (table->IsInsideRow) + TableEndRow(table); + + table->LastRowFlags = table->RowFlags; + table->RowFlags = row_flags; + table->RowMinHeight = row_min_height; + TableBeginRow(table); + + // We honor min_row_height requested by user, but cannot guarantee per-row maximum height, + // because that would essentially require a unique clipping rectangle per-cell. + table->RowPosY2 += table->CellPaddingY * 2.0f; + table->RowPosY2 = ImMax(table->RowPosY2, table->RowPosY1 + row_min_height); + + // Disable output until user calls TableNextColumn() + table->InnerWindow->SkipItems = true; +} + +// [Internal] +void ImGui::TableBeginRow(ImGuiTable* table) +{ + ImGuiWindow* window = table->InnerWindow; + IM_ASSERT(!table->IsInsideRow); + + // New row + table->CurrentRow++; + table->CurrentColumn = -1; + table->RowBgColor[0] = table->RowBgColor[1] = IM_COL32_DISABLE; + table->RowCellDataCurrent = -1; + table->IsInsideRow = true; + + // Begin frozen rows + float next_y1 = table->RowPosY2; + if (table->CurrentRow == 0 && table->FreezeRowsCount > 0) + next_y1 = window->DC.CursorPos.y = table->OuterRect.Min.y; + + table->RowPosY1 = table->RowPosY2 = next_y1; + table->RowTextBaseline = 0.0f; + table->RowIndentOffsetX = window->DC.Indent.x - table->HostIndentX; // Lock indent + window->DC.PrevLineTextBaseOffset = 0.0f; + window->DC.CursorMaxPos.y = next_y1; + + // Making the header BG color non-transparent will allow us to overlay it multiple times when handling smooth dragging. + if (table->RowFlags & ImGuiTableRowFlags_Headers) + { + TableSetBgColor(ImGuiTableBgTarget_RowBg0, GetColorU32(ImGuiCol_TableHeaderBg)); + if (table->CurrentRow == 0) + table->IsUsingHeaders = true; + } +} + +// [Internal] +void ImGui::TableEndRow(ImGuiTable* table) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + IM_ASSERT(window == table->InnerWindow); + IM_ASSERT(table->IsInsideRow); + + if (table->CurrentColumn != -1) + TableEndCell(table); + + // Position cursor at the bottom of our row so it can be used for e.g. clipping calculation. However it is + // likely that the next call to TableBeginCell() will reposition the cursor to take account of vertical padding. + window->DC.CursorPos.y = table->RowPosY2; + + // Row background fill + const float bg_y1 = table->RowPosY1; + const float bg_y2 = table->RowPosY2; + + const bool unfreeze_rows_actual = (table->CurrentRow + 1 == table->FreezeRowsCount); + const bool unfreeze_rows_request = (table->CurrentRow + 1 == table->FreezeRowsRequest); + if (table->CurrentRow == 0) + table->LastFirstRowHeight = bg_y2 - bg_y1; + + const bool is_visible = (bg_y2 >= table->InnerClipRect.Min.y && bg_y1 <= table->InnerClipRect.Max.y); + if (is_visible) + { + // Decide of background color for the row + ImU32 bg_col0 = 0; + ImU32 bg_col1 = 0; + if (table->RowBgColor[0] != IM_COL32_DISABLE) + bg_col0 = table->RowBgColor[0]; + else if (table->Flags & ImGuiTableFlags_RowBg) + bg_col0 = GetColorU32((table->RowBgColorCounter & 1) ? ImGuiCol_TableRowBgAlt : ImGuiCol_TableRowBg); + if (table->RowBgColor[1] != IM_COL32_DISABLE) + bg_col1 = table->RowBgColor[1]; + + // Decide of top border color + ImU32 border_col = 0; + const float border_size = TABLE_BORDER_SIZE; + if (table->CurrentRow > 0 || table->InnerWindow == table->OuterWindow) + if (table->Flags & ImGuiTableFlags_BordersInnerH) + border_col = (table->LastRowFlags & ImGuiTableRowFlags_Headers) ? table->BorderColorStrong : table->BorderColorLight; + + const bool draw_cell_bg_color = table->RowCellDataCurrent >= 0; + const bool draw_strong_bottom_border = unfreeze_rows_actual; + if ((bg_col0 | bg_col1 | border_col) != 0 || draw_strong_bottom_border || draw_cell_bg_color) + { + // In theory we could call SetWindowClipRectBeforeSetChannel() but since we know TableEndRow() is + // always followed by a change of clipping rectangle we perform the smallest overwrite possible here. + if ((table->Flags & ImGuiTableFlags_NoClip) == 0) + window->DrawList->_CmdHeader.ClipRect = table->BgClipRectForDrawCmd.ToVec4(); + table->DrawSplitter.SetCurrentChannel(window->DrawList, TABLE_DRAW_CHANNEL_BG0); + } + + // Draw row background + // We soft/cpu clip this so all backgrounds and borders can share the same clipping rectangle + if (bg_col0 || bg_col1) + { + ImRect row_rect(table->WorkRect.Min.x, bg_y1, table->WorkRect.Max.x, bg_y2); + row_rect.ClipWith(table->BgClipRect); + if (bg_col0 != 0 && row_rect.Min.y < row_rect.Max.y) + window->DrawList->AddRectFilled(row_rect.Min, row_rect.Max, bg_col0); + if (bg_col1 != 0 && row_rect.Min.y < row_rect.Max.y) + window->DrawList->AddRectFilled(row_rect.Min, row_rect.Max, bg_col1); + } + + // Draw cell background color + if (draw_cell_bg_color) + { + ImGuiTableCellData* cell_data_end = &table->RowCellData[table->RowCellDataCurrent]; + for (ImGuiTableCellData* cell_data = &table->RowCellData[0]; cell_data <= cell_data_end; cell_data++) + { + const ImGuiTableColumn* column = &table->Columns[cell_data->Column]; + ImRect cell_bg_rect = TableGetCellBgRect(table, cell_data->Column); + cell_bg_rect.ClipWith(table->BgClipRect); + cell_bg_rect.Min.x = ImMax(cell_bg_rect.Min.x, column->ClipRect.Min.x); // So that first column after frozen one gets clipped + cell_bg_rect.Max.x = ImMin(cell_bg_rect.Max.x, column->ClipRect.Max.x); + window->DrawList->AddRectFilled(cell_bg_rect.Min, cell_bg_rect.Max, cell_data->BgColor); + } + } + + // Draw top border + if (border_col && bg_y1 >= table->BgClipRect.Min.y && bg_y1 < table->BgClipRect.Max.y) + window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y1), ImVec2(table->BorderX2, bg_y1), border_col, border_size); + + // Draw bottom border at the row unfreezing mark (always strong) + if (draw_strong_bottom_border && bg_y2 >= table->BgClipRect.Min.y && bg_y2 < table->BgClipRect.Max.y) + window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y2), ImVec2(table->BorderX2, bg_y2), table->BorderColorStrong, border_size); + } + + // End frozen rows (when we are past the last frozen row line, teleport cursor and alter clipping rectangle) + // We need to do that in TableEndRow() instead of TableBeginRow() so the list clipper can mark end of row and + // get the new cursor position. + if (unfreeze_rows_request) + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + column->NavLayerCurrent = (ImS8)((column_n < table->FreezeColumnsCount) ? ImGuiNavLayer_Menu : ImGuiNavLayer_Main); + } + if (unfreeze_rows_actual) + { + IM_ASSERT(table->IsUnfrozen == false); + table->IsUnfrozen = true; + + // BgClipRect starts as table->InnerClipRect, reduce it now and make BgClipRectForDrawCmd == BgClipRect + float y0 = ImMax(table->RowPosY2 + 1, window->InnerClipRect.Min.y); + table->BgClipRect.Min.y = table->BgClipRectForDrawCmd.Min.y = ImMin(y0, window->InnerClipRect.Max.y); + table->BgClipRect.Max.y = table->BgClipRectForDrawCmd.Max.y = window->InnerClipRect.Max.y; + table->Bg1DrawChannelCurrent = table->Bg1DrawChannelUnfrozen; + IM_ASSERT(table->BgClipRectForDrawCmd.Min.y <= table->BgClipRectForDrawCmd.Max.y); + + float row_height = table->RowPosY2 - table->RowPosY1; + table->RowPosY2 = window->DC.CursorPos.y = table->WorkRect.Min.y + table->RowPosY2 - table->OuterRect.Min.y; + table->RowPosY1 = table->RowPosY2 - row_height; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + column->DrawChannelCurrent = column->DrawChannelUnfrozen; + column->ClipRect.Min.y = table->BgClipRectForDrawCmd.Min.y; + } + + // Update cliprect ahead of TableBeginCell() so clipper can access to new ClipRect->Min.y + SetWindowClipRectBeforeSetChannel(window, table->Columns[0].ClipRect); + table->DrawSplitter.SetCurrentChannel(window->DrawList, table->Columns[0].DrawChannelCurrent); + } + + if (!(table->RowFlags & ImGuiTableRowFlags_Headers)) + table->RowBgColorCounter++; + table->IsInsideRow = false; +} + +// [Internal] Called by TableNextColumn()! +// This is called very frequently, so we need to be mindful of unnecessary overhead. +// FIXME-TABLE FIXME-OPT: Could probably shortcut some things for non-active or clipped columns. +void ImGui::TableBeginCell(ImGuiTable* table, int column_n) +{ + ImGuiTableColumn* column = &table->Columns[column_n]; + ImGuiWindow* window = table->InnerWindow; + table->CurrentColumn = column_n; + + // Start position is roughly ~~ CellRect.Min + CellPadding + Indent + float start_x = column->WorkMinX; + if (column->Flags & ImGuiTableColumnFlags_IndentEnable) + start_x += table->RowIndentOffsetX; // ~~ += window.DC.Indent.x - table->HostIndentX, except we locked it for the row. + + window->DC.CursorPos.x = start_x; + window->DC.CursorPos.y = table->RowPosY1 + table->CellPaddingY; + window->DC.CursorMaxPos.x = window->DC.CursorPos.x; + window->DC.ColumnsOffset.x = start_x - window->Pos.x - window->DC.Indent.x; // FIXME-WORKRECT + window->DC.CurrLineTextBaseOffset = table->RowTextBaseline; + window->DC.LastItemId = 0; + window->DC.NavLayerCurrent = (ImGuiNavLayer)column->NavLayerCurrent; + + window->WorkRect.Min.y = window->DC.CursorPos.y; + window->WorkRect.Min.x = column->WorkMinX; + window->WorkRect.Max.x = column->WorkMaxX; + + // To allow ImGuiListClipper to function we propagate our row height + if (!column->IsVisible) + window->DC.CursorPos.y = ImMax(window->DC.CursorPos.y, table->RowPosY2); + + window->SkipItems = column->IsSkipItems; + if (table->Flags & ImGuiTableFlags_NoClip) + { + // FIXME: if we end up drawing all borders/bg in EndTable, could remove this and just assert that channel hasn't changed. + table->DrawSplitter.SetCurrentChannel(window->DrawList, TABLE_DRAW_CHANNEL_UNCLIPPED); + //IM_ASSERT(table->DrawSplitter._Current == TABLE_DRAW_CHANNEL_UNCLIPPED); + } + else + { + // FIXME-TABLE: Could avoid this if draw channel is dummy channel? + SetWindowClipRectBeforeSetChannel(window, column->ClipRect); + table->DrawSplitter.SetCurrentChannel(window->DrawList, column->DrawChannelCurrent); + } +} + +// [Internal] Called by TableNextRow()/TableNextColumn()! +void ImGui::TableEndCell(ImGuiTable* table) +{ + ImGuiTableColumn* column = &table->Columns[table->CurrentColumn]; + ImGuiWindow* window = table->InnerWindow; + + // Report maximum position so we can infer content size per column. + float* p_max_pos_x; + if (table->RowFlags & ImGuiTableRowFlags_Headers) + p_max_pos_x = &column->ContentMaxXHeadersUsed; // Useful in case user submit contents in header row that is not a TableHeader() call + else + p_max_pos_x = table->IsUnfrozen ? &column->ContentMaxXUnfrozen : &column->ContentMaxXFrozen; + *p_max_pos_x = ImMax(*p_max_pos_x, window->DC.CursorMaxPos.x); + table->RowPosY2 = ImMax(table->RowPosY2, window->DC.CursorMaxPos.y + table->CellPaddingY); + + // Propagate text baseline for the entire row + // FIXME-TABLE: Here we propagate text baseline from the last line of the cell.. instead of the first one. + table->RowTextBaseline = ImMax(table->RowTextBaseline, window->DC.PrevLineTextBaseOffset); +} + +// Append into the next column/cell +// FIXME-TABLE: Wrapping to next row could be optional? +bool ImGui::TableNextColumn() +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + if (!table) + return false; + + if (table->IsInsideRow && table->CurrentColumn + 1 < table->ColumnsCount) + { + if (table->CurrentColumn != -1) + TableEndCell(table); + TableBeginCell(table, table->CurrentColumn + 1); + } + else + { + TableNextRow(); + TableBeginCell(table, 0); + } + + // FIXME-TABLE: it is likely to alter layout if user skips a columns contents based on clipping. + int column_n = table->CurrentColumn; + return (table->VisibleUnclippedMaskByIndex & ((ImU64)1 << column_n)) != 0; +} + +bool ImGui::TableSetColumnIndex(int column_n) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + if (!table) + return false; + + if (table->CurrentColumn != column_n) + { + if (table->CurrentColumn != -1) + TableEndCell(table); + IM_ASSERT(column_n >= 0 && table->ColumnsCount); + TableBeginCell(table, column_n); + } + + // FIXME-TABLE: it is likely to alter layout if user skips a columns contents based on clipping. + return (table->VisibleUnclippedMaskByIndex & ((ImU64)1 << column_n)) != 0; +} + +int ImGui::TableGetColumnCount() +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + return table ? table->ColumnsCount : 0; +} + +const char* ImGui::TableGetColumnName(int column_n) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + if (!table) + return NULL; + if (column_n < 0) + column_n = table->CurrentColumn; + return TableGetColumnName(table, column_n); +} + +// We expose "Visible and Unclipped" to the user, vs our internal "Visible" state which is !Hidden +bool ImGui::TableGetColumnIsVisible(int column_n) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + if (!table) + return false; + if (column_n < 0) + column_n = table->CurrentColumn; + return (table->VisibleUnclippedMaskByIndex & ((ImU64)1 << column_n)) != 0; +} + +int ImGui::TableGetColumnIndex() +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + if (!table) + return 0; + return table->CurrentColumn; +} + +// Return the cell rectangle based on currently known height. +// - Important: we generally don't know our row height until the end of the row, so Max.y will be incorrect in many situations. +// The only case where this is correct is if we provided a min_row_height to TableNextRow() and don't go below it. +// - Important: if ImGuiTableFlags_PadOuterX is set but ImGuiTableFlags_PadInnerX is not set, the outer-most left and right +// columns report a small offset so their CellBgRect can extend up to the outer border. +ImRect ImGui::TableGetCellBgRect(const ImGuiTable* table, int column_n) +{ + const ImGuiTableColumn* column = &table->Columns[column_n]; + float x1 = column->MinX; + float x2 = column->MaxX; + if (column->PrevVisibleColumn == -1) + x1 -= table->CellSpacingX1; + if (column->NextVisibleColumn == -1) + x2 += table->CellSpacingX2; + return ImRect(x1, table->RowPosY1, x2, table->RowPosY2); +} + +const char* ImGui::TableGetColumnName(const ImGuiTable* table, int column_n) +{ + const ImGuiTableColumn* column = &table->Columns[column_n]; + if (column->NameOffset == -1) + return ""; + return &table->ColumnsNames.Buf[column->NameOffset]; +} + +// Return the resizing ID for the right-side of the given column. +ImGuiID ImGui::TableGetColumnResizeID(const ImGuiTable* table, int column_n, int instance_no) +{ + IM_ASSERT(column_n < table->ColumnsCount); + ImGuiID id = table->ID + 1 + (instance_no * table->ColumnsCount) + column_n; + return id; +} + +void ImGui::TableSetColumnAutofit(ImGuiTable* table, int column_n) +{ + // Disable clipping then auto-fit, will take 2 frames + // (we don't take a shortcut for unclipped columns to reduce inconsistencies when e.g. resizing multiple columns) + ImGuiTableColumn* column = &table->Columns[column_n]; + column->CannotSkipItemsQueue = (1 << 0); + column->AutoFitQueue = (1 << 1); +} + +void ImGui::PushTableBackground() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiTable* table = g.CurrentTable; + + // Optimization: avoid SetCurrentChannel() + PushClipRect() + table->HostBackupClipRect = window->ClipRect; + SetWindowClipRectBeforeSetChannel(window, table->BgClipRectForDrawCmd); + table->DrawSplitter.SetCurrentChannel(window->DrawList, table->Bg1DrawChannelCurrent); +} + +void ImGui::PopTableBackground() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiTable* table = g.CurrentTable; + ImGuiTableColumn* column = &table->Columns[table->CurrentColumn]; + + // Optimization: avoid PopClipRect() + SetCurrentChannel() + SetWindowClipRectBeforeSetChannel(window, table->HostBackupClipRect); + table->DrawSplitter.SetCurrentChannel(window->DrawList, column->DrawChannelCurrent); +} + +// Output context menu into current window (generally a popup) +// FIXME-TABLE: Ideally this should be writable by the user. Full programmatic access to that data? +void ImGui::TableDrawContextMenu(ImGuiTable* table) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return; + + bool want_separator = false; + const int column_n = (table->ContextPopupColumn >= 0 && table->ContextPopupColumn < table->ColumnsCount) ? table->ContextPopupColumn : -1; + ImGuiTableColumn* column = (column_n != -1) ? &table->Columns[column_n] : NULL; + + // Sizing + if (table->Flags & ImGuiTableFlags_Resizable) + { + if (column != NULL) + { + const bool can_resize = !(column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_WidthStretch)) && column->IsVisible; + if (MenuItem("Size column to fit", NULL, false, can_resize)) + TableSetColumnAutofit(table, column_n); + } + + if (MenuItem("Size all columns to fit", NULL)) + { + for (int other_column_n = 0; other_column_n < table->ColumnsCount; other_column_n++) + { + ImGuiTableColumn* other_column = &table->Columns[other_column_n]; + if (other_column->IsVisible) + TableSetColumnAutofit(table, other_column_n); + } + } + want_separator = true; + } + + // Ordering + if (table->Flags & ImGuiTableFlags_Reorderable) + { + if (MenuItem("Reset order", NULL, false, !table->IsDefaultDisplayOrder)) + table->IsResetDisplayOrderRequest = true; + want_separator = true; + } + + // Sorting + // (modify TableOpenContextMenu() to add _Sortable flag if enabling this) +#if 0 + if ((table->Flags & ImGuiTableFlags_Sortable) && column != NULL && (column->Flags & ImGuiTableColumnFlags_NoSort) == 0) + { + if (want_separator) + Separator(); + want_separator = true; + + bool append_to_sort_specs = g.IO.KeyShift; + if (MenuItem("Sort in Ascending Order", NULL, column->SortOrder != -1 && column->SortDirection == ImGuiSortDirection_Ascending, (column->Flags & ImGuiTableColumnFlags_NoSortAscending) == 0)) + TableSetColumnSortDirection(table, column_n, ImGuiSortDirection_Ascending, append_to_sort_specs); + if (MenuItem("Sort in Descending Order", NULL, column->SortOrder != -1 && column->SortDirection == ImGuiSortDirection_Descending, (column->Flags & ImGuiTableColumnFlags_NoSortDescending) == 0)) + TableSetColumnSortDirection(table, column_n, ImGuiSortDirection_Descending, append_to_sort_specs); + } +#endif + + // Hiding / Visibility + if (table->Flags & ImGuiTableFlags_Hideable) + { + if (want_separator) + Separator(); + want_separator = true; + + PushItemFlag(ImGuiItemFlags_SelectableDontClosePopup, true); + for (int other_column_n = 0; other_column_n < table->ColumnsCount; other_column_n++) + { + ImGuiTableColumn* other_column = &table->Columns[other_column_n]; + const char* name = TableGetColumnName(table, other_column_n); + if (name == NULL || name[0] == 0) + name = ""; + + // Make sure we can't hide the last active column + bool menu_item_active = (other_column->Flags & ImGuiTableColumnFlags_NoHide) ? false : true; + if (other_column->IsVisible && table->ColumnsVisibleCount <= 1) + menu_item_active = false; + if (MenuItem(name, NULL, other_column->IsVisible, menu_item_active)) + other_column->IsVisibleNextFrame = !other_column->IsVisible; + } + PopItemFlag(); + } +} + +// Use -1 to open menu not specific to a given column. +void ImGui::TableOpenContextMenu(int column_n) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + if (column_n == -1 && table->CurrentColumn != -1) // When called within a column automatically use this one (for consistency) + column_n = table->CurrentColumn; + if (column_n == table->ColumnsCount) // To facilitate using with TableGetHoveredColumn() + column_n = -1; + IM_ASSERT(column_n >= -1 && column_n < table->ColumnsCount); + if (table->Flags & (ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable)) + { + table->IsContextPopupOpen = true; + table->ContextPopupColumn = (ImS8)column_n; + table->InstanceInteracted = table->InstanceCurrent; + const ImGuiID context_menu_id = ImHashStr("##ContextMenu", 0, table->ID); + OpenPopupEx(context_menu_id, ImGuiPopupFlags_None); + } +} + +// This is a helper to output TableHeader() calls based on the column names declared in TableSetupColumn(). +// The intent is that advanced users willing to create customized headers would not need to use this helper +// and can create their own! For example: TableHeader() may be preceeded by Checkbox() or other custom widgets. +// See 'Demo->Tables->Custom headers' for a demonstration of implementing a custom version of this. +void ImGui::TableHeadersRow() +{ + ImGuiStyle& style = ImGui::GetStyle(); + + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + IM_ASSERT(table != NULL && "Need to call TableHeadersRow() after BeginTable()!"); + + // Calculate row height (for the unlikely case that labels may be are multi-line) + // If we didn't do that, uneven header height would work but their highlight won't cover the full row height. + float row_height = GetTextLineHeight(); + const float row_y1 = GetCursorScreenPos().y; + const int columns_count = TableGetColumnCount(); + for (int column_n = 0; column_n < columns_count; column_n++) + if (TableGetColumnIsVisible(column_n)) + row_height = ImMax(row_height, CalcTextSize(TableGetColumnName(column_n)).y); + row_height += style.CellPadding.y * 2.0f; + + // Open row + TableNextRow(ImGuiTableRowFlags_Headers, row_height); + if (table->HostSkipItems) // Merely an optimization, you may skip in your own code. + return; + + // This for loop is constructed to not make use of internal functions, + // as this is intended to be a base template to copy and build from. + for (int column_n = 0; column_n < columns_count; column_n++) + { + if (!TableSetColumnIndex(column_n)) + continue; + + // [DEBUG] Test custom user elements +#if 0 + if (column_n < 2) + { + static bool b[2] = {}; + PushID(column_n); + PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + Checkbox("##", &b[column_n]); + PopStyleVar(); + PopID(); + SameLine(0.0f, style.ItemInnerSpacing.x); + } +#endif + + // Push an id to allow unnamed labels (generally accidental, but let's behave nicely with them) + // - in your own code you may omit the PushID/PopID all-together, provided you know they won't collide + // - table->InstanceCurrent is only >0 when we use multiple BeginTable/EndTable calls with same identifier. + const char* name = TableGetColumnName(column_n); + PushID(table->InstanceCurrent * table->ColumnsCount + column_n); + TableHeader(name); + PopID(); + } + + // Allow opening popup from the right-most section after the last column. + // FIXME-TABLE: TableOpenContextMenu() is not public yet. + ImVec2 mouse_pos = ImGui::GetMousePos(); + if (IsMouseReleased(1) && TableGetHoveredColumn() == columns_count) + if (mouse_pos.y >= row_y1 && mouse_pos.y < row_y1 + row_height) + TableOpenContextMenu(-1); // Will open a non-column-specific popup. +} + +// Emit a column header (text + optional sort order) +// We cpu-clip text here so that all columns headers can be merged into a same draw call. +// Note that because of how we cpu-clip and display sorting indicators, you _cannot_ use SameLine() after a TableHeader() +// FIXME-TABLE: Style confusion between CellPadding.y and FramePadding.y +void ImGui::TableHeader(const char* label) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return; + + ImGuiTable* table = g.CurrentTable; + IM_ASSERT(table != NULL && "Need to call TableHeader() after BeginTable()!"); + IM_ASSERT(table->CurrentColumn != -1); + const int column_n = table->CurrentColumn; + ImGuiTableColumn* column = &table->Columns[column_n]; + + // Label + if (label == NULL) + label = ""; + const char* label_end = FindRenderedTextEnd(label); + ImVec2 label_size = CalcTextSize(label, label_end, true); + ImVec2 label_pos = window->DC.CursorPos; + + // If we already got a row height, there's use that. + // FIXME-TABLE-PADDING: Problem if the correct outer-padding CellBgRect strays off our ClipRect + ImRect cell_r = TableGetCellBgRect(table, column_n); + float label_height = ImMax(label_size.y, table->RowMinHeight - table->CellPaddingY * 2.0f); + + // Keep header highlighted when context menu is open. + const bool selected = (table->IsContextPopupOpen && table->ContextPopupColumn == column_n && table->InstanceInteracted == table->InstanceCurrent); + ImGuiID id = window->GetID(label); + ImRect bb(cell_r.Min.x, cell_r.Min.y, cell_r.Max.x, ImMax(cell_r.Max.y, cell_r.Min.y + label_height + g.Style.CellPadding.y * 2.0f)); + ItemSize(ImVec2(0.0f, label_height)); // Don't declare unclipped width, it'll be fed ContentMaxPosHeadersIdeal + if (!ItemAdd(bb, id)) + return; + + //GetForegroundDrawList()->AddRect(cell_r.Min, cell_r.Max, IM_COL32(255, 0, 0, 255)); // [DEBUG] + //GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(255, 0, 0, 255)); // [DEBUG] + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None); + if (hovered || selected) + { + const ImU32 col = GetColorU32(held ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + //RenderFrame(bb.Min, bb.Max, col, false, 0.0f); + TableSetBgColor(ImGuiTableBgTarget_CellBg, col, table->CurrentColumn); + RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding); + } + if (held) + table->HeldHeaderColumn = (ImS8)column_n; + window->DC.CursorPos.y -= g.Style.ItemSpacing.y * 0.5f; + + // Drag and drop to re-order columns. + // FIXME-TABLE: Scroll request while reordering a column and it lands out of the scrolling zone. + if (held && (table->Flags & ImGuiTableFlags_Reorderable) && IsMouseDragging(0) && !g.DragDropActive) + { + // While moving a column it will jump on the other side of the mouse, so we also test for MouseDelta.x + table->ReorderColumn = (ImS8)column_n; + table->InstanceInteracted = table->InstanceCurrent; + + // We don't reorder: through the frozen<>unfrozen line, or through a column that is marked with ImGuiTableColumnFlags_NoReorder. + if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < cell_r.Min.x) + if (ImGuiTableColumn* prev_column = (column->PrevVisibleColumn != -1) ? &table->Columns[column->PrevVisibleColumn] : NULL) + if (!((column->Flags | prev_column->Flags) & ImGuiTableColumnFlags_NoReorder)) + if ((column->IndexWithinVisibleSet < table->FreezeColumnsRequest) == (prev_column->IndexWithinVisibleSet < table->FreezeColumnsRequest)) + table->ReorderColumnDir = -1; + if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > cell_r.Max.x) + if (ImGuiTableColumn* next_column = (column->NextVisibleColumn != -1) ? &table->Columns[column->NextVisibleColumn] : NULL) + if (!((column->Flags | next_column->Flags) & ImGuiTableColumnFlags_NoReorder)) + if ((column->IndexWithinVisibleSet < table->FreezeColumnsRequest) == (next_column->IndexWithinVisibleSet < table->FreezeColumnsRequest)) + table->ReorderColumnDir = +1; + } + + // Sort order arrow + float w_arrow = 0.0f; + float w_sort_text = 0.0f; + float ellipsis_max = cell_r.Max.x; + if ((table->Flags & ImGuiTableFlags_Sortable) && !(column->Flags & ImGuiTableColumnFlags_NoSort)) + { + const float ARROW_SCALE = 0.65f; + w_arrow = ImFloor(g.FontSize * ARROW_SCALE + g.Style.FramePadding.x);// table->CellPadding.x); + if (column->SortOrder != -1) + { + w_sort_text = 0.0f; + + char sort_order_suf[8]; + if (column->SortOrder > 0) + { + ImFormatString(sort_order_suf, IM_ARRAYSIZE(sort_order_suf), "%d", column->SortOrder + 1); + w_sort_text = g.Style.ItemInnerSpacing.x + CalcTextSize(sort_order_suf).x; + } + + float x = ImMax(cell_r.Min.x, cell_r.Max.x - w_arrow - w_sort_text); + ellipsis_max -= w_arrow + w_sort_text; + + float y = label_pos.y; + ImU32 col = GetColorU32(ImGuiCol_Text); + if (column->SortOrder > 0) + { + PushStyleColor(ImGuiCol_Text, GetColorU32(ImGuiCol_Text, 0.70f)); + RenderText(ImVec2(x + g.Style.ItemInnerSpacing.x, y), sort_order_suf); + PopStyleColor(); + x += w_sort_text; + } + RenderArrow(window->DrawList, ImVec2(x, y), col, column->SortDirection == ImGuiSortDirection_Ascending ? ImGuiDir_Up : ImGuiDir_Down, ARROW_SCALE); + } + + // Handle clicking on column header to adjust Sort Order + if (pressed && table->ReorderColumn != column_n) + { + // Set new sort direction + // - If the PreferSortDescending flag is set, we will default to a Descending direction on the first click. + // - Note that the PreferSortAscending flag is never checked, it is essentially the default and therefore a no-op. + ImGuiSortDirection sort_direction; + if (column->SortOrder == -1) + sort_direction = (column->Flags & ImGuiTableColumnFlags_PreferSortDescending) ? ImGuiSortDirection_Descending : ImGuiSortDirection_Ascending; + else + sort_direction = (column->SortDirection == ImGuiSortDirection_Ascending) ? ImGuiSortDirection_Descending : ImGuiSortDirection_Ascending; + TableSetColumnSortDirection(column_n, sort_direction, g.IO.KeyShift); + } + } + + // Render clipped label. Clipping here ensure that in the majority of situations, all our header cells will + // be merged into a single draw call. + //window->DrawList->AddCircleFilled(ImVec2(ellipsis_max, label_pos.y), 40, IM_COL32_WHITE); + RenderTextEllipsis(window->DrawList, label_pos, ImVec2(ellipsis_max, label_pos.y + label_height + g.Style.FramePadding.y), ellipsis_max, ellipsis_max, label, label_end, &label_size); + + const bool text_clipped = label_size.x > (ellipsis_max - label_pos.x); + if (text_clipped && hovered && g.HoveredIdNotActiveTimer > TOOLTIP_DELAY) + SetTooltip("%.*s", (int)(label_end - label), label); + + // We feed our unclipped width to the column without writing on CursorMaxPos, so that column is still considering for merging. + float max_pos_x = label_pos.x + label_size.x + w_sort_text + w_arrow; + column->ContentMaxXHeadersUsed = ImMax(column->ContentMaxXHeadersUsed, cell_r.Max.x); + column->ContentMaxXHeadersIdeal = ImMax(column->ContentMaxXHeadersIdeal, max_pos_x); + + // We don't use BeginPopupContextItem() because we want the popup to stay up even after the column is hidden + if (IsMouseReleased(1) && IsItemHovered()) + TableOpenContextMenu(column_n); +} + +// Note that the NoSortAscending/NoSortDescending flags are processed in TableSortSpecsSanitize(), and they may change/revert +// the value of SortDirection. We could technically also do it here but it would be unnecessary and duplicate code. +void ImGui::TableSetColumnSortDirection(int column_n, ImGuiSortDirection sort_direction, bool append_to_sort_specs) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + + if (!(table->Flags & ImGuiTableFlags_MultiSortable)) + append_to_sort_specs = false; + + ImS8 sort_order_max = 0; + if (append_to_sort_specs) + for (int other_column_n = 0; other_column_n < table->ColumnsCount; other_column_n++) + sort_order_max = ImMax(sort_order_max, table->Columns[other_column_n].SortOrder); + + ImGuiTableColumn* column = &table->Columns[column_n]; + column->SortDirection = (ImS8)sort_direction; + if (column->SortOrder == -1 || !append_to_sort_specs) + column->SortOrder = append_to_sort_specs ? sort_order_max + 1 : 0; + + for (int other_column_n = 0; other_column_n < table->ColumnsCount; other_column_n++) + { + ImGuiTableColumn* other_column = &table->Columns[other_column_n]; + if (other_column != column && !append_to_sort_specs) + other_column->SortOrder = -1; + TableFixColumnSortDirection(other_column); + } + table->IsSettingsDirty = true; + table->IsSortSpecsDirty = true; +} + +// Return NULL if no sort specs (most often when ImGuiTableFlags_Sortable is not set) +// You can sort your data again when 'SpecsChanged == true'. It will be true with sorting specs have changed since +// last call, or the first time. +// Lifetime: don't hold on this pointer over multiple frames or past any subsequent call to BeginTable()! +ImGuiTableSortSpecs* ImGui::TableGetSortSpecs() +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + IM_ASSERT(table != NULL); + + if (!(table->Flags & ImGuiTableFlags_Sortable)) + return NULL; + + if (table->IsSortSpecsDirty) + TableSortSpecsBuild(table); + + return table->SortSpecs.SpecsCount ? &table->SortSpecs : NULL; +} + +bool ImGui::TableGetColumnIsSorted(int column_n) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + if (!table) + return false; + if (column_n < 0) + column_n = table->CurrentColumn; + ImGuiTableColumn* column = &table->Columns[column_n]; + return (column->SortOrder != -1); +} + +// Return -1 when table is not hovered. return columns_count if the unused space at the right of visible columns is hovered. +int ImGui::TableGetHoveredColumn() +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + if (!table) + return -1; + return (int)table->HoveredColumnBody; +} + +void ImGui::TableSetBgColor(ImGuiTableBgTarget bg_target, ImU32 color, int column_n) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + IM_ASSERT(bg_target != ImGuiTableBgTarget_None); + + if (color == IM_COL32_DISABLE) + color = 0; + + // We cannot draw neither the cell or row background immediately as we don't know the row height at this point in time. + switch (bg_target) + { + case ImGuiTableBgTarget_CellBg: + { + if (table->RowPosY1 > table->InnerClipRect.Max.y) // Discard + return; + if (column_n == -1) + column_n = table->CurrentColumn; + if ((table->VisibleUnclippedMaskByIndex & ((ImU64)1 << column_n)) == 0) + return; + if (table->RowCellDataCurrent < 0 || table->RowCellData[table->RowCellDataCurrent].Column != column_n) + table->RowCellDataCurrent++; + ImGuiTableCellData* cell_data = &table->RowCellData[table->RowCellDataCurrent]; + cell_data->BgColor = color; + cell_data->Column = (ImS8)column_n; + break; + } + case ImGuiTableBgTarget_RowBg0: + case ImGuiTableBgTarget_RowBg1: + { + if (table->RowPosY1 > table->InnerClipRect.Max.y) // Discard + return; + IM_ASSERT(column_n == -1); + int bg_idx = (bg_target == ImGuiTableBgTarget_RowBg1) ? 1 : 0; + table->RowBgColor[bg_idx] = color; + break; + } + default: + IM_ASSERT(0); + } +} + +void ImGui::TableSortSpecsSanitize(ImGuiTable* table) +{ + IM_ASSERT(table->Flags & ImGuiTableFlags_Sortable); + + // Clear SortOrder from hidden column and verify that there's no gap or duplicate. + int sort_order_count = 0; + ImU64 sort_order_mask = 0x00; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + if (column->SortOrder != -1 && !column->IsVisible) + column->SortOrder = -1; + if (column->SortOrder == -1) + continue; + sort_order_count++; + sort_order_mask |= ((ImU64)1 << column->SortOrder); + IM_ASSERT(sort_order_count < (int)sizeof(sort_order_mask) * 8); + } + + const bool need_fix_linearize = ((ImU64)1 << sort_order_count) != (sort_order_mask + 1); + const bool need_fix_single_sort_order = (sort_order_count > 1) && !(table->Flags & ImGuiTableFlags_MultiSortable); + if (need_fix_linearize || need_fix_single_sort_order) + { + ImU64 fixed_mask = 0x00; + for (int sort_n = 0; sort_n < sort_order_count; sort_n++) + { + // Fix: Rewrite sort order fields if needed so they have no gap or duplicate. + // (e.g. SortOrder 0 disappeared, SortOrder 1..2 exists --> rewrite then as SortOrder 0..1) + int column_with_smallest_sort_order = -1; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + if ((fixed_mask & ((ImU64)1 << (ImU64)column_n)) == 0 && table->Columns[column_n].SortOrder != -1) + if (column_with_smallest_sort_order == -1 || table->Columns[column_n].SortOrder < table->Columns[column_with_smallest_sort_order].SortOrder) + column_with_smallest_sort_order = column_n; + IM_ASSERT(column_with_smallest_sort_order != -1); + fixed_mask |= ((ImU64)1 << column_with_smallest_sort_order); + table->Columns[column_with_smallest_sort_order].SortOrder = (ImS8)sort_n; + + // Fix: Make sure only one column has a SortOrder if ImGuiTableFlags_MultiSortable is not set. + if (need_fix_single_sort_order) + { + sort_order_count = 1; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + if (column_n != column_with_smallest_sort_order) + table->Columns[column_n].SortOrder = -1; + break; + } + } + } + + // Fallback default sort order (if no column had the ImGuiTableColumnFlags_DefaultSort flag) + if (sort_order_count == 0) + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + if (column->IsVisible && !(column->Flags & ImGuiTableColumnFlags_NoSort)) + { + sort_order_count = 1; + column->SortOrder = 0; + TableFixColumnSortDirection(column); + break; + } + } + + table->SortSpecsCount = (ImS8)sort_order_count; +} + +void ImGui::TableSortSpecsBuild(ImGuiTable* table) +{ + IM_ASSERT(table->IsSortSpecsDirty); + TableSortSpecsSanitize(table); + + // Write output + table->SortSpecsData.resize(table->SortSpecsCount); + table->SortSpecs.ColumnsMask = 0x00; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + if (column->SortOrder == -1) + continue; + ImGuiTableSortSpecsColumn* sort_spec = &table->SortSpecsData[column->SortOrder]; + sort_spec->ColumnUserID = column->UserID; + sort_spec->ColumnIndex = (ImU8)column_n; + sort_spec->SortOrder = (ImU8)column->SortOrder; + sort_spec->SortDirection = column->SortDirection; + table->SortSpecs.ColumnsMask |= (ImU64)1 << column_n; + } + table->SortSpecs.Specs = table->SortSpecsData.Data; + table->SortSpecs.SpecsCount = table->SortSpecsData.Size; + table->SortSpecs.SpecsDirty = true; // Mark as dirty for user + table->IsSortSpecsDirty = false; // Mark as not dirty for us +} + +//------------------------------------------------------------------------- +// TABLE - .ini settings +//------------------------------------------------------------------------- +// [Init] 1: TableSettingsHandler_ReadXXXX() Load and parse .ini file into TableSettings. +// [Main] 2: TableLoadSettings() When table is created, bind Table to TableSettings, serialize TableSettings data into Table. +// [Main] 3: TableSaveSettings() When table properties are modified, serialize Table data into bound or new TableSettings, mark .ini as dirty. +// [Main] 4: TableSettingsHandler_WriteAll() When .ini file is dirty (which can come from other source), save TableSettings into .ini file. +//------------------------------------------------------------------------- + +// Clear and initialize empty settings instance +static void InitTableSettings(ImGuiTableSettings* settings, ImGuiID id, int columns_count, int columns_count_max) +{ + IM_PLACEMENT_NEW(settings) ImGuiTableSettings(); + ImGuiTableColumnSettings* settings_column = settings->GetColumnSettings(); + for (int n = 0; n < columns_count_max; n++, settings_column++) + IM_PLACEMENT_NEW(settings_column) ImGuiTableColumnSettings(); + settings->ID = id; + settings->ColumnsCount = (ImS8)columns_count; + settings->ColumnsCountMax = (ImS8)columns_count_max; + settings->WantApply = true; +} + +static size_t TableSettingsCalcChunkSize(int columns_count) +{ + return sizeof(ImGuiTableSettings) + (size_t)columns_count * sizeof(ImGuiTableColumnSettings); +} + +ImGuiTableSettings* ImGui::TableSettingsCreate(ImGuiID id, int columns_count) +{ + ImGuiContext& g = *GImGui; + ImGuiTableSettings* settings = g.SettingsTables.alloc_chunk(TableSettingsCalcChunkSize(columns_count)); + InitTableSettings(settings, id, columns_count, columns_count); + return settings; +} + +// Find existing settings +ImGuiTableSettings* ImGui::TableSettingsFindByID(ImGuiID id) +{ + // FIXME-OPT: Might want to store a lookup map for this? + ImGuiContext& g = *GImGui; + for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) + if (settings->ID == id) + return settings; + return NULL; +} + +void ImGui::TableSettingsClearByID(ImGuiID id) +{ + if (ImGuiTableSettings* settings = TableSettingsFindByID(id)) + settings->ID = 0; +} + +// Get settings for a given table, NULL if none +ImGuiTableSettings* ImGui::TableGetBoundSettings(ImGuiTable* table) +{ + if (table->SettingsOffset != -1) + { + ImGuiContext& g = *GImGui; + ImGuiTableSettings* settings = g.SettingsTables.ptr_from_offset(table->SettingsOffset); + IM_ASSERT(settings->ID == table->ID); + if (settings->ColumnsCountMax >= table->ColumnsCount) + return settings; // OK + settings->ID = 0; // Invalidate storage, we won't fit because of a count change + } + return NULL; +} + +void ImGui::TableSaveSettings(ImGuiTable* table) +{ + table->IsSettingsDirty = false; + if (table->Flags & ImGuiTableFlags_NoSavedSettings) + return; + + // Bind or create settings data + ImGuiContext& g = *GImGui; + ImGuiTableSettings* settings = TableGetBoundSettings(table); + if (settings == NULL) + { + settings = TableSettingsCreate(table->ID, table->ColumnsCount); + table->SettingsOffset = g.SettingsTables.offset_from_ptr(settings); + } + settings->ColumnsCount = (ImS8)table->ColumnsCount; + + // Serialize ImGuiTable/ImGuiTableColumn into ImGuiTableSettings/ImGuiTableColumnSettings + IM_ASSERT(settings->ID == table->ID); + IM_ASSERT(settings->ColumnsCount == table->ColumnsCount && settings->ColumnsCountMax >= settings->ColumnsCount); + ImGuiTableColumn* column = table->Columns.Data; + ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings(); + + bool save_ref_scale = false; + settings->SaveFlags = ImGuiTableFlags_None; + for (int n = 0; n < table->ColumnsCount; n++, column++, column_settings++) + { + const float width_or_weight = (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? column->StretchWeight : column->WidthRequest; + column_settings->WidthOrWeight = width_or_weight; + column_settings->Index = (ImS8)n; + column_settings->DisplayOrder = column->DisplayOrder; + column_settings->SortOrder = column->SortOrder; + column_settings->SortDirection = column->SortDirection; + column_settings->IsVisible = column->IsVisible; + column_settings->IsStretch = (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? 1 : 0; + if ((column->Flags & ImGuiTableColumnFlags_WidthStretch) == 0) + save_ref_scale = true; + + // We skip saving some data in the .ini file when they are unnecessary to restore our state. + // Note that fixed width where initial width was derived from auto-fit will always be saved as InitStretchWeightOrWidth will be 0.0f. + // FIXME-TABLE: We don't have logic to easily compare SortOrder to DefaultSortOrder yet so it's always saved when present. + if (width_or_weight != column->InitStretchWeightOrWidth) + settings->SaveFlags |= ImGuiTableFlags_Resizable; + if (column->DisplayOrder != n) + settings->SaveFlags |= ImGuiTableFlags_Reorderable; + if (column->SortOrder != -1) + settings->SaveFlags |= ImGuiTableFlags_Sortable; + if (column->IsVisible != ((column->Flags & ImGuiTableColumnFlags_DefaultHide) == 0)) + settings->SaveFlags |= ImGuiTableFlags_Hideable; + } + settings->SaveFlags &= table->Flags; + settings->RefScale = save_ref_scale ? table->RefScale : 0.0f; + + MarkIniSettingsDirty(); +} + +void ImGui::TableLoadSettings(ImGuiTable* table) +{ + ImGuiContext& g = *GImGui; + table->IsSettingsRequestLoad = false; + if (table->Flags & ImGuiTableFlags_NoSavedSettings) + return; + + // Bind settings + ImGuiTableSettings* settings; + if (table->SettingsOffset == -1) + { + settings = TableSettingsFindByID(table->ID); + if (settings == NULL) + return; + if (settings->ColumnsCount != table->ColumnsCount) // Allow settings if columns count changed. We could otherwise decide to return... + table->IsSettingsDirty = true; + table->SettingsOffset = g.SettingsTables.offset_from_ptr(settings); + } + else + { + settings = TableGetBoundSettings(table); + } + + table->SettingsLoadedFlags = settings->SaveFlags; + table->RefScale = settings->RefScale; + + // Serialize ImGuiTableSettings/ImGuiTableColumnSettings into ImGuiTable/ImGuiTableColumn + ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings(); + for (int data_n = 0; data_n < settings->ColumnsCount; data_n++, column_settings++) + { + int column_n = column_settings->Index; + if (column_n < 0 || column_n >= table->ColumnsCount) + continue; + + ImGuiTableColumn* column = &table->Columns[column_n]; + if (settings->SaveFlags & ImGuiTableFlags_Resizable) + { + if (column_settings->IsStretch) + column->StretchWeight = column_settings->WidthOrWeight; + else + column->WidthRequest = column_settings->WidthOrWeight; + column->AutoFitQueue = 0x00; + } + if (settings->SaveFlags & ImGuiTableFlags_Reorderable) + column->DisplayOrder = column_settings->DisplayOrder; + else + column->DisplayOrder = (ImS8)column_n; + column->IsVisible = column->IsVisibleNextFrame = column_settings->IsVisible; + column->SortOrder = column_settings->SortOrder; + column->SortDirection = column_settings->SortDirection; + } + + // FIXME-TABLE: Need to validate .ini data + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + table->DisplayOrderToIndex[table->Columns[column_n].DisplayOrder] = (ImS8)column_n; +} + +static void TableSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandler*) +{ + ImGuiContext& g = *ctx; + for (int i = 0; i != g.Tables.GetSize(); i++) + g.Tables.GetByIndex(i)->SettingsOffset = -1; + g.SettingsTables.clear(); +} + +// Apply to existing windows (if any) +static void TableSettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettingsHandler*) +{ + ImGuiContext& g = *ctx; + for (int i = 0; i != g.Tables.GetSize(); i++) + { + ImGuiTable* table = g.Tables.GetByIndex(i); + table->IsSettingsRequestLoad = true; + table->SettingsOffset = -1; + } +} + +static void* TableSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name) +{ + ImGuiID id = 0; + int columns_count = 0; + if (sscanf(name, "0x%08X,%d", &id, &columns_count) < 2) + return NULL; + + if (ImGuiTableSettings* settings = ImGui::TableSettingsFindByID(id)) + { + if (settings->ColumnsCountMax >= columns_count) + { + InitTableSettings(settings, id, columns_count, settings->ColumnsCountMax); // Recycle + return settings; + } + settings->ID = 0; // Invalidate storage, we won't fit because of a count change + } + return ImGui::TableSettingsCreate(id, columns_count); +} + +static void TableSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line) +{ + // "Column 0 UserID=0x42AD2D21 Width=100 Visible=1 Order=0 Sort=0v" + ImGuiTableSettings* settings = (ImGuiTableSettings*)entry; + float f = 0.0f; + int column_n = 0, r = 0, n = 0; + + if (sscanf(line, "RefScale=%f", &f) == 1) { settings->RefScale = f; return; } + + if (sscanf(line, "Column %d%n", &column_n, &r) == 1) + { + if (column_n < 0 || column_n >= settings->ColumnsCount) + return; + line = ImStrSkipBlank(line + r); + char c = 0; + ImGuiTableColumnSettings* column = settings->GetColumnSettings() + column_n; + column->Index = (ImS8)column_n; + if (sscanf(line, "UserID=0x%08X%n", (ImU32*)&n, &r)==1) { line = ImStrSkipBlank(line + r); column->UserID = (ImGuiID)n; } + if (sscanf(line, "Width=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->WidthOrWeight = (float)n; column->IsStretch = 0; settings->SaveFlags |= ImGuiTableFlags_Resizable; } + if (sscanf(line, "Weight=%f%n", &f, &r) == 1) { line = ImStrSkipBlank(line + r); column->WidthOrWeight = f; column->IsStretch = 1; settings->SaveFlags |= ImGuiTableFlags_Resizable; } + if (sscanf(line, "Visible=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->IsVisible = (ImU8)n; settings->SaveFlags |= ImGuiTableFlags_Hideable; } + if (sscanf(line, "Order=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->DisplayOrder = (ImS8)n; settings->SaveFlags |= ImGuiTableFlags_Reorderable; } + if (sscanf(line, "Sort=%d%c%n", &n, &c, &r) == 2) { line = ImStrSkipBlank(line + r); column->SortOrder = (ImS8)n; column->SortDirection = (c == '^') ? ImGuiSortDirection_Descending : ImGuiSortDirection_Ascending; settings->SaveFlags |= ImGuiTableFlags_Sortable; } + } +} + +static void TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) +{ + ImGuiContext& g = *ctx; + for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) + { + if (settings->ID == 0) // Skip ditched settings + continue; + + // TableSaveSettings() may clear some of those flags when we establish that the data can be stripped + // (e.g. Order was unchanged) + const bool save_size = (settings->SaveFlags & ImGuiTableFlags_Resizable) != 0; + const bool save_visible = (settings->SaveFlags & ImGuiTableFlags_Hideable) != 0; + const bool save_order = (settings->SaveFlags & ImGuiTableFlags_Reorderable) != 0; + const bool save_sort = (settings->SaveFlags & ImGuiTableFlags_Sortable) != 0; + if (!save_size && !save_visible && !save_order && !save_sort) + continue; + + buf->reserve(buf->size() + 30 + settings->ColumnsCount * 50); // ballpark reserve + buf->appendf("[%s][0x%08X,%d]\n", handler->TypeName, settings->ID, settings->ColumnsCount); + if (settings->RefScale != 0.0f) + buf->appendf("RefScale=%g\n", settings->RefScale); + ImGuiTableColumnSettings* column = settings->GetColumnSettings(); + for (int column_n = 0; column_n < settings->ColumnsCount; column_n++, column++) + { + // "Column 0 UserID=0x42AD2D21 Width=100 Visible=1 Order=0 Sort=0v" + buf->appendf("Column %-2d", column_n); + if (column->UserID != 0) buf->appendf(" UserID=%08X", column->UserID); + if (save_size && column->IsStretch) buf->appendf(" Weight=%.4f", column->WidthOrWeight); + if (save_size && !column->IsStretch) buf->appendf(" Width=%d", (int)column->WidthOrWeight); + if (save_visible) buf->appendf(" Visible=%d", column->IsVisible); + if (save_order) buf->appendf(" Order=%d", column->DisplayOrder); + if (save_sort && column->SortOrder != -1) buf->appendf(" Sort=%d%c", column->SortOrder, (column->SortDirection == ImGuiSortDirection_Ascending) ? 'v' : '^'); + buf->append("\n"); + } + buf->append("\n"); + } +} + +void ImGui::TableSettingsInstallHandler(ImGuiContext* context) +{ + ImGuiContext& g = *context; + ImGuiSettingsHandler ini_handler; + ini_handler.TypeName = "Table"; + ini_handler.TypeHash = ImHashStr("Table"); + ini_handler.ClearAllFn = TableSettingsHandler_ClearAll; + ini_handler.ReadOpenFn = TableSettingsHandler_ReadOpen; + ini_handler.ReadLineFn = TableSettingsHandler_ReadLine; + ini_handler.ApplyAllFn = TableSettingsHandler_ApplyAll; + ini_handler.WriteAllFn = TableSettingsHandler_WriteAll; + g.SettingsHandlers.push_back(ini_handler); +} + +//------------------------------------------------------------------------- +// TABLE - Garbage Collection +//------------------------------------------------------------------------- + +// Remove Table (currently only used by TestEngine) +void ImGui::TableRemove(ImGuiTable* table) +{ + //IMGUI_DEBUG_LOG("TableRemove() id=0x%08X\n", table->ID); + ImGuiContext& g = *GImGui; + int table_idx = g.Tables.GetIndex(table); + //memset(table->RawData.Data, 0, table->RawData.size_in_bytes()); + //memset(table, 0, sizeof(ImGuiTable)); + g.Tables.Remove(table->ID, table); + g.TablesLastTimeActive[table_idx] = -1.0f; +} + +// Free up/compact internal Table buffers for when it gets unused +void ImGui::TableGcCompactTransientBuffers(ImGuiTable* table) +{ + //IMGUI_DEBUG_LOG("TableGcCompactTransientBuffers() id=0x%08X\n", table->ID); + ImGuiContext& g = *GImGui; + IM_ASSERT(table->MemoryCompacted == false); + table->DrawSplitter.ClearFreeMemory(); + table->SortSpecsData.clear(); + table->SortSpecs.Specs = NULL; + table->IsSortSpecsDirty = true; + table->ColumnsNames.clear(); + table->MemoryCompacted = true; + for (int n = 0; n < table->ColumnsCount; n++) + table->Columns[n].NameOffset = -1; + g.TablesLastTimeActive[g.Tables.GetIndex(table)] = -1.0f; +} + +// Compact and remove unused settings data (currently only used by TestEngine) +void ImGui::TableGcCompactSettings() +{ + ImGuiContext& g = *GImGui; + int required_memory = 0; + for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) + if (settings->ID != 0) + required_memory += (int)TableSettingsCalcChunkSize(settings->ColumnsCount); + if (required_memory == g.SettingsTables.Buf.Size) + return; + ImChunkStream new_chunk_stream; + new_chunk_stream.Buf.reserve(required_memory); + for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) + if (settings->ID != 0) + memcpy(new_chunk_stream.alloc_chunk(TableSettingsCalcChunkSize(settings->ColumnsCount)), settings, TableSettingsCalcChunkSize(settings->ColumnsCount)); + g.SettingsTables.swap(new_chunk_stream); +} + +//------------------------------------------------------------------------- +// TABLE - Debugging +//------------------------------------------------------------------------- +// - DebugNodeTable() [Internal] +//------------------------------------------------------------------------- + +#ifndef IMGUI_DISABLE_METRICS_WINDOW + +void ImGui::DebugNodeTable(ImGuiTable* table) +{ + char buf[512]; + char* p = buf; + const char* buf_end = buf + IM_ARRAYSIZE(buf); + const bool is_active = (table->LastFrameActive >= ImGui::GetFrameCount() - 2); + ImFormatString(p, buf_end - p, "Table 0x%08X (%d columns, in '%s')%s", table->ID, table->ColumnsCount, table->OuterWindow->Name, is_active ? "" : " *Inactive*"); + if (!is_active) { PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); } + bool open = TreeNode(table, "%s", buf); + if (!is_active) { PopStyleColor(); } + if (IsItemHovered()) + GetForegroundDrawList()->AddRect(table->OuterRect.Min, table->OuterRect.Max, IM_COL32(255, 255, 0, 255)); + if (!open) + return; + BulletText("OuterRect: Pos: (%.1f,%.1f) Size: (%.1f,%.1f)", table->OuterRect.Min.x, table->OuterRect.Min.y, table->OuterRect.GetWidth(), table->OuterRect.GetHeight()); + BulletText("ColumnsWidth: %.1f, AutoFitWidth: %.1f, InnerWidth: %.1f%s", table->ColumnsTotalWidth, table->ColumnsAutoFitWidth, table->InnerWidth, table->InnerWidth == 0.0f ? " (auto)" : ""); + BulletText("CellPaddingX: %.1f, CellSpacingX: %.1f/%.1f, OuterPaddingX: %.1f", table->CellPaddingX, table->CellSpacingX1, table->CellSpacingX2, table->OuterPaddingX); + BulletText("HoveredColumnBody: %d, HoveredColumnBorder: %d", table->HoveredColumnBody, table->HoveredColumnBorder); + BulletText("ResizedColumn: %d, ReorderColumn: %d, HeldHeaderColumn: %d", table->ResizedColumn, table->ReorderColumn, table->HeldHeaderColumn); + //BulletText("BgDrawChannels: %d/%d", 0, table->BgDrawChannelUnfrozen); + for (int n = 0; n < table->ColumnsCount; n++) + { + ImGuiTableColumn* column = &table->Columns[n]; + const char* name = TableGetColumnName(table, n); + ImFormatString(buf, IM_ARRAYSIZE(buf), + "Column %d order %d name '%s': offset %+.2f to %+.2f\n" + "Visible: %d, Clipped: %d, DrawChannels: %d,%d\n" + "WidthGiven: %.1f, Request/Auto: %.1f/%.1f, StretchWeight: %.3f\n" + "MinX: %.1f, MaxX: %.1f (%+.1f), ClipRect: %.1f to %.1f (+%.1f)\n" + "ContentWidth: %.1f,%.1f, HeadersUsed/Ideal %.1f/%.1f\n" + "SortOrder: %d, SortDir: %s\n" + "UserID: 0x%08X, Flags: 0x%04X: %s%s%s%s..", + n, column->DisplayOrder, name, column->MinX - table->WorkRect.Min.x, column->MaxX - table->WorkRect.Min.x, + column->IsVisible, column->IsClipped, column->DrawChannelFrozen, column->DrawChannelUnfrozen, + column->WidthGiven, column->WidthRequest, column->WidthAuto, column->StretchWeight, + column->MinX, column->MaxX, column->MaxX - column->MinX, column->ClipRect.Min.x, column->ClipRect.Max.x, column->ClipRect.Max.x - column->ClipRect.Min.x, + column->ContentMaxXFrozen - column->WorkMinX, column->ContentMaxXUnfrozen - column->WorkMinX, column->ContentMaxXHeadersUsed - column->WorkMinX, column->ContentMaxXHeadersIdeal - column->WorkMinX, + column->SortOrder, (column->SortDirection == ImGuiSortDirection_Ascending) ? "Ascending" : (column->SortDirection == ImGuiSortDirection_Descending) ? "Descending" : "None", + column->UserID, column->Flags, + (column->Flags & ImGuiTableColumnFlags_WidthFixed) ? "WidthFixed " : "", + (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? "WidthStretch " : "", + (column->Flags & ImGuiTableColumnFlags_WidthAlwaysAutoResize) ? "WidthAlwaysAutoResize " : "", + (column->Flags & ImGuiTableColumnFlags_NoResize) ? "NoResize " : ""); + Bullet(); + Selectable(buf); + if (IsItemHovered()) + { + ImRect r(column->MinX, table->OuterRect.Min.y, column->MaxX, table->OuterRect.Max.y); + GetForegroundDrawList()->AddRect(r.Min, r.Max, IM_COL32(255, 255, 0, 255)); + } + } + if (ImGuiTableSettings* settings = TableGetBoundSettings(table)) + DebugNodeTableSettings(settings); + TreePop(); +} + +void ImGui::DebugNodeTableSettings(ImGuiTableSettings* settings) +{ + if (!TreeNode((void*)(intptr_t)settings->ID, "Settings 0x%08X (%d columns)", settings->ID, settings->ColumnsCount)) + return; + BulletText("SaveFlags: 0x%08X", settings->SaveFlags); + BulletText("ColumnsCount: %d (max %d)", settings->ColumnsCount, settings->ColumnsCountMax); + for (int n = 0; n < settings->ColumnsCount; n++) + { + ImGuiTableColumnSettings* column_settings = &settings->GetColumnSettings()[n]; + ImGuiSortDirection sort_dir = (column_settings->SortOrder != -1) ? (ImGuiSortDirection)column_settings->SortDirection : ImGuiSortDirection_None; + BulletText("Column %d Order %d SortOrder %d %s Vis %d %s %7.3f UserID 0x%08X", + n, column_settings->DisplayOrder, column_settings->SortOrder, + (sort_dir == ImGuiSortDirection_Ascending) ? "Asc" : (sort_dir == ImGuiSortDirection_Descending) ? "Des" : "---", + column_settings->IsVisible, column_settings->IsStretch ? "Weight" : "Width ", column_settings->WidthOrWeight, column_settings->UserID); + } + TreePop(); +} + +#endif // #ifndef IMGUI_DISABLE_METRICS_WINDOW + //------------------------------------------------------------------------- #endif // #ifndef IMGUI_DISABLE