Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/wolfpld/tracy.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/imgui
diff options
context:
space:
mode:
authorBartosz Taudul <wolf.pld@gmail.com>2018-08-31 19:37:29 +0300
committerBartosz Taudul <wolf.pld@gmail.com>2018-08-31 19:37:29 +0300
commitfca71e6e0d4697d1fdfc479d757cfd1eba69cfd7 (patch)
treecd16d29e368db6d33f02a09dbee5fc67e332b6dc /imgui
parent0d6d296e9499bb5a5235263bf714c1f0bbddbf34 (diff)
Update to imgui 1.64.
Diffstat (limited to 'imgui')
-rw-r--r--imgui/imgui.cpp8738
-rw-r--r--imgui/imgui.h6
-rw-r--r--imgui/imgui_demo.cpp2
-rw-r--r--imgui/imgui_draw.cpp25
-rw-r--r--imgui/imgui_internal.h5
-rw-r--r--imgui/imgui_widgets.cpp5536
6 files changed, 7319 insertions, 6993 deletions
diff --git a/imgui/imgui.cpp b/imgui/imgui.cpp
index 0b3ea0f5..2fa7e0a9 100644
--- a/imgui/imgui.cpp
+++ b/imgui/imgui.cpp
@@ -1,4 +1,4 @@
-// dear imgui, v1.63
+// dear imgui, v1.64
// (main code and documentation)
// Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp for demo code.
@@ -19,32 +19,58 @@
/*
- Index
- - MISSION STATEMENT
- - END-USER GUIDE
- - PROGRAMMER GUIDE (read me!)
- - Read first
- - How to update to a newer version of Dear ImGui
- - Getting started with integrating Dear ImGui in your code/engine
- - Using gamepad/keyboard navigation controls [BETA]
- - API BREAKING CHANGES (read me when you update!)
- - ISSUES & TODO LIST
- - FREQUENTLY ASKED QUESTIONS (FAQ), TIPS
- - How can I tell whether to dispatch mouse/keyboard to imgui or to my application?
- - How can I display an image? What is ImTextureID, how does it works?
- - How can I have multiple widgets with the same label or without a label? A primer on labels and the ID Stack.
- - How can I use my own math types instead of ImVec2/ImVec4?
- - How can I load a different font than the default?
- - How can I easily use icons in my application?
- - How can I load multiple fonts?
- - How can I display and input non-latin characters such as Chinese, Japanese, Korean, Cyrillic?
- - How can I use the drawing facilities without an ImGui window? (using ImDrawList API)
- - I integrated Dear ImGui in my engine and the text or lines are blurry..
- - I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around..
- - How can I help?
- - ISSUES & TODO-LIST
- - CODE
+Index of this file:
+
+DOCUMENTATION
+- MISSION STATEMENT
+- END-USER GUIDE
+- PROGRAMMER GUIDE (read me!)
+ - Read first
+ - How to update to a newer version of Dear ImGui
+ - Getting started with integrating Dear ImGui in your code/engine
+ - Using gamepad/keyboard navigation controls [BETA]
+- API BREAKING CHANGES (read me when you update!)
+- FREQUENTLY ASKED QUESTIONS (FAQ), TIPS
+ - How can I tell whether to dispatch mouse/keyboard to imgui or to my application?
+ - How can I display an image? What is ImTextureID, how does it works?
+ - How can I have multiple widgets with the same label or without a label? A primer on labels and the ID Stack.
+ - How can I use my own math types instead of ImVec2/ImVec4?
+ - How can I load a different font than the default?
+ - How can I easily use icons in my application?
+ - How can I load multiple fonts?
+ - How can I display and input non-latin characters such as Chinese, Japanese, Korean, Cyrillic?
+ - How can I use the drawing facilities without an ImGui window? (using ImDrawList API)
+ - I integrated Dear ImGui in my engine and the text or lines are blurry..
+ - I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around..
+ - How can I help?
+
+CODE
+- Forward Declarations
+- Context and Memory Allocators
+- User facing structures (ImGuiStyle, ImGuiIO)
+- Helper/Utilities (ImXXX functions, Color functions)
+- ImGuiStorage
+- ImGuiTextFilter
+- ImGuiTextBuffer
+- ImGuiListClipper
+- Main Code (most of the code! lots of stuff, needs tidying up)
+- Tooltips
+- Popups
+- Navigation
+- Columns
+- Drag and Drop
+- Logging
+- Settings
+- Platform Dependent Helpers
+- Metrics/Debug window
+*/
+
+//-----------------------------------------------------------------------------
+// Documentation
+//-----------------------------------------------------------------------------
+
+/*
MISSION STATEMENT
=================
@@ -307,6 +333,9 @@
When you are not sure about a old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files.
You can read releases logs https://github.com/ocornut/imgui/releases for more details.
+ - 2018/08/31 (1.64) - added imgui_widgets.cpp file, extracted and moved widgets code out of imgui.cpp into imgui_widgets.cpp. Re-ordered some of the code remaining in imgui.cpp.
+ NONE OF THE FUNCTIONS HAVE CHANGED. THE CODE IS SEMANTICALLY 100% IDENTICAL, BUT _EVERY_ FUNCTION HAS BEEN MOVED.
+ Because of this, any local modifications to imgui.cpp will likely conflict when you update. Read docs/CHANGELOG.txt for suggestions.
- 2018/08/22 (1.63) - renamed IsItemDeactivatedAfterChange() to IsItemDeactivatedAfterEdit() for consistency with new IsItemEdited() API. Kept redirection function (will obsolete soonish as IsItemDeactivatedAfterChange() is very recent).
- 2018/08/21 (1.63) - renamed ImGuiTextEditCallback to ImGuiInputTextCallback, ImGuiTextEditCallbackData to ImGuiInputTextCallbackData for consistency. Kept redirection types (will obsolete).
- 2018/08/21 (1.63) - removed ImGuiInputTextCallbackData::ReadOnly since it is a duplication of (ImGuiInputTextCallbackData::Flags & ImGuiInputTextFlags_ReadOnly).
@@ -472,11 +501,6 @@
- 2014/08/28 (1.09) - changed the behavior of IO.PixelCenterOffset following various rendering fixes
- ISSUES & TODO-LIST
- ==================
- See TODO.txt
-
-
FREQUENTLY ASKED QUESTIONS (FAQ), TIPS
======================================
@@ -783,7 +807,7 @@
Rectangles provided by ImGui are defined as (x1=left,y1=top,x2=right,y2=bottom) and NOT as (x1,y1,width,height).
Q: How can I help?
- A: - If you are experienced with Dear ImGui and C++, look at the github issues, or TODO.txt and see how you want/can help!
+ A: - If you are experienced with Dear ImGui and C++, look at the github issues, or docs/TODO.txt and see how you want/can help!
- Convince your company to fund development time! Individual users: you can also become a Patron (patreon.com/imgui) or donate on PayPal! See README.
- Disclose your usage of dear imgui via a dev blog post, a tweet, a screenshot, a mention somewhere etc.
You may post screenshot or links in the gallery threads (github.com/ocornut/imgui/issues/1269). Visuals are ideal as they inspire other programmers.
@@ -824,7 +848,6 @@
// Visual Studio warnings
#ifdef _MSC_VER
#pragma warning (disable: 4127) // condition expression is constant
-#pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff)
#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
#endif
@@ -852,24 +875,6 @@
#endif
#endif
-static const ImS32 IM_S32_MIN = INT_MIN; // (-2147483647 - 1), (0x80000000);
-static const ImS32 IM_S32_MAX = INT_MAX; // (2147483647), (0x7FFFFFFF)
-static const ImU32 IM_U32_MIN = 0;
-static const ImU32 IM_U32_MAX = UINT_MAX; // (0xFFFFFFFF)
-#ifdef LLONG_MIN
-static const ImS64 IM_S64_MIN = LLONG_MIN; // (-9223372036854775807ll - 1ll);
-static const ImS64 IM_S64_MAX = LLONG_MAX; // (9223372036854775807ll);
-#else
-static const ImS64 IM_S64_MIN = -9223372036854775807LL - 1;
-static const ImS64 IM_S64_MAX = 9223372036854775807LL;
-#endif
-static const ImU64 IM_U64_MIN = 0;
-#ifdef ULLONG_MAX
-static const ImU64 IM_U64_MAX = ULLONG_MAX; // (0xFFFFFFFFFFFFFFFFull);
-#else
-static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1);
-#endif
-
// When using CTRL+TAB (or Gamepad Square+L/R) we delay the visual a little in order to reduce visual noise doing a fast switch.
static const float NAV_WINDOWING_HIGHLIGHT_DELAY = 0.20f; // Time before the highlight and screen dimming starts fading in
static const float NAV_WINDOWING_LIST_APPEAR_DELAY = 0.15f; // Time before the window list starts to appear
@@ -884,7 +889,6 @@ static void SetWindowSize(ImGuiWindow* window, const ImVec2& size, I
static void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond);
static void FindHoveredWindow();
static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFlags flags);
-static ImGuiWindowSettings* CreateNewWindowSettings(const char* name);
static void CheckStacksSize(ImGuiWindow* window, bool write);
static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window, bool snap_on_edges);
@@ -894,13 +898,15 @@ static void AddWindowToSortedBuffer(ImVector<ImGuiWindow*>* out_sort
static ImRect GetViewportRect();
-static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data);
-static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end);
-static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false);
+// Settings
+static void* SettingsHandlerWindow_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name);
+static void SettingsHandlerWindow_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line);
+static void SettingsHandlerWindow_WriteAll(ImGuiContext* imgui_ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf);
-static inline int DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* data_ptr, const char* format);
-static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, void* arg_1, const void* arg_2);
-static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* format);
+// Platform Dependents default implementation for IO functions
+static const char* GetClipboardTextFn_DefaultImpl(void* user_data);
+static void SetClipboardTextFn_DefaultImpl(void* user_data, const char* text);
+static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y);
namespace ImGui
{
@@ -909,29 +915,20 @@ static bool BeginChildEx(const char* name, ImGuiID id, const ImVec2&
static void NavUpdate();
static void NavUpdateWindowing();
static void NavUpdateWindowingList();
+static void NavUpdateMoveResult();
+static float NavUpdatePageUpPageDown(int allowed_dir_flags);
+static inline void NavUpdateAnyRequestFlag();
static void NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, const ImGuiID id);
+static ImVec2 NavCalcPreferredRefPos();
+static void NavSaveLastChildNavWindow(ImGuiWindow* nav_window);
+static ImGuiWindow* NavRestoreLastChildNavWindow(ImGuiWindow* window);
static void UpdateMouseInputs();
static void UpdateMouseWheel();
static void UpdateManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4]);
-
-// Template widget behaviors
-template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
-static bool DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, float power);
-
-template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
-static bool SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, TYPE* v, const TYPE v_min, const TYPE v_max, const char* format, float power, ImGuiSliderFlags flags, ImRect* out_grab_bb);
}
//-----------------------------------------------------------------------------
-// Platform dependent default implementations
-//-----------------------------------------------------------------------------
-
-static const char* GetClipboardTextFn_DefaultImpl(void* user_data);
-static void SetClipboardTextFn_DefaultImpl(void* user_data, const char* text);
-static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y);
-
-//-----------------------------------------------------------------------------
// Context and Memory Allocators
//-----------------------------------------------------------------------------
@@ -961,7 +958,7 @@ static void (*GImAllocatorFreeFunc)(void* ptr, void* user_data) = FreeWrapper;
static void* GImAllocatorUserData = NULL;
//-----------------------------------------------------------------------------
-// User facing structures
+// User facing main structures
//-----------------------------------------------------------------------------
ImGuiStyle::ImGuiStyle()
@@ -1106,7 +1103,7 @@ void ImGuiIO::AddInputCharactersUTF8(const char* utf8_chars)
}
//-----------------------------------------------------------------------------
-// HELPERS
+// HELPERS/UTILITIES
//-----------------------------------------------------------------------------
ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p)
@@ -1245,19 +1242,6 @@ void ImStrTrimBlanks(char* buf)
buf[p - p_start] = 0; // Zero terminate
}
-template<typename TYPE>
-static const char* ImAtoi(const char* src, TYPE* output)
-{
- int negative = 0;
- if (*src == '-') { negative = 1; src++; }
- if (*src == '+') { src++; }
- TYPE v = 0;
- while (*src >= '0' && *src <= '9')
- v = (v * 10) + (*src++ - '0');
- *output = negative ? -v : v;
- return src;
-}
-
// A) MSVC version appears to return -1 on overflow, whereas glibc appears to return total count (which may be >= buf_size).
// Ideally we would test for only one of those limits at runtime depending on the behavior the vsnprintf(), but trying to deduct it at compile time sounds like a pandora can of worm.
// B) When buf==NULL vsnprintf() will return the output size.
@@ -1338,7 +1322,7 @@ ImU32 ImHash(const void* data, int data_size, ImU32 seed)
}
//-----------------------------------------------------------------------------
-// ImText* helpers
+// HELPERS/UTILITIES (ImText* helpers)
//-----------------------------------------------------------------------------
// Convert UTF-8 to 32-bits character, process single character input.
@@ -1520,6 +1504,68 @@ int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_e
return bytes_count;
}
+FILE* ImFileOpen(const char* filename, const char* mode)
+{
+#if defined(_WIN32) && !defined(__CYGWIN__)
+ // We need a fopen() wrapper because MSVC/Windows fopen doesn't handle UTF-8 filenames. Converting both strings from UTF-8 to wchar format (using a single allocation, because we can)
+ const int filename_wsize = ImTextCountCharsFromUtf8(filename, NULL) + 1;
+ const int mode_wsize = ImTextCountCharsFromUtf8(mode, NULL) + 1;
+ ImVector<ImWchar> buf;
+ buf.resize(filename_wsize + mode_wsize);
+ ImTextStrFromUtf8(&buf[0], filename_wsize, filename, NULL);
+ ImTextStrFromUtf8(&buf[filename_wsize], mode_wsize, mode, NULL);
+ return _wfopen((wchar_t*)&buf[0], (wchar_t*)&buf[filename_wsize]);
+#else
+ return fopen(filename, mode);
+#endif
+}
+
+// Load file content into memory
+// Memory allocated with ImGui::MemAlloc(), must be freed by user using ImGui::MemFree()
+void* ImFileLoadToMemory(const char* filename, const char* file_open_mode, size_t* out_file_size, int padding_bytes)
+{
+ IM_ASSERT(filename && file_open_mode);
+ if (out_file_size)
+ *out_file_size = 0;
+
+ FILE* f;
+ if ((f = ImFileOpen(filename, file_open_mode)) == NULL)
+ return NULL;
+
+ long file_size_signed;
+ if (fseek(f, 0, SEEK_END) || (file_size_signed = ftell(f)) == -1 || fseek(f, 0, SEEK_SET))
+ {
+ fclose(f);
+ return NULL;
+ }
+
+ size_t file_size = (size_t)file_size_signed;
+ void* file_data = ImGui::MemAlloc(file_size + padding_bytes);
+ if (file_data == NULL)
+ {
+ fclose(f);
+ return NULL;
+ }
+ if (fread(file_data, 1, file_size, f) != file_size)
+ {
+ fclose(f);
+ ImGui::MemFree(file_data);
+ return NULL;
+ }
+ if (padding_bytes > 0)
+ memset((void*)(((char*)file_data) + file_size), 0, (size_t)padding_bytes);
+
+ fclose(f);
+ if (out_file_size)
+ *out_file_size = file_size;
+
+ return file_data;
+}
+
+//-----------------------------------------------------------------------------
+// COLOR FUNCTIONS
+//-----------------------------------------------------------------------------
+
ImVec4 ImGui::ColorConvertU32ToFloat4(ImU32 in)
{
float s = 1.0f/255.0f;
@@ -1540,38 +1586,6 @@ ImU32 ImGui::ColorConvertFloat4ToU32(const ImVec4& in)
return out;
}
-ImU32 ImGui::GetColorU32(ImGuiCol idx, float alpha_mul)
-{
- ImGuiStyle& style = GImGui->Style;
- ImVec4 c = style.Colors[idx];
- c.w *= style.Alpha * alpha_mul;
- return ColorConvertFloat4ToU32(c);
-}
-
-ImU32 ImGui::GetColorU32(const ImVec4& col)
-{
- ImGuiStyle& style = GImGui->Style;
- ImVec4 c = col;
- c.w *= style.Alpha;
- return ColorConvertFloat4ToU32(c);
-}
-
-const ImVec4& ImGui::GetStyleColorVec4(ImGuiCol idx)
-{
- ImGuiStyle& style = GImGui->Style;
- return style.Colors[idx];
-}
-
-ImU32 ImGui::GetColorU32(ImU32 col)
-{
- float style_alpha = GImGui->Style.Alpha;
- if (style_alpha >= 1.0f)
- return col;
- ImU32 a = (col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT;
- a = (ImU32)(a * style_alpha); // We don't need to clamp 0..255 because Style.Alpha is in 0..1 range.
- return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT);
-}
-
// Convert rgb floats ([0-1],[0-1],[0-1]) to hsv floats ([0-1],[0-1],[0-1]), from Foley & van Dam p592
// Optimized http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv
void ImGui::ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v)
@@ -1623,62 +1637,36 @@ void ImGui::ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float&
}
}
-FILE* ImFileOpen(const char* filename, const char* mode)
+ImU32 ImGui::GetColorU32(ImGuiCol idx, float alpha_mul)
{
-#if defined(_WIN32) && !defined(__CYGWIN__)
- // We need a fopen() wrapper because MSVC/Windows fopen doesn't handle UTF-8 filenames. Converting both strings from UTF-8 to wchar format (using a single allocation, because we can)
- const int filename_wsize = ImTextCountCharsFromUtf8(filename, NULL) + 1;
- const int mode_wsize = ImTextCountCharsFromUtf8(mode, NULL) + 1;
- ImVector<ImWchar> buf;
- buf.resize(filename_wsize + mode_wsize);
- ImTextStrFromUtf8(&buf[0], filename_wsize, filename, NULL);
- ImTextStrFromUtf8(&buf[filename_wsize], mode_wsize, mode, NULL);
- return _wfopen((wchar_t*)&buf[0], (wchar_t*)&buf[filename_wsize]);
-#else
- return fopen(filename, mode);
-#endif
+ ImGuiStyle& style = GImGui->Style;
+ ImVec4 c = style.Colors[idx];
+ c.w *= style.Alpha * alpha_mul;
+ return ColorConvertFloat4ToU32(c);
}
-// Load file content into memory
-// Memory allocated with ImGui::MemAlloc(), must be freed by user using ImGui::MemFree()
-void* ImFileLoadToMemory(const char* filename, const char* file_open_mode, size_t* out_file_size, int padding_bytes)
+ImU32 ImGui::GetColorU32(const ImVec4& col)
{
- IM_ASSERT(filename && file_open_mode);
- if (out_file_size)
- *out_file_size = 0;
-
- FILE* f;
- if ((f = ImFileOpen(filename, file_open_mode)) == NULL)
- return NULL;
-
- long file_size_signed;
- if (fseek(f, 0, SEEK_END) || (file_size_signed = ftell(f)) == -1 || fseek(f, 0, SEEK_SET))
- {
- fclose(f);
- return NULL;
- }
-
- size_t file_size = (size_t)file_size_signed;
- void* file_data = ImGui::MemAlloc(file_size + padding_bytes);
- if (file_data == NULL)
- {
- fclose(f);
- return NULL;
- }
- if (fread(file_data, 1, file_size, f) != file_size)
- {
- fclose(f);
- ImGui::MemFree(file_data);
- return NULL;
- }
- if (padding_bytes > 0)
- memset((void*)(((char*)file_data) + file_size), 0, (size_t)padding_bytes);
+ ImGuiStyle& style = GImGui->Style;
+ ImVec4 c = col;
+ c.w *= style.Alpha;
+ return ColorConvertFloat4ToU32(c);
+}
- fclose(f);
- if (out_file_size)
- *out_file_size = file_size;
+const ImVec4& ImGui::GetStyleColorVec4(ImGuiCol idx)
+{
+ ImGuiStyle& style = GImGui->Style;
+ return style.Colors[idx];
+}
- return file_data;
+ImU32 ImGui::GetColorU32(ImU32 col)
+{
+ float style_alpha = GImGui->Style.Alpha;
+ if (style_alpha >= 1.0f)
+ return col;
+ ImU32 a = (col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT;
+ a = (ImU32)(a * style_alpha); // We don't need to clamp 0..255 because Style.Alpha is in 0..1 range.
+ return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT);
}
//-----------------------------------------------------------------------------
@@ -1983,51 +1971,6 @@ void ImGuiTextBuffer::appendf(const char* fmt, ...)
}
//-----------------------------------------------------------------------------
-// ImGuiSimpleColumns (internal use only)
-//-----------------------------------------------------------------------------
-
-ImGuiMenuColumns::ImGuiMenuColumns()
-{
- Count = 0;
- 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));
- Count = count;
- Width = NextWidth = 0.0f;
- Spacing = spacing;
- if (clear) memset(NextWidths, 0, sizeof(NextWidths));
- for (int i = 0; i < Count; i++)
- {
- if (i > 0 && NextWidths[i] > 0.0f)
- Width += Spacing;
- Pos[i] = (float)(int)Width;
- Width += NextWidths[i];
- NextWidths[i] = 0.0f;
- }
-}
-
-float ImGuiMenuColumns::DeclColumns(float w0, float w1, float w2) // not using va_arg because they promote float to double
-{
- NextWidth = 0.0f;
- NextWidths[0] = ImMax(NextWidths[0], w0);
- NextWidths[1] = ImMax(NextWidths[1], w1);
- NextWidths[2] = ImMax(NextWidths[2], w2);
- for (int i = 0; i < 3; i++)
- NextWidth += NextWidths[i] + ((i > 0 && NextWidths[i] > 0.0f) ? Spacing : 0.0f);
- return ImMax(Width, NextWidth);
-}
-
-float ImGuiMenuColumns::CalcExtraSpace(float avail_w)
-{
- return ImMax(0.0f, avail_w - Width);
-}
-
-//-----------------------------------------------------------------------------
// ImGuiListClipper
//-----------------------------------------------------------------------------
@@ -2112,9 +2055,11 @@ bool ImGuiListClipper::Step()
}
//-----------------------------------------------------------------------------
-// ImGuiWindow
+// MAIN CODE
+// (this category is still too large and badly ordered, needs some tidying up)
//-----------------------------------------------------------------------------
+// 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)
{
@@ -2222,10 +2167,6 @@ ImGuiID ImGuiWindow::GetIDFromRectangle(const ImRect& r_abs)
return id;
}
-//-----------------------------------------------------------------------------
-// Internal API exposed in imgui_internal.h
-//-----------------------------------------------------------------------------
-
static void SetCurrentWindow(ImGuiWindow* window)
{
ImGuiContext& g = *GImGui;
@@ -2393,285 +2334,6 @@ void ImGui::ItemSize(const ImRect& bb, float text_offset_y)
ItemSize(bb.GetSize(), text_offset_y);
}
-static ImGuiDir inline NavScoreItemGetQuadrant(float dx, float dy)
-{
- if (ImFabs(dx) > ImFabs(dy))
- return (dx > 0.0f) ? ImGuiDir_Right : ImGuiDir_Left;
- return (dy > 0.0f) ? ImGuiDir_Down : ImGuiDir_Up;
-}
-
-static float inline NavScoreItemDistInterval(float a0, float a1, float b0, float b1)
-{
- if (a1 < b0)
- return a1 - b0;
- if (b1 < a0)
- return a0 - b1;
- return 0.0f;
-}
-
-static void inline NavClampRectToVisibleAreaForMoveDir(ImGuiDir move_dir, ImRect& r, const ImRect& clip_rect)
-{
- if (move_dir == ImGuiDir_Left || move_dir == ImGuiDir_Right)
- {
- r.Min.y = ImClamp(r.Min.y, clip_rect.Min.y, clip_rect.Max.y);
- r.Max.y = ImClamp(r.Max.y, clip_rect.Min.y, clip_rect.Max.y);
- }
- else
- {
- r.Min.x = ImClamp(r.Min.x, clip_rect.Min.x, clip_rect.Max.x);
- r.Max.x = ImClamp(r.Max.x, clip_rect.Min.x, clip_rect.Max.x);
- }
-}
-
-// Scoring function for directional navigation. Based on https://gist.github.com/rygorous/6981057
-static bool NavScoreItem(ImGuiNavMoveResult* result, ImRect cand)
-{
- ImGuiContext& g = *GImGui;
- ImGuiWindow* window = g.CurrentWindow;
- if (g.NavLayer != window->DC.NavLayerCurrent)
- return false;
-
- const ImRect& curr = g.NavScoringRectScreen; // Current modified source rect (NB: we've applied Max.x = Min.x in NavUpdate() to inhibit the effect of having varied item width)
- g.NavScoringCount++;
-
- // When entering through a NavFlattened border, we consider child window items as fully clipped for scoring
- if (window->ParentWindow == g.NavWindow)
- {
- IM_ASSERT((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened);
- if (!window->ClipRect.Contains(cand))
- return false;
- cand.ClipWithFull(window->ClipRect); // This allows the scored item to not overlap other candidates in the parent window
- }
-
- // We perform scoring on items bounding box clipped by the current clipping rectangle on the other axis (clipping on our movement axis would give us equal scores for all clipped items)
- // For example, this ensure that items in one column are not reached when moving vertically from items in another column.
- NavClampRectToVisibleAreaForMoveDir(g.NavMoveClipDir, cand, window->ClipRect);
-
- // Compute distance between boxes
- // FIXME-NAV: Introducing biases for vertical navigation, needs to be removed.
- float dbx = NavScoreItemDistInterval(cand.Min.x, cand.Max.x, curr.Min.x, curr.Max.x);
- float dby = NavScoreItemDistInterval(ImLerp(cand.Min.y, cand.Max.y, 0.2f), ImLerp(cand.Min.y, cand.Max.y, 0.8f), ImLerp(curr.Min.y, curr.Max.y, 0.2f), ImLerp(curr.Min.y, curr.Max.y, 0.8f)); // Scale down on Y to keep using box-distance for vertically touching items
- if (dby != 0.0f && dbx != 0.0f)
- dbx = (dbx/1000.0f) + ((dbx > 0.0f) ? +1.0f : -1.0f);
- float dist_box = ImFabs(dbx) + ImFabs(dby);
-
- // Compute distance between centers (this is off by a factor of 2, but we only compare center distances with each other so it doesn't matter)
- float dcx = (cand.Min.x + cand.Max.x) - (curr.Min.x + curr.Max.x);
- float dcy = (cand.Min.y + cand.Max.y) - (curr.Min.y + curr.Max.y);
- float dist_center = ImFabs(dcx) + ImFabs(dcy); // L1 metric (need this for our connectedness guarantee)
-
- // Determine which quadrant of 'curr' our candidate item 'cand' lies in based on distance
- ImGuiDir quadrant;
- float dax = 0.0f, day = 0.0f, dist_axial = 0.0f;
- if (dbx != 0.0f || dby != 0.0f)
- {
- // For non-overlapping boxes, use distance between boxes
- dax = dbx;
- day = dby;
- dist_axial = dist_box;
- quadrant = NavScoreItemGetQuadrant(dbx, dby);
- }
- else if (dcx != 0.0f || dcy != 0.0f)
- {
- // For overlapping boxes with different centers, use distance between centers
- dax = dcx;
- day = dcy;
- dist_axial = dist_center;
- quadrant = NavScoreItemGetQuadrant(dcx, dcy);
- }
- else
- {
- // Degenerate case: two overlapping buttons with same center, break ties arbitrarily (note that LastItemId here is really the _previous_ item order, but it doesn't matter)
- quadrant = (window->DC.LastItemId < g.NavId) ? ImGuiDir_Left : ImGuiDir_Right;
- }
-
-#if IMGUI_DEBUG_NAV_SCORING
- char buf[128];
- if (ImGui::IsMouseHoveringRect(cand.Min, cand.Max))
- {
- ImFormatString(buf, IM_ARRAYSIZE(buf), "dbox (%.2f,%.2f->%.4f)\ndcen (%.2f,%.2f->%.4f)\nd (%.2f,%.2f->%.4f)\nnav %c, quadrant %c", dbx, dby, dist_box, dcx, dcy, dist_center, dax, day, dist_axial, "WENS"[g.NavMoveDir], "WENS"[quadrant]);
- ImDrawList* draw_list = ImGui::GetOverlayDrawList();
- draw_list->AddRect(curr.Min, curr.Max, IM_COL32(255,200,0,100));
- draw_list->AddRect(cand.Min, cand.Max, IM_COL32(255,255,0,200));
- draw_list->AddRectFilled(cand.Max-ImVec2(4,4), cand.Max+ImGui::CalcTextSize(buf)+ImVec2(4,4), IM_COL32(40,0,0,150));
- draw_list->AddText(g.IO.FontDefault, 13.0f, cand.Max, ~0U, buf);
- }
- else if (g.IO.KeyCtrl) // Hold to preview score in matching quadrant. Press C to rotate.
- {
- if (IsKeyPressedMap(ImGuiKey_C)) { g.NavMoveDirLast = (ImGuiDir)((g.NavMoveDirLast + 1) & 3); g.IO.KeysDownDuration[g.IO.KeyMap[ImGuiKey_C]] = 0.01f; }
- if (quadrant == g.NavMoveDir)
- {
- ImFormatString(buf, IM_ARRAYSIZE(buf), "%.0f/%.0f", dist_box, dist_center);
- ImDrawList* draw_list = ImGui::GetOverlayDrawList();
- draw_list->AddRectFilled(cand.Min, cand.Max, IM_COL32(255, 0, 0, 200));
- draw_list->AddText(g.IO.FontDefault, 13.0f, cand.Min, IM_COL32(255, 255, 255, 255), buf);
- }
- }
- #endif
-
- // Is it in the quadrant we're interesting in moving to?
- bool new_best = false;
- if (quadrant == g.NavMoveDir)
- {
- // Does it beat the current best candidate?
- if (dist_box < result->DistBox)
- {
- result->DistBox = dist_box;
- result->DistCenter = dist_center;
- return true;
- }
- if (dist_box == result->DistBox)
- {
- // Try using distance between center points to break ties
- if (dist_center < result->DistCenter)
- {
- result->DistCenter = dist_center;
- new_best = true;
- }
- else if (dist_center == result->DistCenter)
- {
- // Still tied! we need to be extra-careful to make sure everything gets linked properly. We consistently break ties by symbolically moving "later" items
- // (with higher index) to the right/downwards by an infinitesimal amount since we the current "best" button already (so it must have a lower index),
- // this is fairly easy. This rule ensures that all buttons with dx==dy==0 will end up being linked in order of appearance along the x axis.
- if (((g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) ? dby : dbx) < 0.0f) // moving bj to the right/down decreases distance
- new_best = true;
- }
- }
- }
-
- // Axial check: if 'curr' has no link at all in some direction and 'cand' lies roughly in that direction, add a tentative link. This will only be kept if no "real" matches
- // are found, so it only augments the graph produced by the above method using extra links. (important, since it doesn't guarantee strong connectedness)
- // This is just to avoid buttons having no links in a particular direction when there's a suitable neighbor. you get good graphs without this too.
- // 2017/09/29: FIXME: This now currently only enabled inside menu bars, ideally we'd disable it everywhere. Menus in particular need to catch failure. For general navigation it feels awkward.
- // Disabling it may lead to disconnected graphs when nodes are very spaced out on different axis. Perhaps consider offering this as an option?
- if (result->DistBox == FLT_MAX && dist_axial < result->DistAxial) // Check axial match
- if (g.NavLayer == 1 && !(g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
- if ((g.NavMoveDir == ImGuiDir_Left && dax < 0.0f) || (g.NavMoveDir == ImGuiDir_Right && dax > 0.0f) || (g.NavMoveDir == ImGuiDir_Up && day < 0.0f) || (g.NavMoveDir == ImGuiDir_Down && day > 0.0f))
- {
- result->DistAxial = dist_axial;
- new_best = true;
- }
-
- return new_best;
-}
-
-static void NavSaveLastChildNavWindow(ImGuiWindow* child_window)
-{
- ImGuiWindow* parent_window = child_window;
- while (parent_window && (parent_window->Flags & ImGuiWindowFlags_ChildWindow) != 0 && (parent_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0)
- parent_window = parent_window->ParentWindow;
- if (parent_window && parent_window != child_window)
- parent_window->NavLastChildNavWindow = child_window;
-}
-
-// Call when we are expected to land on Layer 0 after FocusWindow()
-static ImGuiWindow* NavRestoreLastChildNavWindow(ImGuiWindow* window)
-{
- return window->NavLastChildNavWindow ? window->NavLastChildNavWindow : window;
-}
-
-static void NavRestoreLayer(int layer)
-{
- ImGuiContext& g = *GImGui;
- g.NavLayer = layer;
- if (layer == 0)
- g.NavWindow = NavRestoreLastChildNavWindow(g.NavWindow);
- if (layer == 0 && g.NavWindow->NavLastIds[0] != 0)
- ImGui::SetNavIDWithRectRel(g.NavWindow->NavLastIds[0], layer, g.NavWindow->NavRectRel[0]);
- else
- ImGui::NavInitWindow(g.NavWindow, true);
-}
-
-static inline void NavUpdateAnyRequestFlag()
-{
- ImGuiContext& g = *GImGui;
- g.NavAnyRequest = g.NavMoveRequest || g.NavInitRequest || (IMGUI_DEBUG_NAV_SCORING && g.NavWindow != NULL);
- if (g.NavAnyRequest)
- IM_ASSERT(g.NavWindow != NULL);
-}
-
-bool ImGui::NavMoveRequestButNoResultYet()
-{
- ImGuiContext& g = *GImGui;
- return g.NavMoveRequest && g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0;
-}
-
-void ImGui::NavMoveRequestCancel()
-{
- ImGuiContext& g = *GImGui;
- g.NavMoveRequest = false;
- NavUpdateAnyRequestFlag();
-}
-
-// 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)
-{
- ImGuiContext& g = *GImGui;
- //if (!g.IO.NavActive) // [2017/10/06] Removed this possibly redundant test but I am not sure of all the side-effects yet. Some of the feature here will need to work regardless of using a _NoNavInputs flag.
- // return;
-
- const ImGuiItemFlags item_flags = window->DC.ItemFlags;
- const ImRect nav_bb_rel(nav_bb.Min - window->Pos, nav_bb.Max - window->Pos);
-
- // Process Init Request
- if (g.NavInitRequest && g.NavLayer == window->DC.NavLayerCurrent)
- {
- // Even if 'ImGuiItemFlags_NoNavDefaultFocus' is on (typically collapse/close button) we record the first ResultId so they can be used as a fallback
- if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus) || g.NavInitResultId == 0)
- {
- g.NavInitResultId = id;
- g.NavInitResultRectRel = nav_bb_rel;
- }
- if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus))
- {
- g.NavInitRequest = false; // Found a match, clear request
- NavUpdateAnyRequestFlag();
- }
- }
-
- // Process Move Request (scoring for navigation)
- // FIXME-NAV: Consider policy for double scoring (scoring from NavScoringRectScreen + scoring from a rect wrapped according to current wrapping policy)
- if ((g.NavId != id || (g.NavMoveRequestFlags & ImGuiNavMoveFlags_AllowCurrentNavId)) && !(item_flags & ImGuiItemFlags_NoNav))
- {
- ImGuiNavMoveResult* result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther;
-#if IMGUI_DEBUG_NAV_SCORING
- // [DEBUG] Score all items in NavWindow at all times
- if (!g.NavMoveRequest)
- g.NavMoveDir = g.NavMoveDirLast;
- bool new_best = NavScoreItem(result, nav_bb) && g.NavMoveRequest;
-#else
- bool new_best = g.NavMoveRequest && NavScoreItem(result, nav_bb);
-#endif
- if (new_best)
- {
- result->ID = id;
- result->Window = window;
- result->RectRel = nav_bb_rel;
- }
-
- 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->ID = id;
- result->Window = window;
- result->RectRel = nav_bb_rel;
- }
- }
-
- // Update window-relative bounding box of navigated item
- if (g.NavId == id)
- {
- g.NavWindow = window; // Always refresh g.NavWindow, because some operations such as FocusItem() don't have a window.
- g.NavLayer = window->DC.NavLayerCurrent;
- g.NavIdIsAlive = true;
- g.NavIdTabCounter = window->FocusIdxTabCounter;
- window->NavRectRel[window->DC.NavLayerCurrent] = nav_bb_rel; // Store item bounding box (relative to window position)
- }
-}
-
// Declare item bounding box for clipping and interaction.
// Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface
// declare their minimum size requirement to ItemSize() and then use a larger region for drawing/interaction, which is passed to ItemAdd().
@@ -2972,662 +2634,6 @@ ImDrawListSharedData* ImGui::GetDrawListSharedData()
return &GImGui->DrawListSharedData;
}
-// This needs to be called before we submit any widget (aka in or before Begin)
-void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit)
-{
- ImGuiContext& g = *GImGui;
- IM_ASSERT(window == g.NavWindow);
- bool init_for_nav = false;
- if (!(window->Flags & ImGuiWindowFlags_NoNavInputs))
- if (!(window->Flags & ImGuiWindowFlags_ChildWindow) || (window->Flags & ImGuiWindowFlags_Popup) || (window->NavLastIds[0] == 0) || force_reinit)
- init_for_nav = true;
- if (init_for_nav)
- {
- SetNavID(0, g.NavLayer);
- g.NavInitRequest = true;
- g.NavInitRequestFromMove = false;
- g.NavInitResultId = 0;
- g.NavInitResultRectRel = ImRect();
- NavUpdateAnyRequestFlag();
- }
- else
- {
- g.NavId = window->NavLastIds[0];
- }
-}
-
-static ImVec2 NavCalcPreferredRefPos()
-{
- ImGuiContext& g = *GImGui;
- if (g.NavDisableHighlight || !g.NavDisableMouseHover || !g.NavWindow)
- return ImFloor(g.IO.MousePos);
-
- // When navigation is active and mouse is disabled, decide on an arbitrary position around the bottom left of the currently navigated item
- const ImRect& rect_rel = g.NavWindow->NavRectRel[g.NavLayer];
- ImVec2 pos = g.NavWindow->Pos + ImVec2(rect_rel.Min.x + ImMin(g.Style.FramePadding.x*4, rect_rel.GetWidth()), rect_rel.Max.y - ImMin(g.Style.FramePadding.y, rect_rel.GetHeight()));
- ImRect visible_rect = GetViewportRect();
- return ImFloor(ImClamp(pos, visible_rect.Min, visible_rect.Max)); // ImFloor() is important because non-integer mouse position application in back-end might be lossy and result in undesirable non-zero delta.
-}
-
-static int FindWindowIndex(ImGuiWindow* window) // FIXME-OPT O(N)
-{
- ImGuiContext& g = *GImGui;
- for (int i = g.Windows.Size-1; i >= 0; i--)
- if (g.Windows[i] == window)
- return i;
- return -1;
-}
-
-static ImGuiWindow* FindWindowNavFocusable(int i_start, int i_stop, int dir) // FIXME-OPT O(N)
-{
- ImGuiContext& g = *GImGui;
- for (int i = i_start; i >= 0 && i < g.Windows.Size && i != i_stop; i += dir)
- if (ImGui::IsWindowNavFocusable(g.Windows[i]))
- return g.Windows[i];
- return NULL;
-}
-
-float ImGui::GetNavInputAmount(ImGuiNavInput n, ImGuiInputReadMode mode)
-{
- ImGuiContext& g = *GImGui;
- if (mode == ImGuiInputReadMode_Down)
- return g.IO.NavInputs[n]; // Instant, read analog input (0.0f..1.0f, as provided by user)
-
- const float t = g.IO.NavInputsDownDuration[n];
- if (t < 0.0f && mode == ImGuiInputReadMode_Released) // Return 1.0f when just released, no repeat, ignore analog input.
- return (g.IO.NavInputsDownDurationPrev[n] >= 0.0f ? 1.0f : 0.0f);
- if (t < 0.0f)
- return 0.0f;
- if (mode == ImGuiInputReadMode_Pressed) // Return 1.0f when just pressed, no repeat, ignore analog input.
- return (t == 0.0f) ? 1.0f : 0.0f;
- if (mode == ImGuiInputReadMode_Repeat)
- return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 0.80f, g.IO.KeyRepeatRate * 0.80f);
- if (mode == ImGuiInputReadMode_RepeatSlow)
- return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 1.00f, g.IO.KeyRepeatRate * 2.00f);
- if (mode == ImGuiInputReadMode_RepeatFast)
- return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 0.80f, g.IO.KeyRepeatRate * 0.30f);
- return 0.0f;
-}
-
-ImVec2 ImGui::GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources, ImGuiInputReadMode mode, float slow_factor, float fast_factor)
-{
- ImVec2 delta(0.0f, 0.0f);
- if (dir_sources & ImGuiNavDirSourceFlags_Keyboard)
- delta += ImVec2(GetNavInputAmount(ImGuiNavInput_KeyRight_, mode) - GetNavInputAmount(ImGuiNavInput_KeyLeft_, mode), GetNavInputAmount(ImGuiNavInput_KeyDown_, mode) - GetNavInputAmount(ImGuiNavInput_KeyUp_, mode));
- if (dir_sources & ImGuiNavDirSourceFlags_PadDPad)
- delta += ImVec2(GetNavInputAmount(ImGuiNavInput_DpadRight, mode) - GetNavInputAmount(ImGuiNavInput_DpadLeft, mode), GetNavInputAmount(ImGuiNavInput_DpadDown, mode) - GetNavInputAmount(ImGuiNavInput_DpadUp, mode));
- if (dir_sources & ImGuiNavDirSourceFlags_PadLStick)
- delta += ImVec2(GetNavInputAmount(ImGuiNavInput_LStickRight, mode) - GetNavInputAmount(ImGuiNavInput_LStickLeft, mode), GetNavInputAmount(ImGuiNavInput_LStickDown, mode) - GetNavInputAmount(ImGuiNavInput_LStickUp, mode));
- if (slow_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakSlow))
- delta *= slow_factor;
- if (fast_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakFast))
- delta *= fast_factor;
- return delta;
-}
-
-static void NavUpdateWindowingHighlightWindow(int focus_change_dir)
-{
- ImGuiContext& g = *GImGui;
- IM_ASSERT(g.NavWindowingTarget);
- if (g.NavWindowingTarget->Flags & ImGuiWindowFlags_Modal)
- return;
-
- const int i_current = FindWindowIndex(g.NavWindowingTarget);
- ImGuiWindow* window_target = FindWindowNavFocusable(i_current + focus_change_dir, -INT_MAX, focus_change_dir);
- if (!window_target)
- window_target = FindWindowNavFocusable((focus_change_dir < 0) ? (g.Windows.Size - 1) : 0, i_current, focus_change_dir);
- if (window_target) // Don't reset windowing target if there's a single window in the list
- g.NavWindowingTarget = g.NavWindowingTargetAnim = window_target;
- g.NavWindowingToggleLayer = false;
-}
-
-// Window management mode (hold to: change focus/move/resize, tap to: toggle menu layer)
-static void ImGui::NavUpdateWindowing()
-{
- ImGuiContext& g = *GImGui;
- ImGuiWindow* apply_focus_window = NULL;
- bool apply_toggle_layer = false;
-
- ImGuiWindow* modal_window = GetFrontMostPopupModal();
- if (modal_window != NULL)
- {
- g.NavWindowingTarget = NULL;
- return;
- }
-
- // Fade out
- if (g.NavWindowingTargetAnim && g.NavWindowingTarget == NULL)
- {
- g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha - g.IO.DeltaTime * 10.0f, 0.0f);
- if (g.DimBgRatio <= 0.0f && g.NavWindowingHighlightAlpha <= 0.0f)
- g.NavWindowingTargetAnim = NULL;
- }
-
- // Start CTRL-TAB or Square+L/R window selection
- bool start_windowing_with_gamepad = !g.NavWindowingTarget && IsNavInputPressed(ImGuiNavInput_Menu, ImGuiInputReadMode_Pressed);
- bool start_windowing_with_keyboard = !g.NavWindowingTarget && g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab) && (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard);
- if (start_windowing_with_gamepad || start_windowing_with_keyboard)
- if (ImGuiWindow* window = g.NavWindow ? g.NavWindow : FindWindowNavFocusable(g.Windows.Size - 1, -INT_MAX, -1))
- {
- g.NavWindowingTarget = g.NavWindowingTargetAnim = window;
- g.NavWindowingTimer = g.NavWindowingHighlightAlpha = 0.0f;
- g.NavWindowingToggleLayer = start_windowing_with_keyboard ? false : true;
- g.NavInputSource = start_windowing_with_keyboard ? ImGuiInputSource_NavKeyboard : ImGuiInputSource_NavGamepad;
- }
-
- // Gamepad update
- g.NavWindowingTimer += g.IO.DeltaTime;
- if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_NavGamepad)
- {
- // Highlight only appears after a brief time holding the button, so that a fast tap on PadMenu (to toggle NavLayer) doesn't add visual noise
- g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f));
-
- // Select window to focus
- const int focus_change_dir = (int)IsNavInputPressed(ImGuiNavInput_FocusPrev, ImGuiInputReadMode_RepeatSlow) - (int)IsNavInputPressed(ImGuiNavInput_FocusNext, ImGuiInputReadMode_RepeatSlow);
- if (focus_change_dir != 0)
- {
- NavUpdateWindowingHighlightWindow(focus_change_dir);
- g.NavWindowingHighlightAlpha = 1.0f;
- }
-
- // Single press toggles NavLayer, long press with L/R apply actual focus on release (until then the window was merely rendered front-most)
- if (!IsNavInputDown(ImGuiNavInput_Menu))
- {
- g.NavWindowingToggleLayer &= (g.NavWindowingHighlightAlpha < 1.0f); // Once button was held long enough we don't consider it a tap-to-toggle-layer press anymore.
- if (g.NavWindowingToggleLayer && g.NavWindow)
- apply_toggle_layer = true;
- else if (!g.NavWindowingToggleLayer)
- apply_focus_window = g.NavWindowingTarget;
- g.NavWindowingTarget = NULL;
- }
- }
-
- // Keyboard: Focus
- if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_NavKeyboard)
- {
- // Visuals only appears after a brief time after pressing TAB the first time, so that a fast CTRL+TAB doesn't add visual noise
- g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); // 1.0f
- if (IsKeyPressedMap(ImGuiKey_Tab, true))
- NavUpdateWindowingHighlightWindow(g.IO.KeyShift ? +1 : -1);
- if (!g.IO.KeyCtrl)
- apply_focus_window = g.NavWindowingTarget;
- }
-
- // Keyboard: Press and Release ALT to toggle menu layer
- // FIXME: We lack an explicit IO variable for "is the imgui window focused", so compare mouse validity to detect the common case of back-end clearing releases all keys on ALT-TAB
- if ((g.ActiveId == 0 || g.ActiveIdAllowOverlap) && IsNavInputPressed(ImGuiNavInput_KeyMenu_, ImGuiInputReadMode_Released))
- if (IsMousePosValid(&g.IO.MousePos) == IsMousePosValid(&g.IO.MousePosPrev))
- apply_toggle_layer = true;
-
- // Move window
- if (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoMove))
- {
- ImVec2 move_delta;
- if (g.NavInputSource == ImGuiInputSource_NavKeyboard && !g.IO.KeyShift)
- move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down);
- if (g.NavInputSource == ImGuiInputSource_NavGamepad)
- move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down);
- if (move_delta.x != 0.0f || move_delta.y != 0.0f)
- {
- const float NAV_MOVE_SPEED = 800.0f;
- const float move_speed = ImFloor(NAV_MOVE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y)); // FIXME: Doesn't code variable framerate very well
- g.NavWindowingTarget->RootWindow->Pos += move_delta * move_speed;
- g.NavDisableMouseHover = true;
- MarkIniSettingsDirty(g.NavWindowingTarget);
- }
- }
-
- // Apply final focus
- if (apply_focus_window && (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindow))
- {
- g.NavDisableHighlight = false;
- g.NavDisableMouseHover = true;
- apply_focus_window = NavRestoreLastChildNavWindow(apply_focus_window);
- ClosePopupsOverWindow(apply_focus_window);
- FocusWindow(apply_focus_window);
- if (apply_focus_window->NavLastIds[0] == 0)
- NavInitWindow(apply_focus_window, false);
-
- // If the window only has a menu layer, select it directly
- if (apply_focus_window->DC.NavLayerActiveMask == (1 << 1))
- g.NavLayer = 1;
- }
- if (apply_focus_window)
- g.NavWindowingTarget = NULL;
-
- // Apply menu/layer toggle
- if (apply_toggle_layer && g.NavWindow)
- {
- // Move to parent menu if necessary
- ImGuiWindow* new_nav_window = g.NavWindow;
- while ((new_nav_window->DC.NavLayerActiveMask & (1 << 1)) == 0 && (new_nav_window->Flags & ImGuiWindowFlags_ChildWindow) != 0 && (new_nav_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0)
- new_nav_window = new_nav_window->ParentWindow;
-
- if (new_nav_window != g.NavWindow)
- {
- ImGuiWindow* old_nav_window = g.NavWindow;
- FocusWindow(new_nav_window);
- new_nav_window->NavLastChildNavWindow = old_nav_window;
- }
- g.NavDisableHighlight = false;
- g.NavDisableMouseHover = true;
- NavRestoreLayer((g.NavWindow->DC.NavLayerActiveMask & (1 << 1)) ? (g.NavLayer ^ 1) : 0);
- }
-}
-
-// Window has already passed the IsWindowNavFocusable()
-static const char* GetFallbackWindowNameForWindowingList(ImGuiWindow* window)
-{
- if (window->Flags & ImGuiWindowFlags_Popup)
- return "(Popup)";
- if ((window->Flags & ImGuiWindowFlags_MenuBar) && strcmp(window->Name, "##MainMenuBar") == 0)
- return "(Main menu bar)";
- return "(Untitled)";
-}
-
-// Overlay displayed when using CTRL+TAB. Called by EndFrame().
-void ImGui::NavUpdateWindowingList()
-{
- ImGuiContext& g = *GImGui;
- IM_ASSERT(g.NavWindowingTarget != NULL);
-
- if (g.NavWindowingTimer < NAV_WINDOWING_LIST_APPEAR_DELAY)
- return;
-
- if (g.NavWindowingList == NULL)
- g.NavWindowingList = FindWindowByName("###NavWindowingList");
- SetNextWindowSizeConstraints(ImVec2(g.IO.DisplaySize.x * 0.20f, g.IO.DisplaySize.y * 0.20f), ImVec2(FLT_MAX, FLT_MAX));
- SetNextWindowPos(g.IO.DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
- PushStyleVar(ImGuiStyleVar_WindowPadding, g.Style.WindowPadding * 2.0f);
- Begin("###NavWindowingList", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings);
- for (int n = g.Windows.Size - 1; n >= 0; n--)
- {
- ImGuiWindow* window = g.Windows[n];
- if (!IsWindowNavFocusable(window))
- continue;
- const char* label = window->Name;
- if (label == FindRenderedTextEnd(label))
- label = GetFallbackWindowNameForWindowingList(window);
- Selectable(label, g.NavWindowingTarget == window);
- }
- End();
- PopStyleVar();
-}
-
-// Scroll to keep newly navigated item fully into view
-// NB: We modify rect_rel by the amount we scrolled for, so it is immediately updated.
-static void NavScrollToBringItemIntoView(ImGuiWindow* window, const ImRect& item_rect)
-{
- ImRect window_rect(window->InnerMainRect.Min - ImVec2(1, 1), window->InnerMainRect.Max + ImVec2(1, 1));
- //g.OverlayDrawList.AddRect(window_rect.Min, window_rect.Max, IM_COL32_WHITE); // [DEBUG]
- if (window_rect.Contains(item_rect))
- return;
-
- ImGuiContext& g = *GImGui;
- if (window->ScrollbarX && item_rect.Min.x < window_rect.Min.x)
- {
- window->ScrollTarget.x = item_rect.Min.x - window->Pos.x + window->Scroll.x - g.Style.ItemSpacing.x;
- window->ScrollTargetCenterRatio.x = 0.0f;
- }
- else if (window->ScrollbarX && item_rect.Max.x >= window_rect.Max.x)
- {
- window->ScrollTarget.x = item_rect.Max.x - window->Pos.x + window->Scroll.x + g.Style.ItemSpacing.x;
- window->ScrollTargetCenterRatio.x = 1.0f;
- }
- if (item_rect.Min.y < window_rect.Min.y)
- {
- window->ScrollTarget.y = item_rect.Min.y - window->Pos.y + window->Scroll.y - g.Style.ItemSpacing.y;
- window->ScrollTargetCenterRatio.y = 0.0f;
- }
- else if (item_rect.Max.y >= window_rect.Max.y)
- {
- window->ScrollTarget.y = item_rect.Max.y - window->Pos.y + window->Scroll.y + g.Style.ItemSpacing.y;
- window->ScrollTargetCenterRatio.y = 1.0f;
- }
-}
-
-static void ImGui::NavUpdate()
-{
- ImGuiContext& g = *GImGui;
- g.IO.WantSetMousePos = false;
-
-#if 0
- if (g.NavScoringCount > 0) printf("[%05d] NavScoringCount %d for '%s' layer %d (Init:%d, Move:%d)\n", g.FrameCount, g.NavScoringCount, g.NavWindow ? g.NavWindow->Name : "NULL", g.NavLayer, g.NavInitRequest || g.NavInitResultId != 0, g.NavMoveRequest);
-#endif
-
- bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0;
- bool nav_gamepad_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (g.IO.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
-
- // Set input source as Gamepad when buttons are pressed before we map Keyboard (some features differs when used with Gamepad vs Keyboard)
- if (nav_gamepad_active)
- if (g.IO.NavInputs[ImGuiNavInput_Activate] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Input] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Cancel] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Menu] > 0.0f)
- g.NavInputSource = ImGuiInputSource_NavGamepad;
-
- // Update Keyboard->Nav inputs mapping
- if (nav_keyboard_active)
- {
- #define NAV_MAP_KEY(_KEY, _NAV_INPUT) if (IsKeyDown(g.IO.KeyMap[_KEY])) { g.IO.NavInputs[_NAV_INPUT] = 1.0f; g.NavInputSource = ImGuiInputSource_NavKeyboard; }
- NAV_MAP_KEY(ImGuiKey_Space, ImGuiNavInput_Activate );
- NAV_MAP_KEY(ImGuiKey_Enter, ImGuiNavInput_Input );
- NAV_MAP_KEY(ImGuiKey_Escape, ImGuiNavInput_Cancel );
- NAV_MAP_KEY(ImGuiKey_LeftArrow, ImGuiNavInput_KeyLeft_ );
- NAV_MAP_KEY(ImGuiKey_RightArrow,ImGuiNavInput_KeyRight_);
- NAV_MAP_KEY(ImGuiKey_UpArrow, ImGuiNavInput_KeyUp_ );
- NAV_MAP_KEY(ImGuiKey_DownArrow, ImGuiNavInput_KeyDown_ );
- if (g.IO.KeyCtrl) g.IO.NavInputs[ImGuiNavInput_TweakSlow] = 1.0f;
- if (g.IO.KeyShift) g.IO.NavInputs[ImGuiNavInput_TweakFast] = 1.0f;
- if (g.IO.KeyAlt) g.IO.NavInputs[ImGuiNavInput_KeyMenu_] = 1.0f;
- #undef NAV_MAP_KEY
- }
-
- memcpy(g.IO.NavInputsDownDurationPrev, g.IO.NavInputsDownDuration, sizeof(g.IO.NavInputsDownDuration));
- for (int i = 0; i < IM_ARRAYSIZE(g.IO.NavInputs); i++)
- g.IO.NavInputsDownDuration[i] = (g.IO.NavInputs[i] > 0.0f) ? (g.IO.NavInputsDownDuration[i] < 0.0f ? 0.0f : g.IO.NavInputsDownDuration[i] + g.IO.DeltaTime) : -1.0f;
-
- // Process navigation init request (select first/default focus)
- if (g.NavInitResultId != 0 && (!g.NavDisableHighlight || g.NavInitRequestFromMove))
- {
- // Apply result from previous navigation init request (will typically select the first item, unless SetItemDefaultFocus() has been called)
- IM_ASSERT(g.NavWindow);
- if (g.NavInitRequestFromMove)
- SetNavIDWithRectRel(g.NavInitResultId, g.NavLayer, g.NavInitResultRectRel);
- else
- SetNavID(g.NavInitResultId, g.NavLayer);
- g.NavWindow->NavRectRel[g.NavLayer] = g.NavInitResultRectRel;
- }
- g.NavInitRequest = false;
- g.NavInitRequestFromMove = false;
- g.NavInitResultId = 0;
- g.NavJustMovedToId = 0;
-
- // Process navigation move request
- if (g.NavMoveRequest && (g.NavMoveResultLocal.ID != 0 || g.NavMoveResultOther.ID != 0))
- {
- // Select which result to use
- ImGuiNavMoveResult* result = (g.NavMoveResultLocal.ID != 0) ? &g.NavMoveResultLocal : &g.NavMoveResultOther;
-
- // PageUp/PageDown behavior first jumps to the bottom/top mostly visible item, _otherwise_ use the result from the previous/next page.
- if (g.NavMoveRequestFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet)
- if (g.NavMoveResultLocalVisibleSet.ID != 0 && g.NavMoveResultLocalVisibleSet.ID != g.NavId)
- result = &g.NavMoveResultLocalVisibleSet;
-
- // Maybe entering a flattened child from the outside? In this case solve the tie using the regular scoring rules.
- if (result != &g.NavMoveResultOther && g.NavMoveResultOther.ID != 0 && g.NavMoveResultOther.Window->ParentWindow == g.NavWindow)
- if ((g.NavMoveResultOther.DistBox < result->DistBox) || (g.NavMoveResultOther.DistBox == result->DistBox && g.NavMoveResultOther.DistCenter < result->DistCenter))
- result = &g.NavMoveResultOther;
- IM_ASSERT(g.NavWindow && result->Window);
-
- // Scroll to keep newly navigated item fully into view.
- if (g.NavLayer == 0)
- {
- ImRect rect_abs = ImRect(result->RectRel.Min + result->Window->Pos, result->RectRel.Max + result->Window->Pos);
- NavScrollToBringItemIntoView(result->Window, rect_abs);
-
- // Estimate upcoming scroll so we can offset our result position so mouse position can be applied immediately after in NavUpdate()
- ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(result->Window, false);
- ImVec2 delta_scroll = result->Window->Scroll - next_scroll;
- result->RectRel.Translate(delta_scroll);
-
- // Also scroll parent window to keep us into view if necessary (we could/should technically recurse back the whole the parent hierarchy).
- if (result->Window->Flags & ImGuiWindowFlags_ChildWindow)
- NavScrollToBringItemIntoView(result->Window->ParentWindow, ImRect(rect_abs.Min + delta_scroll, rect_abs.Max + delta_scroll));
- }
-
- // Apply result from previous frame navigation directional move request
- ClearActiveID();
- g.NavWindow = result->Window;
- SetNavIDWithRectRel(result->ID, g.NavLayer, result->RectRel);
- g.NavJustMovedToId = result->ID;
- g.NavMoveFromClampedRefRect = false;
- }
-
- // When a forwarded move request failed, we restore the highlight that we disabled during the forward frame
- if (g.NavMoveRequestForward == ImGuiNavForward_ForwardActive)
- {
- IM_ASSERT(g.NavMoveRequest);
- if (g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0)
- g.NavDisableHighlight = false;
- g.NavMoveRequestForward = ImGuiNavForward_None;
- }
-
- // Apply application mouse position movement, after we had a chance to process move request result.
- if (g.NavMousePosDirty && g.NavIdIsAlive)
- {
- // Set mouse position given our knowledge of the navigated item position from last frame
- if ((g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) && (g.IO.BackendFlags & ImGuiBackendFlags_HasSetMousePos))
- {
- if (!g.NavDisableHighlight && g.NavDisableMouseHover && g.NavWindow)
- {
- g.IO.MousePos = g.IO.MousePosPrev = NavCalcPreferredRefPos();
- g.IO.WantSetMousePos = true;
- }
- }
- g.NavMousePosDirty = false;
- }
- g.NavIdIsAlive = false;
- g.NavJustTabbedId = 0;
- IM_ASSERT(g.NavLayer == 0 || g.NavLayer == 1);
-
- // Store our return window (for returning from Layer 1 to Layer 0) and clear it as soon as we step back in our own Layer 0
- if (g.NavWindow)
- NavSaveLastChildNavWindow(g.NavWindow);
- if (g.NavWindow && g.NavWindow->NavLastChildNavWindow != NULL && g.NavLayer == 0)
- g.NavWindow->NavLastChildNavWindow = NULL;
-
- NavUpdateWindowing();
-
- // Set output flags for user application
- g.IO.NavActive = (nav_keyboard_active || nav_gamepad_active) && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs);
- g.IO.NavVisible = (g.IO.NavActive && g.NavId != 0 && !g.NavDisableHighlight) || (g.NavWindowingTarget != NULL) || g.NavInitRequest;
-
- // Process NavCancel input (to close a popup, get back to parent, clear focus)
- if (IsNavInputPressed(ImGuiNavInput_Cancel, ImGuiInputReadMode_Pressed))
- {
- if (g.ActiveId != 0)
- {
- ClearActiveID();
- }
- else if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow) && !(g.NavWindow->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->ParentWindow)
- {
- // Exit child window
- ImGuiWindow* child_window = g.NavWindow;
- ImGuiWindow* parent_window = g.NavWindow->ParentWindow;
- IM_ASSERT(child_window->ChildId != 0);
- FocusWindow(parent_window);
- SetNavID(child_window->ChildId, 0);
- g.NavIdIsAlive = false;
- if (g.NavDisableMouseHover)
- g.NavMousePosDirty = true;
- }
- else if (g.OpenPopupStack.Size > 0)
- {
- // Close open popup/menu
- if (!(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal))
- ClosePopupToLevel(g.OpenPopupStack.Size - 1);
- }
- else if (g.NavLayer != 0)
- {
- // Leave the "menu" layer
- NavRestoreLayer(0);
- }
- else
- {
- // Clear NavLastId for popups but keep it for regular child window so we can leave one and come back where we were
- if (g.NavWindow && ((g.NavWindow->Flags & ImGuiWindowFlags_Popup) || !(g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow)))
- g.NavWindow->NavLastIds[0] = 0;
- g.NavId = 0;
- }
- }
-
- // Process manual activation request
- g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = 0;
- if (g.NavId != 0 && !g.NavDisableHighlight && !g.NavWindowingTarget && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
- {
- bool activate_down = IsNavInputDown(ImGuiNavInput_Activate);
- bool activate_pressed = activate_down && IsNavInputPressed(ImGuiNavInput_Activate, ImGuiInputReadMode_Pressed);
- if (g.ActiveId == 0 && activate_pressed)
- g.NavActivateId = g.NavId;
- if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_down)
- g.NavActivateDownId = g.NavId;
- if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_pressed)
- g.NavActivatePressedId = g.NavId;
- if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && IsNavInputPressed(ImGuiNavInput_Input, ImGuiInputReadMode_Pressed))
- g.NavInputId = g.NavId;
- }
- if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
- g.NavDisableHighlight = true;
- if (g.NavActivateId != 0)
- IM_ASSERT(g.NavActivateDownId == g.NavActivateId);
- g.NavMoveRequest = false;
-
- // Process programmatic activation request
- if (g.NavNextActivateId != 0)
- g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = g.NavNextActivateId;
- g.NavNextActivateId = 0;
-
- // Initiate directional inputs request
- const int allowed_dir_flags = (g.ActiveId == 0) ? ~0 : g.ActiveIdAllowNavDirFlags;
- if (g.NavMoveRequestForward == ImGuiNavForward_None)
- {
- g.NavMoveDir = ImGuiDir_None;
- g.NavMoveRequestFlags = 0;
- if (g.NavWindow && !g.NavWindowingTarget && allowed_dir_flags && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
- {
- if ((allowed_dir_flags & (1<<ImGuiDir_Left)) && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadLeft, ImGuiNavInput_KeyLeft_, ImGuiInputReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Left;
- if ((allowed_dir_flags & (1<<ImGuiDir_Right)) && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadRight,ImGuiNavInput_KeyRight_,ImGuiInputReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Right;
- if ((allowed_dir_flags & (1<<ImGuiDir_Up)) && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadUp, ImGuiNavInput_KeyUp_, ImGuiInputReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Up;
- if ((allowed_dir_flags & (1<<ImGuiDir_Down)) && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadDown, ImGuiNavInput_KeyDown_, ImGuiInputReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Down;
- }
- g.NavMoveClipDir = g.NavMoveDir;
- }
- else
- {
- // Forwarding previous request (which has been modified, e.g. wrap around menus rewrite the requests with a starting rectangle at the other side of the window)
- // (Preserve g.NavMoveRequestFlags, g.NavMoveClipDir which were set by the NavMoveRequestForward() function)
- IM_ASSERT(g.NavMoveDir != ImGuiDir_None && g.NavMoveClipDir != ImGuiDir_None);
- IM_ASSERT(g.NavMoveRequestForward == ImGuiNavForward_ForwardQueued);
- g.NavMoveRequestForward = ImGuiNavForward_ForwardActive;
- }
-
- // PageUp/PageDown scroll
- float nav_scoring_rect_offset_y = 0.0f;
- if (nav_keyboard_active && g.NavMoveDir == ImGuiDir_None && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget && g.NavLayer == 0)
- {
- ImGuiWindow* window = g.NavWindow;
- bool page_up_held = IsKeyDown(g.IO.KeyMap[ImGuiKey_PageUp]) && (allowed_dir_flags & (1 << ImGuiDir_Up));
- bool page_down_held = IsKeyDown(g.IO.KeyMap[ImGuiKey_PageDown]) && (allowed_dir_flags & (1 << ImGuiDir_Down));
- if ((page_up_held && !page_down_held) || (page_down_held && !page_up_held))
- {
- if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll)
- {
- // Fallback manual-scroll when window has no navigable item
- if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageUp], true))
- SetWindowScrollY(window, window->Scroll.y - window->InnerClipRect.GetHeight());
- else if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageDown], true))
- SetWindowScrollY(window, window->Scroll.y + window->InnerClipRect.GetHeight());
- }
- else
- {
- const ImRect& nav_rect_rel = window->NavRectRel[g.NavLayer];
- const float page_offset_y = ImMax(0.0f, window->InnerClipRect.GetHeight() - window->CalcFontSize() * 1.0f + nav_rect_rel.GetHeight());
- if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageUp], true))
- {
- nav_scoring_rect_offset_y = -page_offset_y;
- g.NavMoveDir = ImGuiDir_Down; // Because our scoring rect is offset, we intentionally request the opposite direction (so we can always land on the last item)
- g.NavMoveClipDir = ImGuiDir_Up;
- g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet;
- }
- else if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageDown], true))
- {
- nav_scoring_rect_offset_y = +page_offset_y;
- g.NavMoveDir = ImGuiDir_Up; // Because our scoring rect is offset, we intentionally request the opposite direction (so we can always land on the last item)
- g.NavMoveClipDir = ImGuiDir_Down;
- g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet;
- }
- }
- }
- }
-
- if (g.NavMoveDir != ImGuiDir_None)
- {
- g.NavMoveRequest = true;
- g.NavMoveDirLast = g.NavMoveDir;
- }
-
- // If we initiate a movement request and have no current NavId, we initiate a InitDefautRequest that will be used as a fallback if the direction fails to find a match
- if (g.NavMoveRequest && g.NavId == 0)
- {
- g.NavInitRequest = g.NavInitRequestFromMove = true;
- g.NavInitResultId = 0;
- g.NavDisableHighlight = false;
- }
-
- NavUpdateAnyRequestFlag();
-
- // Scrolling
- if (g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget)
- {
- // *Fallback* manual-scroll with Nav directional keys when window has no navigable item
- ImGuiWindow* window = g.NavWindow;
- const float scroll_speed = ImFloor(window->CalcFontSize() * 100 * g.IO.DeltaTime + 0.5f); // We need round the scrolling speed because sub-pixel scroll isn't reliably supported.
- if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll && g.NavMoveRequest)
- {
- if (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right)
- SetWindowScrollX(window, ImFloor(window->Scroll.x + ((g.NavMoveDir == ImGuiDir_Left) ? -1.0f : +1.0f) * scroll_speed));
- if (g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down)
- SetWindowScrollY(window, ImFloor(window->Scroll.y + ((g.NavMoveDir == ImGuiDir_Up) ? -1.0f : +1.0f) * scroll_speed));
- }
-
- // *Normal* Manual scroll with NavScrollXXX keys
- // Next movement request will clamp the NavId reference rectangle to the visible area, so navigation will resume within those bounds.
- ImVec2 scroll_dir = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down, 1.0f/10.0f, 10.0f);
- if (scroll_dir.x != 0.0f && window->ScrollbarX)
- {
- SetWindowScrollX(window, ImFloor(window->Scroll.x + scroll_dir.x * scroll_speed));
- g.NavMoveFromClampedRefRect = true;
- }
- if (scroll_dir.y != 0.0f)
- {
- SetWindowScrollY(window, ImFloor(window->Scroll.y + scroll_dir.y * scroll_speed));
- g.NavMoveFromClampedRefRect = true;
- }
- }
-
- // Reset search results
- g.NavMoveResultLocal.Clear();
- g.NavMoveResultLocalVisibleSet.Clear();
- g.NavMoveResultOther.Clear();
-
- // When we have manually scrolled (without using navigation) and NavId becomes out of bounds, we project its bounding box to the visible area to restart navigation within visible items
- if (g.NavMoveRequest && g.NavMoveFromClampedRefRect && g.NavLayer == 0)
- {
- ImGuiWindow* window = g.NavWindow;
- ImRect window_rect_rel(window->InnerMainRect.Min - window->Pos - ImVec2(1,1), window->InnerMainRect.Max - window->Pos + ImVec2(1,1));
- if (!window_rect_rel.Contains(window->NavRectRel[g.NavLayer]))
- {
- float pad = window->CalcFontSize() * 0.5f;
- window_rect_rel.Expand(ImVec2(-ImMin(window_rect_rel.GetWidth(), pad), -ImMin(window_rect_rel.GetHeight(), pad))); // Terrible approximation for the intent of starting navigation from first fully visible item
- window->NavRectRel[g.NavLayer].ClipWith(window_rect_rel);
- g.NavId = 0;
- }
- g.NavMoveFromClampedRefRect = false;
- }
-
- // For scoring we use a single segment on the left side our current item bounding box (not touching the edge to avoid box overlap with zero-spaced items)
- ImRect nav_rect_rel = (g.NavWindow && !g.NavWindow->NavRectRel[g.NavLayer].IsInverted()) ? g.NavWindow->NavRectRel[g.NavLayer] : ImRect(0,0,0,0);
- g.NavScoringRectScreen = g.NavWindow ? ImRect(g.NavWindow->Pos + nav_rect_rel.Min, g.NavWindow->Pos + nav_rect_rel.Max) : GetViewportRect();
- g.NavScoringRectScreen.TranslateY(nav_scoring_rect_offset_y);
- g.NavScoringRectScreen.Min.x = ImMin(g.NavScoringRectScreen.Min.x + 1.0f, g.NavScoringRectScreen.Max.x);
- g.NavScoringRectScreen.Max.x = g.NavScoringRectScreen.Min.x;
- IM_ASSERT(!g.NavScoringRectScreen.IsInverted()); // Ensure if we have a finite, non-inverted bounding box here will allows us to remove extraneous ImFabs() calls in NavScoreItem().
- //g.OverlayDrawList.AddRect(g.NavScoringRectScreen.Min, g.NavScoringRectScreen.Max, IM_COL32(255,200,0,255)); // [DEBUG]
- g.NavScoringCount = 0;
-#if IMGUI_DEBUG_NAV_RECTS
- if (g.NavWindow) { for (int layer = 0; layer < 2; layer++) GetOverlayDrawList()->AddRect(g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Min, g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Max, IM_COL32(255,200,0,255)); } // [DEBUG]
- if (g.NavWindow) { ImU32 col = (g.NavWindow->HiddenFrames == 0) ? IM_COL32(255,0,255,255) : IM_COL32(255,0,0,255); ImVec2 p = NavCalcPreferredRefPos(); char buf[32]; ImFormatString(buf, 32, "%d", g.NavLayer); g.OverlayDrawList.AddCircleFilled(p, 3.0f, col); g.OverlayDrawList.AddText(NULL, 13.0f, p + ImVec2(8,-4), col, buf); }
-#endif
-}
-
void ImGui::StartMouseMovingWindow(ImGuiWindow* window)
{
// Set ActiveId even if the _NoMove flag is set. Without it, dragging away from a window with _NoMove would activate hover on other windows.
@@ -4010,65 +3016,6 @@ void ImGui::NewFrame()
Begin("Debug##Default");
}
-static void* SettingsHandlerWindow_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name)
-{
- ImGuiWindowSettings* settings = ImGui::FindWindowSettings(ImHash(name, 0));
- if (!settings)
- settings = CreateNewWindowSettings(name);
- return (void*)settings;
-}
-
-static void SettingsHandlerWindow_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line)
-{
- ImGuiWindowSettings* settings = (ImGuiWindowSettings*)entry;
- float x, y;
- int i;
- if (sscanf(line, "Pos=%f,%f", &x, &y) == 2) settings->Pos = ImVec2(x, y);
- else if (sscanf(line, "Size=%f,%f", &x, &y) == 2) settings->Size = ImMax(ImVec2(x, y), GImGui->Style.WindowMinSize);
- else if (sscanf(line, "Collapsed=%d", &i) == 1) settings->Collapsed = (i != 0);
-}
-
-static void SettingsHandlerWindow_WriteAll(ImGuiContext* imgui_ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf)
-{
- // Gather data from windows that were active during this session
- ImGuiContext& g = *imgui_ctx;
- for (int i = 0; i != g.Windows.Size; i++)
- {
- ImGuiWindow* window = g.Windows[i];
- if (window->Flags & ImGuiWindowFlags_NoSavedSettings)
- continue;
-
- ImGuiWindowSettings* settings = (window->SettingsIdx != -1) ? &g.SettingsWindows[window->SettingsIdx] : ImGui::FindWindowSettings(window->ID);
- if (!settings)
- {
- settings = CreateNewWindowSettings(window->Name);
- window->SettingsIdx = g.SettingsWindows.index_from_pointer(settings);
- }
- IM_ASSERT(settings->ID == window->ID);
- settings->Pos = window->Pos;
- settings->Size = window->SizeFull;
- settings->Collapsed = window->Collapsed;
- }
-
- // Write a buffer
- // If a window wasn't opened in this session we preserve its settings
- buf->reserve(buf->size() + g.SettingsWindows.Size * 96); // ballpark reserve
- for (int i = 0; i != g.SettingsWindows.Size; i++)
- {
- const ImGuiWindowSettings* settings = &g.SettingsWindows[i];
- if (settings->Pos.x == FLT_MAX)
- continue;
- const char* name = settings->Name;
- if (const char* p = strstr(name, "###")) // Skip to the "###" marker if any. We don't skip past to match the behavior of GetID()
- name = p;
- buf->appendf("[%s][%s]\n", handler->TypeName, name);
- buf->appendf("Pos=%d,%d\n", (int)settings->Pos.x, (int)settings->Pos.y);
- buf->appendf("Size=%d,%d\n", (int)settings->Size.x, (int)settings->Size.y);
- buf->appendf("Collapsed=%d\n", settings->Collapsed);
- buf->appendf("\n");
- }
-}
-
void ImGui::Initialize(ImGuiContext* context)
{
ImGuiContext& g = *context;
@@ -4143,154 +3090,6 @@ void ImGui::Shutdown(ImGuiContext* context)
g.Initialized = false;
}
-ImGuiWindowSettings* ImGui::FindWindowSettings(ImGuiID id)
-{
- ImGuiContext& g = *GImGui;
- for (int i = 0; i != g.SettingsWindows.Size; i++)
- if (g.SettingsWindows[i].ID == id)
- return &g.SettingsWindows[i];
- return NULL;
-}
-
-static ImGuiWindowSettings* CreateNewWindowSettings(const char* name)
-{
- ImGuiContext& g = *GImGui;
- g.SettingsWindows.push_back(ImGuiWindowSettings());
- ImGuiWindowSettings* settings = &g.SettingsWindows.back();
- settings->Name = ImStrdup(name);
- settings->ID = ImHash(name, 0);
- return settings;
-}
-
-void ImGui::LoadIniSettingsFromDisk(const char* ini_filename)
-{
- size_t file_data_size = 0;
- char* file_data = (char*)ImFileLoadToMemory(ini_filename, "rb", &file_data_size);
- if (!file_data)
- return;
- LoadIniSettingsFromMemory(file_data, (size_t)file_data_size);
- ImGui::MemFree(file_data);
-}
-
-ImGuiSettingsHandler* ImGui::FindSettingsHandler(const char* type_name)
-{
- ImGuiContext& g = *GImGui;
- const ImGuiID type_hash = ImHash(type_name, 0, 0);
- for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++)
- if (g.SettingsHandlers[handler_n].TypeHash == type_hash)
- return &g.SettingsHandlers[handler_n];
- return NULL;
-}
-
-// Zero-tolerance, no error reporting, cheap .ini parsing
-void ImGui::LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size)
-{
- ImGuiContext& g = *GImGui;
- IM_ASSERT(g.Initialized);
- IM_ASSERT(g.SettingsLoaded == false && g.FrameCount == 0);
-
- // For user convenience, we allow passing a non zero-terminated string (hence the ini_size parameter).
- // For our convenience and to make the code simpler, we'll also write zero-terminators within the buffer. So let's create a writable copy..
- if (ini_size == 0)
- ini_size = strlen(ini_data);
- char* buf = (char*)ImGui::MemAlloc(ini_size + 1);
- char* buf_end = buf + ini_size;
- memcpy(buf, ini_data, ini_size);
- buf[ini_size] = 0;
-
- void* entry_data = NULL;
- ImGuiSettingsHandler* entry_handler = NULL;
-
- char* line_end = NULL;
- for (char* line = buf; line < buf_end; line = line_end + 1)
- {
- // Skip new lines markers, then find end of the line
- while (*line == '\n' || *line == '\r')
- line++;
- line_end = line;
- while (line_end < buf_end && *line_end != '\n' && *line_end != '\r')
- line_end++;
- line_end[0] = 0;
-
- if (line[0] == '[' && line_end > line && line_end[-1] == ']')
- {
- // Parse "[Type][Name]". Note that 'Name' can itself contains [] characters, which is acceptable with the current format and parsing code.
- line_end[-1] = 0;
- const char* name_end = line_end - 1;
- const char* type_start = line + 1;
- char* type_end = (char*)(intptr_t)ImStrchrRange(type_start, name_end, ']');
- const char* name_start = type_end ? ImStrchrRange(type_end + 1, name_end, '[') : NULL;
- if (!type_end || !name_start)
- {
- name_start = type_start; // Import legacy entries that have no type
- type_start = "Window";
- }
- else
- {
- *type_end = 0; // Overwrite first ']'
- name_start++; // Skip second '['
- }
- entry_handler = FindSettingsHandler(type_start);
- entry_data = entry_handler ? entry_handler->ReadOpenFn(&g, entry_handler, name_start) : NULL;
- }
- else if (entry_handler != NULL && entry_data != NULL)
- {
- // Let type handler parse the line
- entry_handler->ReadLineFn(&g, entry_handler, entry_data, line);
- }
- }
- ImGui::MemFree(buf);
- g.SettingsLoaded = true;
-}
-
-void ImGui::SaveIniSettingsToDisk(const char* ini_filename)
-{
- ImGuiContext& g = *GImGui;
- g.SettingsDirtyTimer = 0.0f;
- if (!ini_filename)
- return;
-
- size_t ini_data_size = 0;
- const char* ini_data = SaveIniSettingsToMemory(&ini_data_size);
- FILE* f = ImFileOpen(ini_filename, "wt");
- if (!f)
- return;
- fwrite(ini_data, sizeof(char), ini_data_size, f);
- fclose(f);
-}
-
-// Call registered handlers (e.g. SettingsHandlerWindow_WriteAll() + custom handlers) to write their stuff into a text buffer
-const char* ImGui::SaveIniSettingsToMemory(size_t* out_size)
-{
- ImGuiContext& g = *GImGui;
- g.SettingsDirtyTimer = 0.0f;
- g.SettingsIniData.Buf.resize(0);
- g.SettingsIniData.Buf.push_back(0);
- for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++)
- {
- ImGuiSettingsHandler* handler = &g.SettingsHandlers[handler_n];
- handler->WriteAllFn(&g, handler, &g.SettingsIniData);
- }
- if (out_size)
- *out_size = (size_t)g.SettingsIniData.size();
- return g.SettingsIniData.c_str();
-}
-
-void ImGui::MarkIniSettingsDirty()
-{
- ImGuiContext& g = *GImGui;
- if (g.SettingsDirtyTimer <= 0.0f)
- g.SettingsDirtyTimer = g.IO.IniSavingRate;
-}
-
-void ImGui::MarkIniSettingsDirty(ImGuiWindow* window)
-{
- ImGuiContext& g = *GImGui;
- if (!(window->Flags & ImGuiWindowFlags_NoSavedSettings))
- if (g.SettingsDirtyTimer <= 0.0f)
- g.SettingsDirtyTimer = g.IO.IniSavingRate;
-}
-
// FIXME: Add a more explicit sort order in the window structure.
static int IMGUI_CDECL ChildWindowComparer(const void* lhs, const void* rhs)
{
@@ -4588,74 +3387,6 @@ const char* ImGui::FindRenderedTextEnd(const char* text, const char* text_end)
return text_display_end;
}
-// Pass text data straight to log (without being displayed)
-void ImGui::LogText(const char* fmt, ...)
-{
- ImGuiContext& g = *GImGui;
- if (!g.LogEnabled)
- return;
-
- va_list args;
- va_start(args, fmt);
- if (g.LogFile)
- vfprintf(g.LogFile, fmt, args);
- else
- g.LogClipboard.appendfv(fmt, args);
- va_end(args);
-}
-
-// Internal version that takes a position to decide on newline placement and pad items according to their depth.
-// We split text into individual lines to add current tree level padding
-void ImGui::LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end)
-{
- ImGuiContext& g = *GImGui;
- ImGuiWindow* window = g.CurrentWindow;
-
- if (!text_end)
- text_end = ImGui::FindRenderedTextEnd(text, text_end);
-
- const bool log_new_line = ref_pos && (ref_pos->y > window->DC.LogLinePosY + 1);
- if (ref_pos)
- window->DC.LogLinePosY = ref_pos->y;
-
- const char* text_remaining = text;
- if (g.LogStartDepth > window->DC.TreeDepth) // Re-adjust padding if we have popped out of our starting depth
- g.LogStartDepth = window->DC.TreeDepth;
- const int tree_depth = (window->DC.TreeDepth - g.LogStartDepth);
- for (;;)
- {
- // Split the string. Each new line (after a '\n') is followed by spacing corresponding to the current depth of our log entry.
- const char* line_end = text_remaining;
- while (line_end < text_end)
- if (*line_end == '\n')
- break;
- else
- line_end++;
- if (line_end >= text_end)
- line_end = NULL;
-
- const bool is_first_line = (text == text_remaining);
- bool is_last_line = false;
- if (line_end == NULL)
- {
- is_last_line = true;
- line_end = text_end;
- }
- if (line_end != NULL && !(is_last_line && (line_end - text_remaining)==0))
- {
- const int char_count = (int)(line_end - text_remaining);
- if (log_new_line || !is_first_line)
- ImGui::LogText(IM_NEWLINE "%*s%.*s", tree_depth*4, "", char_count, text_remaining);
- else
- ImGui::LogText(" %.*s", char_count, text_remaining);
- }
-
- if (is_last_line)
- break;
- text_remaining = line_end + 1;
- }
-}
-
// Internal ImGui functions to render text
// RenderText***() functions calls ImDrawList::AddText() calls ImBitmapFont::RenderText()
void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash)
@@ -5257,376 +3988,6 @@ static ImRect GetViewportRect()
return ImRect(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y);
}
-// Not exposed publicly as BeginTooltip() because bool parameters are evil. Let's see if other needs arise first.
-void ImGui::BeginTooltipEx(ImGuiWindowFlags extra_flags, bool override_previous_tooltip)
-{
- ImGuiContext& g = *GImGui;
- char window_name[16];
- ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", g.TooltipOverrideCount);
- if (override_previous_tooltip)
- if (ImGuiWindow* window = FindWindowByName(window_name))
- if (window->Active)
- {
- // Hide previous tooltip from being displayed. We can't easily "reset" the content of a window so we create a new one.
- window->Hidden = true;
- window->HiddenFramesRegular = 1;
- ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", ++g.TooltipOverrideCount);
- }
- ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip|ImGuiWindowFlags_NoInputs|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoNav;
- Begin(window_name, NULL, flags | extra_flags);
-}
-
-void ImGui::SetTooltipV(const char* fmt, va_list args)
-{
- ImGuiContext& g = *GImGui;
- if (g.DragDropWithinSourceOrTarget)
- BeginTooltip();
- else
- BeginTooltipEx(0, true);
- TextV(fmt, args);
- EndTooltip();
-}
-
-void ImGui::SetTooltip(const char* fmt, ...)
-{
- va_list args;
- va_start(args, fmt);
- SetTooltipV(fmt, args);
- va_end(args);
-}
-
-void ImGui::BeginTooltip()
-{
- ImGuiContext& g = *GImGui;
- if (g.DragDropWithinSourceOrTarget)
- {
- // The default tooltip position is a little offset to give space to see the context menu (it's also clamped within the current viewport/monitor)
- // In the context of a dragging tooltip we try to reduce that offset and we enforce following the cursor.
- // Whatever we do we want to call SetNextWindowPos() to enforce a tooltip position and disable clipping the tooltip without our display area, like regular tooltip do.
- //ImVec2 tooltip_pos = g.IO.MousePos - g.ActiveIdClickOffset - g.Style.WindowPadding;
- ImVec2 tooltip_pos = g.IO.MousePos + ImVec2(16 * g.Style.MouseCursorScale, 8 * g.Style.MouseCursorScale);
- SetNextWindowPos(tooltip_pos);
- SetNextWindowBgAlpha(g.Style.Colors[ImGuiCol_PopupBg].w * 0.60f);
- //PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * 0.60f); // This would be nice but e.g ColorButton with checkboard has issue with transparent colors :(
- BeginTooltipEx(0, true);
- }
- else
- {
- BeginTooltipEx(0, false);
- }
-}
-
-void ImGui::EndTooltip()
-{
- IM_ASSERT(GetCurrentWindowRead()->Flags & ImGuiWindowFlags_Tooltip); // Mismatched BeginTooltip()/EndTooltip() calls
- End();
-}
-
-// Mark popup as open (toggle toward open state).
-// Popups are closed when user click outside, or activate a pressable item, or CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block.
-// Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level).
-// One open popup per level of the popup hierarchy (NB: when assigning we reset the Window member of ImGuiPopupRef to NULL)
-void ImGui::OpenPopupEx(ImGuiID id)
-{
- ImGuiContext& g = *GImGui;
- ImGuiWindow* parent_window = g.CurrentWindow;
- int current_stack_size = g.CurrentPopupStack.Size;
- ImGuiPopupRef popup_ref; // Tagged as new ref as Window will be set back to NULL if we write this into OpenPopupStack.
- popup_ref.PopupId = id;
- popup_ref.Window = NULL;
- popup_ref.ParentWindow = parent_window;
- popup_ref.OpenFrameCount = g.FrameCount;
- popup_ref.OpenParentId = parent_window->IDStack.back();
- popup_ref.OpenMousePos = g.IO.MousePos;
- popup_ref.OpenPopupPos = NavCalcPreferredRefPos();
-
- //printf("[%05d] OpenPopupEx(0x%08X)\n", g.FrameCount, id);
- if (g.OpenPopupStack.Size < current_stack_size + 1)
- {
- g.OpenPopupStack.push_back(popup_ref);
- }
- else
- {
- // Gently handle the user mistakenly calling OpenPopup() every frame. It is a programming mistake! However, if we were to run the regular code path, the ui
- // would become completely unusable because the popup will always be in hidden-while-calculating-size state _while_ claiming focus. Which would be a very confusing
- // situation for the programmer. Instead, we silently allow the popup to proceed, it will keep reappearing and the programming error will be more obvious to understand.
- if (g.OpenPopupStack[current_stack_size].PopupId == id && g.OpenPopupStack[current_stack_size].OpenFrameCount == g.FrameCount - 1)
- {
- g.OpenPopupStack[current_stack_size].OpenFrameCount = popup_ref.OpenFrameCount;
- }
- else
- {
- // Close child popups if any, then flag popup for open/reopen
- g.OpenPopupStack.resize(current_stack_size + 1);
- g.OpenPopupStack[current_stack_size] = popup_ref;
- }
-
- // When reopening a popup we first refocus its parent, otherwise if its parent is itself a popup it would get closed by ClosePopupsOverWindow().
- // This is equivalent to what ClosePopupToLevel() does.
- //if (g.OpenPopupStack[current_stack_size].PopupId == id)
- // FocusWindow(parent_window);
- }
-}
-
-void ImGui::OpenPopup(const char* str_id)
-{
- ImGuiContext& g = *GImGui;
- OpenPopupEx(g.CurrentWindow->GetID(str_id));
-}
-
-void ImGui::ClosePopupsOverWindow(ImGuiWindow* ref_window)
-{
- ImGuiContext& g = *GImGui;
- if (g.OpenPopupStack.empty())
- return;
-
- // When popups are stacked, clicking on a lower level popups puts focus back to it and close popups above it.
- // Don't close our own child popup windows.
- int n = 0;
- if (ref_window)
- {
- for (n = 0; n < g.OpenPopupStack.Size; n++)
- {
- ImGuiPopupRef& popup = g.OpenPopupStack[n];
- if (!popup.Window)
- continue;
- IM_ASSERT((popup.Window->Flags & ImGuiWindowFlags_Popup) != 0);
- if (popup.Window->Flags & ImGuiWindowFlags_ChildWindow)
- continue;
-
- // Trim the stack if popups are not direct descendant of the reference window (which is often the NavWindow)
- bool has_focus = false;
- for (int m = n; m < g.OpenPopupStack.Size && !has_focus; m++)
- has_focus = (g.OpenPopupStack[m].Window && g.OpenPopupStack[m].Window->RootWindow == ref_window->RootWindow);
- if (!has_focus)
- break;
- }
- }
- if (n < g.OpenPopupStack.Size) // This test is not required but it allows to set a convenient breakpoint on the block below
- ClosePopupToLevel(n);
-}
-
-ImGuiWindow* ImGui::GetFrontMostPopupModal()
-{
- ImGuiContext& g = *GImGui;
- for (int n = g.OpenPopupStack.Size-1; n >= 0; n--)
- if (ImGuiWindow* popup = g.OpenPopupStack.Data[n].Window)
- if (popup->Flags & ImGuiWindowFlags_Modal)
- return popup;
- return NULL;
-}
-
-void ImGui::ClosePopupToLevel(int remaining)
-{
- IM_ASSERT(remaining >= 0);
- ImGuiContext& g = *GImGui;
- ImGuiWindow* focus_window = (remaining > 0) ? g.OpenPopupStack[remaining-1].Window : g.OpenPopupStack[0].ParentWindow;
- if (g.NavLayer == 0)
- focus_window = NavRestoreLastChildNavWindow(focus_window);
- FocusWindow(focus_window);
- focus_window->DC.NavHideHighlightOneFrame = true;
- g.OpenPopupStack.resize(remaining);
-}
-
-void ImGui::ClosePopup(ImGuiID id)
-{
- if (!IsPopupOpen(id))
- return;
- ImGuiContext& g = *GImGui;
- ClosePopupToLevel(g.OpenPopupStack.Size - 1);
-}
-
-// Close the popup we have begin-ed into.
-void ImGui::CloseCurrentPopup()
-{
- ImGuiContext& g = *GImGui;
- int popup_idx = g.CurrentPopupStack.Size - 1;
- if (popup_idx < 0 || popup_idx >= g.OpenPopupStack.Size || g.CurrentPopupStack[popup_idx].PopupId != g.OpenPopupStack[popup_idx].PopupId)
- return;
- while (popup_idx > 0 && g.OpenPopupStack[popup_idx].Window && (g.OpenPopupStack[popup_idx].Window->Flags & ImGuiWindowFlags_ChildMenu))
- popup_idx--;
- ClosePopupToLevel(popup_idx);
-}
-
-bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags)
-{
- ImGuiContext& g = *GImGui;
- if (!IsPopupOpen(id))
- {
- g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values
- return false;
- }
-
- char name[20];
- if (extra_flags & ImGuiWindowFlags_ChildMenu)
- ImFormatString(name, IM_ARRAYSIZE(name), "##Menu_%02d", g.CurrentPopupStack.Size); // Recycle windows based on depth
- else
- ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x", id); // Not recycling, so we can close/open during the same frame
-
- bool is_open = Begin(name, NULL, extra_flags | ImGuiWindowFlags_Popup);
- if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display)
- EndPopup();
-
- return is_open;
-}
-
-bool ImGui::BeginPopup(const char* str_id, ImGuiWindowFlags flags)
-{
- ImGuiContext& g = *GImGui;
- if (g.OpenPopupStack.Size <= g.CurrentPopupStack.Size) // Early out for performance
- {
- g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values
- return false;
- }
- return BeginPopupEx(g.CurrentWindow->GetID(str_id), flags|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings);
-}
-
-bool ImGui::IsPopupOpen(ImGuiID id)
-{
- ImGuiContext& g = *GImGui;
- return g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].PopupId == id;
-}
-
-bool ImGui::IsPopupOpen(const char* str_id)
-{
- ImGuiContext& g = *GImGui;
- return g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].PopupId == g.CurrentWindow->GetID(str_id);
-}
-
-bool ImGui::BeginPopupModal(const char* name, bool* p_open, ImGuiWindowFlags flags)
-{
- ImGuiContext& g = *GImGui;
- ImGuiWindow* window = g.CurrentWindow;
- const ImGuiID id = window->GetID(name);
- if (!IsPopupOpen(id))
- {
- g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values
- return false;
- }
-
- // Center modal windows by default
- // FIXME: Should test for (PosCond & window->SetWindowPosAllowFlags) with the upcoming window.
- if (g.NextWindowData.PosCond == 0)
- SetNextWindowPos(g.IO.DisplaySize * 0.5f, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
-
- bool is_open = Begin(name, p_open, flags | ImGuiWindowFlags_Popup | ImGuiWindowFlags_Modal | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings);
- if (!is_open || (p_open && !*p_open)) // NB: is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
- {
- EndPopup();
- if (is_open)
- ClosePopup(id);
- return false;
- }
-
- return is_open;
-}
-
-void ImGui::NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, const ImRect& bb_rel, ImGuiNavMoveFlags move_flags)
-{
- ImGuiContext& g = *GImGui;
- IM_ASSERT(g.NavMoveRequestForward == ImGuiNavForward_None);
- ImGui::NavMoveRequestCancel();
- g.NavMoveDir = move_dir;
- g.NavMoveClipDir = clip_dir;
- g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued;
- g.NavMoveRequestFlags = move_flags;
- g.NavWindow->NavRectRel[g.NavLayer] = bb_rel;
-}
-
-void ImGui::NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags move_flags)
-{
- ImGuiContext& g = *GImGui;
- if (g.NavWindow != window || !NavMoveRequestButNoResultYet() || g.NavMoveRequestForward != ImGuiNavForward_None || g.NavLayer != 0)
- return;
- IM_ASSERT(move_flags != 0); // No points calling this with no wrapping
- ImRect bb_rel = window->NavRectRel[0];
-
- ImGuiDir clip_dir = g.NavMoveDir;
- if (g.NavMoveDir == ImGuiDir_Left && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX)))
- {
- bb_rel.Min.x = bb_rel.Max.x = ImMax(window->SizeFull.x, window->SizeContents.x) - window->Scroll.x;
- if (move_flags & ImGuiNavMoveFlags_WrapX) { bb_rel.TranslateY(-bb_rel.GetHeight()); clip_dir = ImGuiDir_Up; }
- NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags);
- }
- if (g.NavMoveDir == ImGuiDir_Right && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX)))
- {
- bb_rel.Min.x = bb_rel.Max.x = -window->Scroll.x;
- if (move_flags & ImGuiNavMoveFlags_WrapX) { bb_rel.TranslateY(+bb_rel.GetHeight()); clip_dir = ImGuiDir_Down; }
- NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags);
- }
- if (g.NavMoveDir == ImGuiDir_Up && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY)))
- {
- bb_rel.Min.y = bb_rel.Max.y = ImMax(window->SizeFull.y, window->SizeContents.y) - window->Scroll.y;
- if (move_flags & ImGuiNavMoveFlags_WrapY) { bb_rel.TranslateX(-bb_rel.GetWidth()); clip_dir = ImGuiDir_Left; }
- NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags);
- }
- if (g.NavMoveDir == ImGuiDir_Down && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY)))
- {
- bb_rel.Min.y = bb_rel.Max.y = -window->Scroll.y;
- if (move_flags & ImGuiNavMoveFlags_WrapY) { bb_rel.TranslateX(+bb_rel.GetWidth()); clip_dir = ImGuiDir_Right; }
- NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags);
- }
-}
-
-void ImGui::EndPopup()
-{
- ImGuiContext& g = *GImGui; (void)g;
- IM_ASSERT(g.CurrentWindow->Flags & ImGuiWindowFlags_Popup); // Mismatched BeginPopup()/EndPopup() calls
- IM_ASSERT(g.CurrentPopupStack.Size > 0);
-
- // Make all menus and popups wrap around for now, may need to expose that policy.
- NavMoveRequestTryWrapping(g.CurrentWindow, ImGuiNavMoveFlags_LoopY);
-
- End();
-}
-
-bool ImGui::OpenPopupOnItemClick(const char* str_id, int mouse_button)
-{
- ImGuiWindow* window = GImGui->CurrentWindow;
- if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))
- {
- ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict!
- IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item)
- OpenPopupEx(id);
- return true;
- }
- return false;
-}
-
-// This is a helper to handle the simplest case of associating one named popup to one given widget.
-// You may want to handle this on user side if you have specific needs (e.g. tweaking IsItemHovered() parameters).
-// You can pass a NULL str_id to use the identifier of the last item.
-bool ImGui::BeginPopupContextItem(const char* str_id, int mouse_button)
-{
- ImGuiWindow* window = GImGui->CurrentWindow;
- ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict!
- IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item)
- if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))
- OpenPopupEx(id);
- return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings);
-}
-
-bool ImGui::BeginPopupContextWindow(const char* str_id, int mouse_button, bool also_over_items)
-{
- if (!str_id)
- str_id = "window_context";
- ImGuiID id = GImGui->CurrentWindow->GetID(str_id);
- if (IsMouseReleased(mouse_button) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))
- if (also_over_items || !IsAnyItemHovered())
- OpenPopupEx(id);
- return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings);
-}
-
-bool ImGui::BeginPopupContextVoid(const char* str_id, int mouse_button)
-{
- if (!str_id)
- str_id = "void_context";
- ImGuiID id = GImGui->CurrentWindow->GetID(str_id);
- if (IsMouseReleased(mouse_button) && !IsWindowHovered(ImGuiHoveredFlags_AnyWindow))
- OpenPopupEx(id);
- return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings);
-}
-
static bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags flags)
{
ImGuiContext& g = *GImGui;
@@ -5670,7 +4031,6 @@ static bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size
SetActiveID(id+1, child_window); // Steal ActiveId with a dummy id so that key-press won't activate child item
g.ActiveIdSource = ImGuiInputSource_Nav;
}
-
return ret;
}
@@ -5761,112 +4121,6 @@ static void CheckStacksSize(ImGuiWindow* window, bool write)
IM_ASSERT(p_backup == window->DC.StackSizesBackup + IM_ARRAYSIZE(window->DC.StackSizesBackup));
}
-ImRect ImGui::GetWindowAllowedExtentRect(ImGuiWindow*)
-{
- ImVec2 padding = GImGui->Style.DisplaySafeAreaPadding;
- ImRect r_screen = GetViewportRect();
- r_screen.Expand(ImVec2((r_screen.GetWidth() > padding.x * 2) ? -padding.x : 0.0f, (r_screen.GetHeight() > padding.y * 2) ? -padding.y : 0.0f));
- return r_screen;
-}
-
-// r_avoid = the rectangle to avoid (e.g. for tooltip it is a rectangle around the mouse cursor which we want to avoid. for popups it's a small point around the cursor.)
-// r_outer = the visible area rectangle, minus safe area padding. If our popup size won't fit because of safe area padding we ignore it.
-ImVec2 ImGui::FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_outer, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy)
-{
- ImVec2 base_pos_clamped = ImClamp(ref_pos, r_outer.Min, r_outer.Max - size);
- //GImGui->OverlayDrawList.AddRect(r_avoid.Min, r_avoid.Max, IM_COL32(255,0,0,255));
- //GImGui->OverlayDrawList.AddRect(r_outer.Min, r_outer.Max, IM_COL32(0,255,0,255));
-
- // Combo Box policy (we want a connecting edge)
- if (policy == ImGuiPopupPositionPolicy_ComboBox)
- {
- const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = { ImGuiDir_Down, ImGuiDir_Right, ImGuiDir_Left, ImGuiDir_Up };
- for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++)
- {
- const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n];
- if (n != -1 && dir == *last_dir) // Already tried this direction?
- continue;
- ImVec2 pos;
- if (dir == ImGuiDir_Down) pos = ImVec2(r_avoid.Min.x, r_avoid.Max.y); // Below, Toward Right (default)
- if (dir == ImGuiDir_Right) pos = ImVec2(r_avoid.Min.x, r_avoid.Min.y - size.y); // Above, Toward Right
- if (dir == ImGuiDir_Left) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Max.y); // Below, Toward Left
- if (dir == ImGuiDir_Up) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Min.y - size.y); // Above, Toward Left
- if (!r_outer.Contains(ImRect(pos, pos + size)))
- continue;
- *last_dir = dir;
- return pos;
- }
- }
-
- // Default popup policy
- const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = { ImGuiDir_Right, ImGuiDir_Down, ImGuiDir_Up, ImGuiDir_Left };
- for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++)
- {
- const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n];
- if (n != -1 && dir == *last_dir) // Already tried this direction?
- continue;
- float avail_w = (dir == ImGuiDir_Left ? r_avoid.Min.x : r_outer.Max.x) - (dir == ImGuiDir_Right ? r_avoid.Max.x : r_outer.Min.x);
- float avail_h = (dir == ImGuiDir_Up ? r_avoid.Min.y : r_outer.Max.y) - (dir == ImGuiDir_Down ? r_avoid.Max.y : r_outer.Min.y);
- if (avail_w < size.x || avail_h < size.y)
- continue;
- ImVec2 pos;
- pos.x = (dir == ImGuiDir_Left) ? r_avoid.Min.x - size.x : (dir == ImGuiDir_Right) ? r_avoid.Max.x : base_pos_clamped.x;
- pos.y = (dir == ImGuiDir_Up) ? r_avoid.Min.y - size.y : (dir == ImGuiDir_Down) ? r_avoid.Max.y : base_pos_clamped.y;
- *last_dir = dir;
- return pos;
- }
-
- // Fallback, try to keep within display
- *last_dir = ImGuiDir_None;
- ImVec2 pos = ref_pos;
- pos.x = ImMax(ImMin(pos.x + size.x, r_outer.Max.x) - size.x, r_outer.Min.x);
- pos.y = ImMax(ImMin(pos.y + size.y, r_outer.Max.y) - size.y, r_outer.Min.y);
- return pos;
-}
-
-ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window)
-{
- ImGuiContext& g = *GImGui;
-
- ImRect r_outer = GetWindowAllowedExtentRect(window);
- if (window->Flags & ImGuiWindowFlags_ChildMenu)
- {
- // Child menus typically request _any_ position within the parent menu item, and then our FindBestWindowPosForPopup() function will move the new menu outside the parent bounds.
- // This is how we end up with child menus appearing (most-commonly) on the right of the parent menu.
- IM_ASSERT(g.CurrentWindow == window);
- ImGuiWindow* parent_window = g.CurrentWindowStack[g.CurrentWindowStack.Size - 2];
- float horizontal_overlap = g.Style.ItemSpacing.x; // We want some overlap to convey the relative depth of each menu (currently the amount of overlap is hard-coded to style.ItemSpacing.x).
- ImRect r_avoid;
- if (parent_window->DC.MenuBarAppending)
- r_avoid = ImRect(-FLT_MAX, parent_window->Pos.y + parent_window->TitleBarHeight(), FLT_MAX, parent_window->Pos.y + parent_window->TitleBarHeight() + parent_window->MenuBarHeight());
- else
- r_avoid = ImRect(parent_window->Pos.x + horizontal_overlap, -FLT_MAX, parent_window->Pos.x + parent_window->Size.x - horizontal_overlap - parent_window->ScrollbarSizes.x, FLT_MAX);
- return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid);
- }
- if (window->Flags & ImGuiWindowFlags_Popup)
- {
- ImRect r_avoid = ImRect(window->Pos.x - 1, window->Pos.y - 1, window->Pos.x + 1, window->Pos.y + 1);
- return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid);
- }
- if (window->Flags & ImGuiWindowFlags_Tooltip)
- {
- // Position tooltip (always follows mouse)
- float sc = g.Style.MouseCursorScale;
- ImVec2 ref_pos = NavCalcPreferredRefPos();
- ImRect r_avoid;
- if (!g.NavDisableHighlight && g.NavDisableMouseHover && !(g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos))
- r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 16, ref_pos.y + 8);
- else
- r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 24 * sc, ref_pos.y + 24 * sc); // FIXME: Hard-coded based on mouse cursor shape expectation. Exact dimension not very important.
- ImVec2 pos = FindBestWindowPosForPopupEx(ref_pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid);
- if (window->AutoPosLastDirection == ImGuiDir_None)
- pos = ref_pos + ImVec2(2, 2); // If there's not enough room, for tooltip we prefer avoiding the cursor at all cost even if it means that part of the tooltip won't be visible.
- return pos;
- }
- IM_ASSERT(0);
- return window->Pos;
-}
-
static void SetWindowConditionAllowFlags(ImGuiWindow* window, ImGuiCond flags, bool enabled)
{
window->SetWindowPosAllowFlags = enabled ? (window->SetWindowPosAllowFlags | flags) : (window->SetWindowPosAllowFlags & ~flags);
@@ -6843,116 +5097,6 @@ void ImGui::End()
SetCurrentWindow(g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back());
}
-// Vertical scrollbar
-// The entire piece of code below is rather confusing because:
-// - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab)
-// - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar
-// - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal.
-void ImGui::Scrollbar(ImGuiLayoutType direction)
-{
- ImGuiContext& g = *GImGui;
- ImGuiWindow* window = g.CurrentWindow;
-
- const bool horizontal = (direction == ImGuiLayoutType_Horizontal);
- const ImGuiStyle& style = g.Style;
- const ImGuiID id = window->GetID(horizontal ? "#SCROLLX" : "#SCROLLY");
-
- // Render background
- bool other_scrollbar = (horizontal ? window->ScrollbarY : window->ScrollbarX);
- float other_scrollbar_size_w = other_scrollbar ? style.ScrollbarSize : 0.0f;
- const ImRect window_rect = window->Rect();
- const float border_size = window->WindowBorderSize;
- ImRect bb = horizontal
- ? ImRect(window->Pos.x + border_size, window_rect.Max.y - style.ScrollbarSize, window_rect.Max.x - other_scrollbar_size_w - border_size, window_rect.Max.y - border_size)
- : ImRect(window_rect.Max.x - style.ScrollbarSize, window->Pos.y + border_size, window_rect.Max.x - border_size, window_rect.Max.y - other_scrollbar_size_w - border_size);
- if (!horizontal)
- bb.Min.y += window->TitleBarHeight() + ((window->Flags & ImGuiWindowFlags_MenuBar) ? window->MenuBarHeight() : 0.0f);
- if (bb.GetWidth() <= 0.0f || bb.GetHeight() <= 0.0f)
- return;
-
- int window_rounding_corners;
- if (horizontal)
- window_rounding_corners = ImDrawCornerFlags_BotLeft | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight);
- else
- window_rounding_corners = (((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) ? ImDrawCornerFlags_TopRight : 0) | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight);
- window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_ScrollbarBg), window->WindowRounding, window_rounding_corners);
- bb.Expand(ImVec2(-ImClamp((float)(int)((bb.Max.x - bb.Min.x - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp((float)(int)((bb.Max.y - bb.Min.y - 2.0f) * 0.5f), 0.0f, 3.0f)));
-
- // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar)
- float scrollbar_size_v = horizontal ? bb.GetWidth() : bb.GetHeight();
- float scroll_v = horizontal ? window->Scroll.x : window->Scroll.y;
- float win_size_avail_v = (horizontal ? window->SizeFull.x : window->SizeFull.y) - other_scrollbar_size_w;
- float win_size_contents_v = horizontal ? window->SizeContents.x : window->SizeContents.y;
-
- // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount)
- // But we maintain a minimum size in pixel to allow for the user to still aim inside.
- IM_ASSERT(ImMax(win_size_contents_v, win_size_avail_v) > 0.0f); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers.
- const float win_size_v = ImMax(ImMax(win_size_contents_v, win_size_avail_v), 1.0f);
- const float grab_h_pixels = ImClamp(scrollbar_size_v * (win_size_avail_v / win_size_v), style.GrabMinSize, scrollbar_size_v);
- const float grab_h_norm = grab_h_pixels / scrollbar_size_v;
-
- // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar().
- bool held = false;
- bool hovered = false;
- const bool previously_held = (g.ActiveId == id);
- ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus);
-
- float scroll_max = ImMax(1.0f, win_size_contents_v - win_size_avail_v);
- float scroll_ratio = ImSaturate(scroll_v / scroll_max);
- float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
- if (held && grab_h_norm < 1.0f)
- {
- float scrollbar_pos_v = horizontal ? bb.Min.x : bb.Min.y;
- float mouse_pos_v = horizontal ? g.IO.MousePos.x : g.IO.MousePos.y;
- float* click_delta_to_grab_center_v = horizontal ? &g.ScrollbarClickDeltaToGrabCenter.x : &g.ScrollbarClickDeltaToGrabCenter.y;
-
- // Click position in scrollbar normalized space (0.0f->1.0f)
- const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v);
- SetHoveredID(id);
-
- bool seek_absolute = false;
- if (!previously_held)
- {
- // On initial click calculate the distance between mouse and the center of the grab
- if (clicked_v_norm >= grab_v_norm && clicked_v_norm <= grab_v_norm + grab_h_norm)
- {
- *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f;
- }
- else
- {
- seek_absolute = true;
- *click_delta_to_grab_center_v = 0.0f;
- }
- }
-
- // Apply scroll
- // It is ok to modify Scroll here because we are being called in Begin() after the calculation of SizeContents and before setting up our starting position
- const float scroll_v_norm = ImSaturate((clicked_v_norm - *click_delta_to_grab_center_v - grab_h_norm*0.5f) / (1.0f - grab_h_norm));
- scroll_v = (float)(int)(0.5f + scroll_v_norm * scroll_max);//(win_size_contents_v - win_size_v));
- if (horizontal)
- window->Scroll.x = scroll_v;
- else
- window->Scroll.y = scroll_v;
-
- // Update values for rendering
- scroll_ratio = ImSaturate(scroll_v / scroll_max);
- grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
-
- // Update distance to grab now that we have seeked and saturated
- if (seek_absolute)
- *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f;
- }
-
- // Render
- const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab);
- ImRect grab_rect;
- if (horizontal)
- grab_rect = ImRect(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y, ImMin(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, window_rect.Max.x), bb.Max.y);
- else
- grab_rect = ImRect(bb.Min.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm), bb.Max.x, ImMin(ImLerp(bb.Min.y, bb.Max.y, grab_v_norm) + grab_h_pixels, window_rect.Max.y));
- window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding);
-}
-
void ImGui::BringWindowToFront(ImGuiWindow* window)
{
ImGuiContext& g = *GImGui;
@@ -7871,5443 +6015,1778 @@ ImGuiStorage* ImGui::GetStateStorage()
return window->DC.StateStorage;
}
-void ImGui::TextV(const char* fmt, va_list args)
+void ImGui::AlignTextToFramePadding()
{
ImGuiWindow* window = GetCurrentWindow();
if (window->SkipItems)
return;
ImGuiContext& g = *GImGui;
- const char* text_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
- TextUnformatted(g.TempBuffer, text_end);
+ window->DC.CurrentLineSize.y = ImMax(window->DC.CurrentLineSize.y, g.FontSize + g.Style.FramePadding.y * 2);
+ window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.CurrentLineTextBaseOffset, g.Style.FramePadding.y);
}
-void ImGui::Text(const char* fmt, ...)
+void ImGui::PushID(const char* str_id)
{
- va_list args;
- va_start(args, fmt);
- TextV(fmt, args);
- va_end(args);
+ ImGuiWindow* window = GetCurrentWindowRead();
+ window->IDStack.push_back(window->GetIDNoKeepAlive(str_id));
}
-void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args)
+void ImGui::PushID(const char* str_id_begin, const char* str_id_end)
{
- PushStyleColor(ImGuiCol_Text, col);
- TextV(fmt, args);
- PopStyleColor();
+ ImGuiWindow* window = GetCurrentWindowRead();
+ window->IDStack.push_back(window->GetIDNoKeepAlive(str_id_begin, str_id_end));
}
-void ImGui::TextColored(const ImVec4& col, const char* fmt, ...)
+void ImGui::PushID(const void* ptr_id)
{
- va_list args;
- va_start(args, fmt);
- TextColoredV(col, fmt, args);
- va_end(args);
+ ImGuiWindow* window = GetCurrentWindowRead();
+ window->IDStack.push_back(window->GetIDNoKeepAlive(ptr_id));
}
-void ImGui::TextDisabledV(const char* fmt, va_list args)
+void ImGui::PushID(int int_id)
{
- PushStyleColor(ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled]);
- TextV(fmt, args);
- PopStyleColor();
+ const void* ptr_id = (void*)(intptr_t)int_id;
+ ImGuiWindow* window = GetCurrentWindowRead();
+ window->IDStack.push_back(window->GetIDNoKeepAlive(ptr_id));
}
-void ImGui::TextDisabled(const char* fmt, ...)
+void ImGui::PopID()
{
- va_list args;
- va_start(args, fmt);
- TextDisabledV(fmt, args);
- va_end(args);
+ ImGuiWindow* window = GetCurrentWindowRead();
+ window->IDStack.pop_back();
}
-void ImGui::TextWrappedV(const char* fmt, va_list args)
+ImGuiID ImGui::GetID(const char* str_id)
{
- bool need_wrap = (GImGui->CurrentWindow->DC.TextWrapPos < 0.0f); // Keep existing wrap position is one ia already set
- if (need_wrap) PushTextWrapPos(0.0f);
- TextV(fmt, args);
- if (need_wrap) PopTextWrapPos();
+ return GImGui->CurrentWindow->GetID(str_id);
}
-void ImGui::TextWrapped(const char* fmt, ...)
+ImGuiID ImGui::GetID(const char* str_id_begin, const char* str_id_end)
{
- va_list args;
- va_start(args, fmt);
- TextWrappedV(fmt, args);
- va_end(args);
+ return GImGui->CurrentWindow->GetID(str_id_begin, str_id_end);
+}
+
+ImGuiID ImGui::GetID(const void* ptr_id)
+{
+ return GImGui->CurrentWindow->GetID(ptr_id);
}
-void ImGui::TextUnformatted(const char* text, const char* text_end)
+// Horizontal/vertical separating line
+void ImGui::Separator()
{
ImGuiWindow* window = GetCurrentWindow();
if (window->SkipItems)
return;
-
ImGuiContext& g = *GImGui;
- IM_ASSERT(text != NULL);
- const char* text_begin = text;
- if (text_end == NULL)
- text_end = text + strlen(text); // FIXME-OPT
- const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrentLineTextBaseOffset);
- const float wrap_pos_x = window->DC.TextWrapPos;
- const bool wrap_enabled = wrap_pos_x >= 0.0f;
- if (text_end - text > 2000 && !wrap_enabled)
- {
- // Long text!
- // Perform manual coarse clipping to optimize for long multi-line text
- // From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled.
- // We also don't vertically center the text within the line full height, which is unlikely to matter because we are likely the biggest and only item on the line.
- const char* line = text;
- const float line_height = GetTextLineHeight();
- const ImRect clip_rect = window->ClipRect;
- ImVec2 text_size(0,0);
-
- if (text_pos.y <= clip_rect.Max.y)
- {
- ImVec2 pos = text_pos;
+ // Those flags should eventually be overridable by the user
+ ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal;
+ IM_ASSERT(ImIsPowerOfTwo((int)(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)))); // Check that only 1 option is selected
+ if (flags & ImGuiSeparatorFlags_Vertical)
+ {
+ VerticalSeparator();
+ return;
+ }
- // Lines to skip (can't skip when logging text)
- if (!g.LogEnabled)
- {
- int lines_skippable = (int)((clip_rect.Min.y - text_pos.y) / line_height);
- if (lines_skippable > 0)
- {
- int lines_skipped = 0;
- while (line < text_end && lines_skipped < lines_skippable)
- {
- const char* line_end = strchr(line, '\n');
- if (!line_end)
- line_end = text_end;
- line = line_end + 1;
- lines_skipped++;
- }
- pos.y += lines_skipped * line_height;
- }
- }
+ // Horizontal Separator
+ if (window->DC.ColumnsSet)
+ PopClipRect();
- // Lines to render
- if (line < text_end)
- {
- ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height));
- while (line < text_end)
- {
- const char* line_end = strchr(line, '\n');
- if (IsClippedEx(line_rect, 0, false))
- break;
+ float x1 = window->Pos.x;
+ float x2 = window->Pos.x + window->Size.x;
+ if (!window->DC.GroupStack.empty())
+ x1 += window->DC.Indent.x;
- const ImVec2 line_size = CalcTextSize(line, line_end, false);
- text_size.x = ImMax(text_size.x, line_size.x);
- RenderText(pos, line, line_end, false);
- if (!line_end)
- line_end = text_end;
- line = line_end + 1;
- line_rect.Min.y += line_height;
- line_rect.Max.y += line_height;
- pos.y += line_height;
- }
+ const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y+1.0f));
+ ItemSize(ImVec2(0.0f, 0.0f)); // NB: we don't provide our width so that it doesn't get feed back into AutoFit, we don't provide height to not alter layout.
+ if (!ItemAdd(bb, 0))
+ {
+ if (window->DC.ColumnsSet)
+ PushColumnClipRect();
+ return;
+ }
- // Count remaining lines
- int lines_skipped = 0;
- while (line < text_end)
- {
- const char* line_end = strchr(line, '\n');
- if (!line_end)
- line_end = text_end;
- line = line_end + 1;
- lines_skipped++;
- }
- pos.y += lines_skipped * line_height;
- }
+ window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x,bb.Min.y), GetColorU32(ImGuiCol_Separator));
- text_size.y += (pos - text_pos).y;
- }
+ if (g.LogEnabled)
+ LogRenderedText(NULL, IM_NEWLINE "--------------------------------");
- ImRect bb(text_pos, text_pos + text_size);
- ItemSize(bb);
- ItemAdd(bb, 0);
- }
- else
+ if (window->DC.ColumnsSet)
{
- const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f;
- const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width);
-
- // Account of baseline offset
- ImRect bb(text_pos, text_pos + text_size);
- ItemSize(text_size);
- if (!ItemAdd(bb, 0))
- return;
-
- // Render (we don't hide text after ## in this end-user function)
- RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width);
+ PushColumnClipRect();
+ window->DC.ColumnsSet->LineMinY = window->DC.CursorPos.y;
}
}
-void ImGui::AlignTextToFramePadding()
-{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return;
-
- ImGuiContext& g = *GImGui;
- window->DC.CurrentLineSize.y = ImMax(window->DC.CurrentLineSize.y, g.FontSize + g.Style.FramePadding.y * 2);
- window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.CurrentLineTextBaseOffset, g.Style.FramePadding.y);
-}
-
-// Add a label+text combo aligned to other label+value widgets
-void ImGui::LabelTextV(const char* label, const char* fmt, va_list args)
+void ImGui::VerticalSeparator()
{
ImGuiWindow* window = GetCurrentWindow();
if (window->SkipItems)
return;
-
ImGuiContext& g = *GImGui;
- const ImGuiStyle& style = g.Style;
- const float w = CalcItemWidth();
- const ImVec2 label_size = CalcTextSize(label, NULL, true);
- const ImRect value_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2));
- const ImRect total_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w + (label_size.x > 0.0f ? style.ItemInnerSpacing.x : 0.0f), style.FramePadding.y*2) + label_size);
- ItemSize(total_bb, style.FramePadding.y);
- if (!ItemAdd(total_bb, 0))
+ float y1 = window->DC.CursorPos.y;
+ float y2 = window->DC.CursorPos.y + window->DC.CurrentLineSize.y;
+ const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + 1.0f, y2));
+ ItemSize(ImVec2(bb.GetWidth(), 0.0f));
+ if (!ItemAdd(bb, 0))
return;
- // Render
- const char* value_text_begin = &g.TempBuffer[0];
- const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
- RenderTextClipped(value_bb.Min, value_bb.Max, value_text_begin, value_text_end, NULL, ImVec2(0.0f,0.5f));
- if (label_size.x > 0.0f)
- RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label);
-}
-
-void ImGui::LabelText(const char* label, const char* fmt, ...)
-{
- va_list args;
- va_start(args, fmt);
- LabelTextV(label, fmt, args);
- va_end(args);
+ window->DrawList->AddLine(ImVec2(bb.Min.x, bb.Min.y), ImVec2(bb.Min.x, bb.Max.y), GetColorU32(ImGuiCol_Separator));
+ if (g.LogEnabled)
+ LogText(" |");
}
-bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags)
+// Using 'hover_visibility_delay' allows us to hide the highlight and mouse cursor for a short time, which can be convenient to reduce visual noise.
+bool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend, float hover_visibility_delay)
{
ImGuiContext& g = *GImGui;
- ImGuiWindow* window = GetCurrentWindow();
+ ImGuiWindow* window = g.CurrentWindow;
- if (flags & ImGuiButtonFlags_Disabled)
- {
- if (out_hovered) *out_hovered = false;
- if (out_held) *out_held = false;
- if (g.ActiveId == id) ClearActiveID();
+ const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags;
+ window->DC.ItemFlags |= ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus;
+ bool item_add = ItemAdd(bb, id);
+ window->DC.ItemFlags = item_flags_backup;
+ if (!item_add)
return false;
- }
- // Default behavior requires click+release on same spot
- if ((flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick)) == 0)
- flags |= ImGuiButtonFlags_PressedOnClickRelease;
-
- ImGuiWindow* backup_hovered_window = g.HoveredWindow;
- if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window)
- g.HoveredWindow = window;
-
- bool pressed = false;
- bool hovered = ItemHoverable(bb, id);
-
- // Drag source doesn't report as hovered
- if (hovered && g.DragDropActive && g.DragDropPayload.SourceId == id && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoDisableHover))
- hovered = false;
-
- // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button
- if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers))
- if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
- {
- hovered = true;
- SetHoveredID(id);
- if (CalcTypematicPressedRepeatAmount(g.HoveredIdTimer + 0.0001f, g.HoveredIdTimer + 0.0001f - g.IO.DeltaTime, 0.01f, 0.70f)) // FIXME: Our formula for CalcTypematicPressedRepeatAmount() is fishy
- {
- pressed = true;
- FocusWindow(window);
- }
- }
-
- if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window)
- g.HoveredWindow = backup_hovered_window;
+ bool hovered, held;
+ ImRect bb_interact = bb;
+ bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f));
+ ButtonBehavior(bb_interact, id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap);
+ if (g.ActiveId != id)
+ SetItemAllowOverlap();
- // AllowOverlap mode (rarely used) requires previous frame HoveredId to be null or to match. This allows using patterns where a later submitted widget overlaps a previous one.
- if (hovered && (flags & ImGuiButtonFlags_AllowItemOverlap) && (g.HoveredIdPreviousFrame != id && g.HoveredIdPreviousFrame != 0))
- hovered = false;
+ if (held || (g.HoveredId == id && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay))
+ SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW);
- // Mouse
- if (hovered)
+ ImRect bb_render = bb;
+ if (held)
{
- if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt))
- {
- // | CLICKING | HOLDING with ImGuiButtonFlags_Repeat
- // PressedOnClickRelease | <on release>* | <on repeat> <on repeat> .. (NOT on release) <-- MOST COMMON! (*) only if both click/release were over bounds
- // PressedOnClick | <on click> | <on click> <on repeat> <on repeat> ..
- // PressedOnRelease | <on release> | <on repeat> <on repeat> .. (NOT on release)
- // PressedOnDoubleClick | <on dclick> | <on dclick> <on repeat> <on repeat> ..
- // FIXME-NAV: We don't honor those different behaviors.
- if ((flags & ImGuiButtonFlags_PressedOnClickRelease) && g.IO.MouseClicked[0])
- {
- SetActiveID(id, window);
- if (!(flags & ImGuiButtonFlags_NoNavFocus))
- SetFocusID(id, window);
- FocusWindow(window);
- }
- if (((flags & ImGuiButtonFlags_PressedOnClick) && g.IO.MouseClicked[0]) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDoubleClicked[0]))
- {
- pressed = true;
- if (flags & ImGuiButtonFlags_NoHoldingActiveID)
- ClearActiveID();
- else
- SetActiveID(id, window); // Hold on ID
- FocusWindow(window);
- }
- if ((flags & ImGuiButtonFlags_PressedOnRelease) && g.IO.MouseReleased[0])
- {
- if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps <on release>
- pressed = true;
- ClearActiveID();
- }
-
- // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above).
- // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings.
- if ((flags & ImGuiButtonFlags_Repeat) && g.ActiveId == id && g.IO.MouseDownDuration[0] > 0.0f && IsMouseClicked(0, true))
- pressed = true;
- }
-
- if (pressed)
- g.NavDisableHighlight = true;
- }
-
- // Gamepad/Keyboard navigation
- // We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse.
- if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover && (g.ActiveId == 0 || g.ActiveId == id || g.ActiveId == window->MoveId))
- hovered = true;
+ ImVec2 mouse_delta_2d = g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min;
+ float mouse_delta = (axis == ImGuiAxis_Y) ? mouse_delta_2d.y : mouse_delta_2d.x;
- if (g.NavActivateDownId == id)
- {
- bool nav_activated_by_code = (g.NavActivateId == id);
- bool nav_activated_by_inputs = IsNavInputPressed(ImGuiNavInput_Activate, (flags & ImGuiButtonFlags_Repeat) ? ImGuiInputReadMode_Repeat : ImGuiInputReadMode_Pressed);
- if (nav_activated_by_code || nav_activated_by_inputs)
- pressed = true;
- if (nav_activated_by_code || nav_activated_by_inputs || g.ActiveId == id)
- {
- // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.
- g.NavActivateId = id; // This is so SetActiveId assign a Nav source
- SetActiveID(id, window);
- if (!(flags & ImGuiButtonFlags_NoNavFocus))
- SetFocusID(id, window);
- g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right) | (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
- }
- }
+ // Minimum pane size
+ float size_1_maximum_delta = ImMax(0.0f, *size1 - min_size1);
+ float size_2_maximum_delta = ImMax(0.0f, *size2 - min_size2);
+ if (mouse_delta < -size_1_maximum_delta)
+ mouse_delta = -size_1_maximum_delta;
+ if (mouse_delta > size_2_maximum_delta)
+ mouse_delta = size_2_maximum_delta;
- bool held = false;
- if (g.ActiveId == id)
- {
- if (g.ActiveIdSource == ImGuiInputSource_Mouse)
- {
- if (g.ActiveIdIsJustActivated)
- g.ActiveIdClickOffset = g.IO.MousePos - bb.Min;
- if (g.IO.MouseDown[0])
- {
- held = true;
- }
- else
- {
- if (hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease))
- if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps <on release>
- if (!g.DragDropActive)
- pressed = true;
- ClearActiveID();
- }
- if (!(flags & ImGuiButtonFlags_NoNavFocus))
- g.NavDisableHighlight = true;
- }
- else if (g.ActiveIdSource == ImGuiInputSource_Nav)
+ // Apply resize
+ if (mouse_delta != 0.0f)
{
- if (g.NavActivateDownId != id)
- ClearActiveID();
+ if (mouse_delta < 0.0f)
+ IM_ASSERT(*size1 + mouse_delta >= min_size1);
+ if (mouse_delta > 0.0f)
+ IM_ASSERT(*size2 - mouse_delta >= min_size2);
+ *size1 += mouse_delta;
+ *size2 -= mouse_delta;
+ bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta));
+ MarkItemEdited(id);
}
}
- if (out_hovered) *out_hovered = hovered;
- if (out_held) *out_held = held;
+ // Render
+ const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
+ window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, g.Style.FrameRounding);
- return pressed;
+ return held;
}
-bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags)
+void ImGui::Spacing()
{
ImGuiWindow* window = GetCurrentWindow();
if (window->SkipItems)
- return false;
-
- ImGuiContext& g = *GImGui;
- const ImGuiStyle& style = g.Style;
- const ImGuiID id = window->GetID(label);
- const ImVec2 label_size = CalcTextSize(label, NULL, true);
-
- ImVec2 pos = window->DC.CursorPos;
- if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrentLineTextBaseOffset) // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag)
- pos.y += window->DC.CurrentLineTextBaseOffset - style.FramePadding.y;
- ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f);
-
- const ImRect bb(pos, pos + size);
- ItemSize(bb, style.FramePadding.y);
- if (!ItemAdd(bb, id))
- return false;
-
- if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat)
- flags |= ImGuiButtonFlags_Repeat;
- bool hovered, held;
- bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
- if (pressed)
- MarkItemEdited(id);
-
- // Render
- const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
- RenderNavHighlight(bb, id);
- RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
- RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb);
-
- // Automatically close popups
- //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
- // CloseCurrentPopup();
-
- return pressed;
-}
-
-bool ImGui::Button(const char* label, const ImVec2& size_arg)
-{
- return ButtonEx(label, size_arg, 0);
-}
-
-// Small buttons fits within text without additional vertical spacing.
-bool ImGui::SmallButton(const char* label)
-{
- ImGuiContext& g = *GImGui;
- float backup_padding_y = g.Style.FramePadding.y;
- g.Style.FramePadding.y = 0.0f;
- bool pressed = ButtonEx(label, ImVec2(0,0), ImGuiButtonFlags_AlignTextBaseLine);
- g.Style.FramePadding.y = backup_padding_y;
- return pressed;
+ return;
+ ItemSize(ImVec2(0,0));
}
-bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags)
+void ImGui::Dummy(const ImVec2& size)
{
ImGuiWindow* window = GetCurrentWindow();
if (window->SkipItems)
- return false;
+ return;
- ImGuiContext& g = *GImGui;
- const ImGuiID id = window->GetID(str_id);
const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
- const float default_size = GetFrameHeight();
- ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
- if (!ItemAdd(bb, id))
- return false;
-
- if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat)
- flags |= ImGuiButtonFlags_Repeat;
-
- bool hovered, held;
- bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
-
- // Render
- const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
- RenderNavHighlight(bb, id);
- RenderFrame(bb.Min, bb.Max, col, true, g.Style.FrameRounding);
- RenderArrow(bb.Min + ImVec2(ImMax(0.0f, size.x - g.FontSize - g.Style.FramePadding.x), ImMax(0.0f, size.y - g.FontSize - g.Style.FramePadding.y)), dir);
+ ItemSize(bb);
+ ItemAdd(bb, 0);
+}
- return pressed;
+bool ImGui::IsRectVisible(const ImVec2& size)
+{
+ ImGuiWindow* window = GetCurrentWindowRead();
+ return window->ClipRect.Overlaps(ImRect(window->DC.CursorPos, window->DC.CursorPos + size));
}
-bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir)
+bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max)
{
- float sz = GetFrameHeight();
- return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), 0);
+ ImGuiWindow* window = GetCurrentWindowRead();
+ return window->ClipRect.Overlaps(ImRect(rect_min, rect_max));
}
-// Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack.
-// Then you can keep 'str_id' empty or the same for all your buttons (instead of creating a string based on a non-string id)
-bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg)
+// Lock horizontal starting position + capture group bounding box into one "item" (so you can use IsItemHovered() or layout primitives such as SameLine() on whole group, etc.)
+void ImGui::BeginGroup()
{
+ ImGuiContext& g = *GImGui;
ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
-
- // Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size.
- IM_ASSERT(size_arg.x != 0.0f && size_arg.y != 0.0f);
-
- const ImGuiID id = window->GetID(str_id);
- ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f);
- const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
- ItemSize(bb);
- if (!ItemAdd(bb, id))
- return false;
- bool hovered, held;
- bool pressed = ButtonBehavior(bb, id, &hovered, &held);
+ window->DC.GroupStack.resize(window->DC.GroupStack.Size + 1);
+ ImGuiGroupData& group_data = window->DC.GroupStack.back();
+ group_data.BackupCursorPos = window->DC.CursorPos;
+ group_data.BackupCursorMaxPos = window->DC.CursorMaxPos;
+ group_data.BackupIndent = window->DC.Indent;
+ group_data.BackupGroupOffset = window->DC.GroupOffset;
+ group_data.BackupCurrentLineSize = window->DC.CurrentLineSize;
+ group_data.BackupCurrentLineTextBaseOffset = window->DC.CurrentLineTextBaseOffset;
+ group_data.BackupLogLinePosY = window->DC.LogLinePosY;
+ group_data.BackupActiveIdIsAlive = g.ActiveIdIsAlive;
+ group_data.BackupActiveIdPreviousFrameIsAlive = g.ActiveIdPreviousFrameIsAlive;
+ group_data.AdvanceCursor = true;
- return pressed;
+ window->DC.GroupOffset.x = window->DC.CursorPos.x - window->Pos.x - window->DC.ColumnsOffset.x;
+ window->DC.Indent = window->DC.GroupOffset;
+ window->DC.CursorMaxPos = window->DC.CursorPos;
+ window->DC.CurrentLineSize = ImVec2(0.0f, 0.0f);
+ window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; // To enforce Log carriage return
}
-// Button to close a window
-bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos, float radius)
+void ImGui::EndGroup()
{
ImGuiContext& g = *GImGui;
- ImGuiWindow* window = g.CurrentWindow;
-
- // We intentionally allow interaction when clipped so that a mechanical Alt,Right,Validate sequence close a window.
- // (this isn't the regular behavior of buttons, but it doesn't affect the user much because navigation tends to keep items visible).
- const ImRect bb(pos - ImVec2(radius,radius), pos + ImVec2(radius,radius));
- bool is_clipped = !ItemAdd(bb, id);
-
- bool hovered, held;
- bool pressed = ButtonBehavior(bb, id, &hovered, &held);
- if (is_clipped)
- return pressed;
-
- // Render
- ImVec2 center = bb.GetCenter();
- if (hovered)
- window->DrawList->AddCircleFilled(center, ImMax(2.0f, radius), GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered), 9);
+ ImGuiWindow* window = GetCurrentWindow();
+ IM_ASSERT(!window->DC.GroupStack.empty()); // Mismatched BeginGroup()/EndGroup() calls
- float cross_extent = (radius * 0.7071f) - 1.0f;
- ImU32 cross_col = GetColorU32(ImGuiCol_Text);
- center -= ImVec2(0.5f, 0.5f);
- window->DrawList->AddLine(center + ImVec2(+cross_extent,+cross_extent), center + ImVec2(-cross_extent,-cross_extent), cross_col, 1.0f);
- window->DrawList->AddLine(center + ImVec2(+cross_extent,-cross_extent), center + ImVec2(-cross_extent,+cross_extent), cross_col, 1.0f);
+ ImGuiGroupData& group_data = window->DC.GroupStack.back();
- return pressed;
-}
+ ImRect group_bb(group_data.BackupCursorPos, window->DC.CursorMaxPos);
+ group_bb.Max = ImMax(group_bb.Min, group_bb.Max);
-bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos)
-{
- ImGuiContext& g = *GImGui;
- ImGuiWindow* window = g.CurrentWindow;
+ window->DC.CursorPos = group_data.BackupCursorPos;
+ window->DC.CursorMaxPos = ImMax(group_data.BackupCursorMaxPos, window->DC.CursorMaxPos);
+ window->DC.Indent = group_data.BackupIndent;
+ window->DC.GroupOffset = group_data.BackupGroupOffset;
+ window->DC.CurrentLineSize = group_data.BackupCurrentLineSize;
+ window->DC.CurrentLineTextBaseOffset = group_data.BackupCurrentLineTextBaseOffset;
+ window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; // To enforce Log carriage return
- ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f);
- ItemAdd(bb, id);
- bool hovered, held;
- bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None);
+ if (group_data.AdvanceCursor)
+ {
+ window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.PrevLineTextBaseOffset, group_data.BackupCurrentLineTextBaseOffset); // FIXME: Incorrect, we should grab the base offset from the *first line* of the group but it is hard to obtain now.
+ ItemSize(group_bb.GetSize(), group_data.BackupCurrentLineTextBaseOffset);
+ ItemAdd(group_bb, 0);
+ }
- ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
- if (hovered || held)
- window->DrawList->AddCircleFilled(bb.GetCenter() + ImVec2(0.0f, -0.5f), g.FontSize * 0.5f + 1.0f, col, 9);
- RenderArrow(bb.Min + g.Style.FramePadding, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f);
+ // If the current ActiveId was declared within the boundary of our group, we copy it to LastItemId so IsItemActive(), IsItemDeactivated() etc. will be functional on the entire group.
+ // It would be be neater if we replaced window.DC.LastItemId by e.g. 'bool LastItemIsActive', but would put a little more burden on individual widgets.
+ // (and if you grep for LastItemId you'll notice it is only used in that context.
+ if ((group_data.BackupActiveIdIsAlive != g.ActiveId) && (g.ActiveIdIsAlive == g.ActiveId) && g.ActiveId) // && g.ActiveIdWindow->RootWindow == window->RootWindow)
+ window->DC.LastItemId = g.ActiveId;
+ else if (!group_data.BackupActiveIdPreviousFrameIsAlive && g.ActiveIdPreviousFrameIsAlive) // && g.ActiveIdPreviousFrameWindow->RootWindow == window->RootWindow)
+ window->DC.LastItemId = g.ActiveIdPreviousFrame;
+ window->DC.LastItemRect = group_bb;
- // Switch to moving the window after mouse is moved beyond the initial drag threshold
- if (IsItemActive() && IsMouseDragging())
- StartMouseMovingWindow(window);
+ window->DC.GroupStack.pop_back();
- return pressed;
+ //window->DrawList->AddRect(group_bb.Min, group_bb.Max, IM_COL32(255,0,255,255)); // [Debug]
}
-void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col)
+// Gets back to previous line and continue with horizontal layout
+// pos_x == 0 : follow right after previous item
+// pos_x != 0 : align to specified x position (relative to window/group left)
+// spacing_w < 0 : use default spacing if pos_x == 0, no spacing if pos_x != 0
+// spacing_w >= 0 : enforce spacing amount
+void ImGui::SameLine(float pos_x, float spacing_w)
{
ImGuiWindow* window = GetCurrentWindow();
if (window->SkipItems)
return;
- ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
- if (border_col.w > 0.0f)
- bb.Max += ImVec2(2,2);
- ItemSize(bb);
- if (!ItemAdd(bb, 0))
- return;
-
- if (border_col.w > 0.0f)
+ ImGuiContext& g = *GImGui;
+ if (pos_x != 0.0f)
{
- window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f);
- window->DrawList->AddImage(user_texture_id, bb.Min+ImVec2(1,1), bb.Max-ImVec2(1,1), uv0, uv1, GetColorU32(tint_col));
+ if (spacing_w < 0.0f) spacing_w = 0.0f;
+ window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + pos_x + spacing_w + window->DC.GroupOffset.x + window->DC.ColumnsOffset.x;
+ window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y;
}
else
{
- window->DrawList->AddImage(user_texture_id, bb.Min, bb.Max, uv0, uv1, GetColorU32(tint_col));
+ if (spacing_w < 0.0f) spacing_w = g.Style.ItemSpacing.x;
+ window->DC.CursorPos.x = window->DC.CursorPosPrevLine.x + spacing_w;
+ window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y;
}
+ window->DC.CurrentLineSize = window->DC.PrevLineSize;
+ window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset;
}
-// frame_padding < 0: uses FramePadding from style (default)
-// frame_padding = 0: no framing
-// frame_padding > 0: set framing size
-// The color used are the button colors.
-bool ImGui::ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col)
+void ImGui::NewLine()
{
ImGuiWindow* window = GetCurrentWindow();
if (window->SkipItems)
- return false;
-
- ImGuiContext& g = *GImGui;
- const ImGuiStyle& style = g.Style;
-
- // Default to using texture ID as ID. User can still push string/integer prefixes.
- // We could hash the size/uv to create a unique ID but that would prevent the user from animating UV.
- PushID((void*)user_texture_id);
- const ImGuiID id = window->GetID("#image");
- PopID();
-
- const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : style.FramePadding;
- const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding*2);
- const ImRect image_bb(window->DC.CursorPos + padding, window->DC.CursorPos + padding + size);
- ItemSize(bb);
- if (!ItemAdd(bb, id))
- return false;
-
- bool hovered, held;
- bool pressed = ButtonBehavior(bb, id, &hovered, &held);
-
- // Render
- const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
- RenderNavHighlight(bb, id);
- RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, style.FrameRounding));
- if (bg_col.w > 0.0f)
- window->DrawList->AddRectFilled(image_bb.Min, image_bb.Max, GetColorU32(bg_col));
- window->DrawList->AddImage(user_texture_id, image_bb.Min, image_bb.Max, uv0, uv1, GetColorU32(tint_col));
-
- return pressed;
-}
-
-// Start logging ImGui output to TTY
-void ImGui::LogToTTY(int max_depth)
-{
- ImGuiContext& g = *GImGui;
- if (g.LogEnabled)
return;
- ImGuiWindow* window = g.CurrentWindow;
-
- IM_ASSERT(g.LogFile == NULL);
- g.LogFile = stdout;
- g.LogEnabled = true;
- g.LogStartDepth = window->DC.TreeDepth;
- if (max_depth >= 0)
- g.LogAutoExpandMaxDepth = max_depth;
-}
-// Start logging ImGui output to given file
-void ImGui::LogToFile(int max_depth, const char* filename)
-{
ImGuiContext& g = *GImGui;
- if (g.LogEnabled)
- return;
- ImGuiWindow* window = g.CurrentWindow;
-
- if (!filename)
- {
- filename = g.IO.LogFilename;
- if (!filename)
- return;
- }
-
- IM_ASSERT(g.LogFile == NULL);
- g.LogFile = ImFileOpen(filename, "ab");
- if (!g.LogFile)
- {
- IM_ASSERT(g.LogFile != NULL); // Consider this an error
- return;
- }
- g.LogEnabled = true;
- g.LogStartDepth = window->DC.TreeDepth;
- if (max_depth >= 0)
- g.LogAutoExpandMaxDepth = max_depth;
+ const ImGuiLayoutType backup_layout_type = window->DC.LayoutType;
+ window->DC.LayoutType = ImGuiLayoutType_Vertical;
+ if (window->DC.CurrentLineSize.y > 0.0f) // In the event that we are on a line with items that is smaller that FontSize high, we will preserve its height.
+ ItemSize(ImVec2(0,0));
+ else
+ ItemSize(ImVec2(0.0f, g.FontSize));
+ window->DC.LayoutType = backup_layout_type;
}
-// Start logging ImGui output to clipboard
-void ImGui::LogToClipboard(int max_depth)
+void ImGui::Indent(float indent_w)
{
ImGuiContext& g = *GImGui;
- if (g.LogEnabled)
- return;
- ImGuiWindow* window = g.CurrentWindow;
-
- IM_ASSERT(g.LogFile == NULL);
- g.LogFile = NULL;
- g.LogEnabled = true;
- g.LogStartDepth = window->DC.TreeDepth;
- if (max_depth >= 0)
- g.LogAutoExpandMaxDepth = max_depth;
+ ImGuiWindow* window = GetCurrentWindow();
+ window->DC.Indent.x += (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing;
+ window->DC.CursorPos.x = window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x;
}
-void ImGui::LogFinish()
+void ImGui::Unindent(float indent_w)
{
ImGuiContext& g = *GImGui;
- if (!g.LogEnabled)
- return;
-
- LogText(IM_NEWLINE);
- if (g.LogFile != NULL)
- {
- if (g.LogFile == stdout)
- fflush(g.LogFile);
- else
- fclose(g.LogFile);
- g.LogFile = NULL;
- }
- if (g.LogClipboard.size() > 1)
- {
- SetClipboardText(g.LogClipboard.begin());
- g.LogClipboard.clear();
- }
- g.LogEnabled = false;
+ ImGuiWindow* window = GetCurrentWindow();
+ window->DC.Indent.x -= (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing;
+ window->DC.CursorPos.x = window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x;
}
-// Helper to display logging buttons
-void ImGui::LogButtons()
-{
- ImGuiContext& g = *GImGui;
-
- PushID("LogButtons");
- const bool log_to_tty = Button("Log To TTY"); SameLine();
- const bool log_to_file = Button("Log To File"); SameLine();
- const bool log_to_clipboard = Button("Log To Clipboard"); SameLine();
- PushItemWidth(80.0f);
- PushAllowKeyboardFocus(false);
- SliderInt("Depth", &g.LogAutoExpandMaxDepth, 0, 9, NULL);
- PopAllowKeyboardFocus();
- PopItemWidth();
- PopID();
-
- // Start logging at the end of the function so that the buttons don't appear in the log
- if (log_to_tty)
- LogToTTY(g.LogAutoExpandMaxDepth);
- if (log_to_file)
- LogToFile(g.LogAutoExpandMaxDepth, g.IO.LogFilename);
- if (log_to_clipboard)
- LogToClipboard(g.LogAutoExpandMaxDepth);
-}
+//-----------------------------------------------------------------------------
+// TOOLTIPS
+//-----------------------------------------------------------------------------
-bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags)
+void ImGui::BeginTooltip()
{
- if (flags & ImGuiTreeNodeFlags_Leaf)
- return true;
-
- // We only write to the tree storage if the user clicks (or explicitly use SetNextTreeNode*** functions)
ImGuiContext& g = *GImGui;
- ImGuiWindow* window = g.CurrentWindow;
- ImGuiStorage* storage = window->DC.StateStorage;
-
- bool is_open;
- if (g.NextTreeNodeOpenCond != 0)
+ if (g.DragDropWithinSourceOrTarget)
{
- if (g.NextTreeNodeOpenCond & ImGuiCond_Always)
- {
- is_open = g.NextTreeNodeOpenVal;
- storage->SetInt(id, is_open);
- }
- else
- {
- // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently.
- const int stored_value = storage->GetInt(id, -1);
- if (stored_value == -1)
- {
- is_open = g.NextTreeNodeOpenVal;
- storage->SetInt(id, is_open);
- }
- else
- {
- is_open = stored_value != 0;
- }
- }
- g.NextTreeNodeOpenCond = 0;
+ // The default tooltip position is a little offset to give space to see the context menu (it's also clamped within the current viewport/monitor)
+ // In the context of a dragging tooltip we try to reduce that offset and we enforce following the cursor.
+ // Whatever we do we want to call SetNextWindowPos() to enforce a tooltip position and disable clipping the tooltip without our display area, like regular tooltip do.
+ //ImVec2 tooltip_pos = g.IO.MousePos - g.ActiveIdClickOffset - g.Style.WindowPadding;
+ ImVec2 tooltip_pos = g.IO.MousePos + ImVec2(16 * g.Style.MouseCursorScale, 8 * g.Style.MouseCursorScale);
+ SetNextWindowPos(tooltip_pos);
+ SetNextWindowBgAlpha(g.Style.Colors[ImGuiCol_PopupBg].w * 0.60f);
+ //PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * 0.60f); // This would be nice but e.g ColorButton with checkboard has issue with transparent colors :(
+ BeginTooltipEx(0, true);
}
else
{
- is_open = storage->GetInt(id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0;
+ BeginTooltipEx(0, false);
}
-
- // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior).
- // NB- If we are above max depth we still allow manually opened nodes to be logged.
- if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && window->DC.TreeDepth < g.LogAutoExpandMaxDepth)
- is_open = true;
-
- return is_open;
}
-bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end)
+// Not exposed publicly as BeginTooltip() because bool parameters are evil. Let's see if other needs arise first.
+void ImGui::BeginTooltipEx(ImGuiWindowFlags extra_flags, bool override_previous_tooltip)
{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
-
ImGuiContext& g = *GImGui;
- const ImGuiStyle& style = g.Style;
- const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0;
- const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, 0.0f);
-
- if (!label_end)
- label_end = FindRenderedTextEnd(label);
- const ImVec2 label_size = CalcTextSize(label, label_end, false);
-
- // We vertically grow up to current line height up the typical widget height.
- const float text_base_offset_y = ImMax(padding.y, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it
- const float frame_height = ImMax(ImMin(window->DC.CurrentLineSize.y, g.FontSize + style.FramePadding.y*2), label_size.y + padding.y*2);
- ImRect frame_bb = ImRect(window->DC.CursorPos, ImVec2(window->Pos.x + GetContentRegionMax().x, window->DC.CursorPos.y + frame_height));
- if (display_frame)
- {
- // Framed header expand a little outside the default padding
- frame_bb.Min.x -= (float)(int)(window->WindowPadding.x*0.5f) - 1;
- frame_bb.Max.x += (float)(int)(window->WindowPadding.x*0.5f) - 1;
- }
-
- const float text_offset_x = (g.FontSize + (display_frame ? padding.x*3 : padding.x*2)); // Collapser arrow width + Spacing
- const float text_width = g.FontSize + (label_size.x > 0.0f ? label_size.x + padding.x*2 : 0.0f); // Include collapser
- ItemSize(ImVec2(text_width, frame_height), text_base_offset_y);
-
- // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing
- // (Ideally we'd want to add a flag for the user to specify if we want the hit test to be done up to the right side of the content or not)
- const ImRect interact_bb = display_frame ? frame_bb : ImRect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + text_width + style.ItemSpacing.x*2, frame_bb.Max.y);
- bool is_open = TreeNodeBehaviorIsOpen(id, flags);
-
- // Store a flag for the current depth to tell if we will allow closing this node when navigating one of its child.
- // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
- // This is currently only support 32 level deep and we are fine with (1 << Depth) overflowing into a zero.
- if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
- window->DC.TreeDepthMayJumpToParentOnPop |= (1 << window->DC.TreeDepth);
-
- bool item_add = ItemAdd(interact_bb, id);
- window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HasDisplayRect;
- window->DC.LastItemDisplayRect = frame_bb;
-
- if (!item_add)
- {
- if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
- TreePushRawID(id);
- return is_open;
- }
-
- // Flags that affects opening behavior:
- // - 0(default) ..................... single-click anywhere to open
- // - OpenOnDoubleClick .............. double-click anywhere to open
- // - OpenOnArrow .................... single-click on arrow to open
- // - OpenOnDoubleClick|OpenOnArrow .. single-click on arrow or double-click anywhere to open
- ImGuiButtonFlags button_flags = ImGuiButtonFlags_NoKeyModifiers | ((flags & ImGuiTreeNodeFlags_AllowItemOverlap) ? ImGuiButtonFlags_AllowItemOverlap : 0);
- if (!(flags & ImGuiTreeNodeFlags_Leaf))
- button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
- if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
- button_flags |= ImGuiButtonFlags_PressedOnDoubleClick | ((flags & ImGuiTreeNodeFlags_OpenOnArrow) ? ImGuiButtonFlags_PressedOnClickRelease : 0);
-
- bool hovered, held, pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags);
- if (!(flags & ImGuiTreeNodeFlags_Leaf))
- {
- bool toggled = false;
- if (pressed)
- {
- toggled = !(flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) || (g.NavActivateId == id);
- if (flags & ImGuiTreeNodeFlags_OpenOnArrow)
- toggled |= IsMouseHoveringRect(interact_bb.Min, ImVec2(interact_bb.Min.x + text_offset_x, interact_bb.Max.y)) && (!g.NavDisableMouseHover);
- if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
- toggled |= g.IO.MouseDoubleClicked[0];
- if (g.DragDropActive && is_open) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again.
- toggled = false;
- }
-
- if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Left && is_open)
- {
- toggled = true;
- NavMoveRequestCancel();
- }
- if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right && !is_open) // If there's something upcoming on the line we may want to give it the priority?
- {
- toggled = true;
- NavMoveRequestCancel();
- }
-
- if (toggled)
- {
- is_open = !is_open;
- window->DC.StateStorage->SetInt(id, is_open);
- }
- }
- if (flags & ImGuiTreeNodeFlags_AllowItemOverlap)
- SetItemAllowOverlap();
-
- // Render
- const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
- const ImVec2 text_pos = frame_bb.Min + ImVec2(text_offset_x, text_base_offset_y);
- if (display_frame)
- {
- // Framed type
- RenderFrame(frame_bb.Min, frame_bb.Max, col, true, style.FrameRounding);
- RenderNavHighlight(frame_bb, id, ImGuiNavHighlightFlags_TypeThin);
- RenderArrow(frame_bb.Min + ImVec2(padding.x, text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 1.0f);
- if (g.LogEnabled)
- {
- // NB: '##' is normally used to hide text (as a library-wide feature), so we need to specify the text range to make sure the ## aren't stripped out here.
- const char log_prefix[] = "\n##";
- const char log_suffix[] = "##";
- LogRenderedText(&text_pos, log_prefix, log_prefix+3);
- RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
- LogRenderedText(&text_pos, log_suffix+1, log_suffix+3);
- }
- else
- {
- RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
- }
- }
- else
- {
- // Unframed typed for tree nodes
- if (hovered || (flags & ImGuiTreeNodeFlags_Selected))
- {
- RenderFrame(frame_bb.Min, frame_bb.Max, col, false);
- RenderNavHighlight(frame_bb, id, ImGuiNavHighlightFlags_TypeThin);
- }
-
- if (flags & ImGuiTreeNodeFlags_Bullet)
- RenderBullet(frame_bb.Min + ImVec2(text_offset_x * 0.5f, g.FontSize*0.50f + text_base_offset_y));
- else if (!(flags & ImGuiTreeNodeFlags_Leaf))
- RenderArrow(frame_bb.Min + ImVec2(padding.x, g.FontSize*0.15f + text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 0.70f);
- if (g.LogEnabled)
- LogRenderedText(&text_pos, ">");
- RenderText(text_pos, label, label_end, false);
- }
-
- if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
- TreePushRawID(id);
- return is_open;
-}
-
-// CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag).
-// This is basically the same as calling TreeNodeEx(label, ImGuiTreeNodeFlags_CollapsingHeader). You can remove the _NoTreePushOnOpen flag if you want behavior closer to normal TreeNode().
-bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags)
-{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
-
- return TreeNodeBehavior(window->GetID(label), flags | ImGuiTreeNodeFlags_CollapsingHeader, label);
-}
-
-bool ImGui::CollapsingHeader(const char* label, bool* p_open, ImGuiTreeNodeFlags flags)
-{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
-
- if (p_open && !*p_open)
- return false;
-
- ImGuiID id = window->GetID(label);
- bool is_open = TreeNodeBehavior(id, flags | ImGuiTreeNodeFlags_CollapsingHeader | (p_open ? ImGuiTreeNodeFlags_AllowItemOverlap : 0), label);
- if (p_open)
- {
- // Create a small overlapping close button // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc.
- ImGuiContext& g = *GImGui;
- ImGuiItemHoveredDataBackup last_item_backup;
- float button_radius = g.FontSize * 0.5f;
- ImVec2 button_center = ImVec2(ImMin(window->DC.LastItemRect.Max.x, window->ClipRect.Max.x) - g.Style.FramePadding.x - button_radius, window->DC.LastItemRect.GetCenter().y);
- if (CloseButton(window->GetID((void*)(intptr_t)(id+1)), button_center, button_radius))
- *p_open = false;
- last_item_backup.Restore();
- }
-
- return is_open;
-}
-
-bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags)
-{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
-
- return TreeNodeBehavior(window->GetID(label), flags, label, NULL);
+ char window_name[16];
+ ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", g.TooltipOverrideCount);
+ if (override_previous_tooltip)
+ if (ImGuiWindow* window = FindWindowByName(window_name))
+ if (window->Active)
+ {
+ // Hide previous tooltip from being displayed. We can't easily "reset" the content of a window so we create a new one.
+ window->Hidden = true;
+ window->HiddenFramesRegular = 1;
+ ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", ++g.TooltipOverrideCount);
+ }
+ ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip|ImGuiWindowFlags_NoInputs|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoNav;
+ Begin(window_name, NULL, flags | extra_flags);
}
-bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
+void ImGui::EndTooltip()
{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
-
- ImGuiContext& g = *GImGui;
- const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
- return TreeNodeBehavior(window->GetID(str_id), flags, g.TempBuffer, label_end);
+ IM_ASSERT(GetCurrentWindowRead()->Flags & ImGuiWindowFlags_Tooltip); // Mismatched BeginTooltip()/EndTooltip() calls
+ End();
}
-bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
+void ImGui::SetTooltipV(const char* fmt, va_list args)
{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
-
ImGuiContext& g = *GImGui;
- const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
- return TreeNodeBehavior(window->GetID(ptr_id), flags, g.TempBuffer, label_end);
-}
-
-bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args)
-{
- return TreeNodeExV(str_id, 0, fmt, args);
-}
-
-bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args)
-{
- return TreeNodeExV(ptr_id, 0, fmt, args);
-}
-
-bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
-{
- va_list args;
- va_start(args, fmt);
- bool is_open = TreeNodeExV(str_id, flags, fmt, args);
- va_end(args);
- return is_open;
-}
-
-bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
-{
- va_list args;
- va_start(args, fmt);
- bool is_open = TreeNodeExV(ptr_id, flags, fmt, args);
- va_end(args);
- return is_open;
-}
-
-bool ImGui::TreeNode(const char* str_id, const char* fmt, ...)
-{
- va_list args;
- va_start(args, fmt);
- bool is_open = TreeNodeExV(str_id, 0, fmt, args);
- va_end(args);
- return is_open;
+ if (g.DragDropWithinSourceOrTarget)
+ BeginTooltip();
+ else
+ BeginTooltipEx(0, true);
+ TextV(fmt, args);
+ EndTooltip();
}
-bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...)
+void ImGui::SetTooltip(const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
- bool is_open = TreeNodeExV(ptr_id, 0, fmt, args);
+ SetTooltipV(fmt, args);
va_end(args);
- return is_open;
}
-bool ImGui::TreeNode(const char* label)
-{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
- return TreeNodeBehavior(window->GetID(label), 0, label, NULL);
-}
-
-void ImGui::TreeAdvanceToLabelPos()
-{
- ImGuiContext& g = *GImGui;
- g.CurrentWindow->DC.CursorPos.x += GetTreeNodeToLabelSpacing();
-}
+//-----------------------------------------------------------------------------
+// POPUPS
+//-----------------------------------------------------------------------------
-// Horizontal distance preceding label when using TreeNode() or Bullet()
-float ImGui::GetTreeNodeToLabelSpacing()
+bool ImGui::IsPopupOpen(ImGuiID id)
{
ImGuiContext& g = *GImGui;
- return g.FontSize + (g.Style.FramePadding.x * 2.0f);
+ return g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].PopupId == id;
}
-void ImGui::SetNextTreeNodeOpen(bool is_open, ImGuiCond cond)
+bool ImGui::IsPopupOpen(const char* str_id)
{
ImGuiContext& g = *GImGui;
- if (g.CurrentWindow->SkipItems)
- return;
- g.NextTreeNodeOpenVal = is_open;
- g.NextTreeNodeOpenCond = cond ? cond : ImGuiCond_Always;
-}
-
-void ImGui::PushID(const char* str_id)
-{
- ImGuiWindow* window = GetCurrentWindowRead();
- window->IDStack.push_back(window->GetIDNoKeepAlive(str_id));
-}
-
-void ImGui::PushID(const char* str_id_begin, const char* str_id_end)
-{
- ImGuiWindow* window = GetCurrentWindowRead();
- window->IDStack.push_back(window->GetIDNoKeepAlive(str_id_begin, str_id_end));
-}
-
-void ImGui::PushID(const void* ptr_id)
-{
- ImGuiWindow* window = GetCurrentWindowRead();
- window->IDStack.push_back(window->GetIDNoKeepAlive(ptr_id));
-}
-
-void ImGui::PushID(int int_id)
-{
- const void* ptr_id = (void*)(intptr_t)int_id;
- ImGuiWindow* window = GetCurrentWindowRead();
- window->IDStack.push_back(window->GetIDNoKeepAlive(ptr_id));
-}
-
-void ImGui::PopID()
-{
- ImGuiWindow* window = GetCurrentWindowRead();
- window->IDStack.pop_back();
-}
-
-ImGuiID ImGui::GetID(const char* str_id)
-{
- return GImGui->CurrentWindow->GetID(str_id);
-}
-
-ImGuiID ImGui::GetID(const char* str_id_begin, const char* str_id_end)
-{
- return GImGui->CurrentWindow->GetID(str_id_begin, str_id_end);
-}
-
-ImGuiID ImGui::GetID(const void* ptr_id)
-{
- return GImGui->CurrentWindow->GetID(ptr_id);
+ return g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].PopupId == g.CurrentWindow->GetID(str_id);
}
-void ImGui::Bullet()
+ImGuiWindow* ImGui::GetFrontMostPopupModal()
{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return;
-
ImGuiContext& g = *GImGui;
- const ImGuiStyle& style = g.Style;
- const float line_height = ImMax(ImMin(window->DC.CurrentLineSize.y, g.FontSize + g.Style.FramePadding.y*2), g.FontSize);
- const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height));
- ItemSize(bb);
- if (!ItemAdd(bb, 0))
- {
- SameLine(0, style.FramePadding.x*2);
- return;
- }
-
- // Render and stay on same line
- RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f));
- SameLine(0, style.FramePadding.x*2);
+ for (int n = g.OpenPopupStack.Size-1; n >= 0; n--)
+ if (ImGuiWindow* popup = g.OpenPopupStack.Data[n].Window)
+ if (popup->Flags & ImGuiWindowFlags_Modal)
+ return popup;
+ return NULL;
}
-// Text with a little bullet aligned to the typical tree node.
-void ImGui::BulletTextV(const char* fmt, va_list args)
+void ImGui::OpenPopup(const char* str_id)
{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return;
-
ImGuiContext& g = *GImGui;
- const ImGuiStyle& style = g.Style;
-
- const char* text_begin = g.TempBuffer;
- const char* text_end = text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
- const ImVec2 label_size = CalcTextSize(text_begin, text_end, false);
- const float text_base_offset_y = ImMax(0.0f, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it
- const float line_height = ImMax(ImMin(window->DC.CurrentLineSize.y, g.FontSize + g.Style.FramePadding.y*2), g.FontSize);
- const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize + (label_size.x > 0.0f ? (label_size.x + style.FramePadding.x*2) : 0.0f), ImMax(line_height, label_size.y))); // Empty text doesn't add padding
- ItemSize(bb);
- if (!ItemAdd(bb, 0))
- return;
-
- // Render
- RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f));
- RenderText(bb.Min+ImVec2(g.FontSize + style.FramePadding.x*2, text_base_offset_y), text_begin, text_end, false);
-}
-
-void ImGui::BulletText(const char* fmt, ...)
-{
- va_list args;
- va_start(args, fmt);
- BulletTextV(fmt, args);
- va_end(args);
-}
-
-static inline int DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* data_ptr, const char* format)
-{
- if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32) // Signedness doesn't matter when pushing the argument
- return ImFormatString(buf, buf_size, format, *(const ImU32*)data_ptr);
- if (data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64) // Signedness doesn't matter when pushing the argument
- return ImFormatString(buf, buf_size, format, *(const ImU64*)data_ptr);
- if (data_type == ImGuiDataType_Float)
- return ImFormatString(buf, buf_size, format, *(const float*)data_ptr);
- if (data_type == ImGuiDataType_Double)
- return ImFormatString(buf, buf_size, format, *(const double*)data_ptr);
- IM_ASSERT(0);
- return 0;
-}
-
-// FIXME: Adding support for clamping on boundaries of the data type would be nice.
-static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, void* arg1, const void* arg2)
-{
- IM_ASSERT(op == '+' || op == '-');
- switch (data_type)
- {
- case ImGuiDataType_S32:
- if (op == '+') *(int*)output = *(const int*)arg1 + *(const int*)arg2;
- else if (op == '-') *(int*)output = *(const int*)arg1 - *(const int*)arg2;
- return;
- case ImGuiDataType_U32:
- if (op == '+') *(unsigned int*)output = *(const unsigned int*)arg1 + *(const ImU32*)arg2;
- else if (op == '-') *(unsigned int*)output = *(const unsigned int*)arg1 - *(const ImU32*)arg2;
- return;
- case ImGuiDataType_S64:
- if (op == '+') *(ImS64*)output = *(const ImS64*)arg1 + *(const ImS64*)arg2;
- else if (op == '-') *(ImS64*)output = *(const ImS64*)arg1 - *(const ImS64*)arg2;
- return;
- case ImGuiDataType_U64:
- if (op == '+') *(ImU64*)output = *(const ImU64*)arg1 + *(const ImU64*)arg2;
- else if (op == '-') *(ImU64*)output = *(const ImU64*)arg1 - *(const ImU64*)arg2;
- return;
- case ImGuiDataType_Float:
- if (op == '+') *(float*)output = *(const float*)arg1 + *(const float*)arg2;
- else if (op == '-') *(float*)output = *(const float*)arg1 - *(const float*)arg2;
- return;
- case ImGuiDataType_Double:
- if (op == '+') *(double*)output = *(const double*)arg1 + *(const double*)arg2;
- else if (op == '-') *(double*)output = *(const double*)arg1 - *(const double*)arg2;
- return;
- case ImGuiDataType_COUNT: break;
- }
- IM_ASSERT(0);
-}
-
-struct ImGuiDataTypeInfo
-{
- size_t Size;
- const char* PrintFmt; // Unused
- const char* ScanFmt;
-};
-
-static const ImGuiDataTypeInfo GDataTypeInfo[] =
-{
- { sizeof(int), "%d", "%d" },
- { sizeof(unsigned int), "%u", "%u" },
-#ifdef _MSC_VER
- { sizeof(ImS64), "%I64d","%I64d" },
- { sizeof(ImU64), "%I64u","%I64u" },
-#else
- { sizeof(ImS64), "%lld", "%lld" },
- { sizeof(ImU64), "%llu", "%llu" },
-#endif
- { sizeof(float), "%f", "%f" }, // float are promoted to double in va_arg
- { sizeof(double), "%f", "%lf" },
-};
-IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT);
-
-// User can input math operators (e.g. +100) to edit a numerical values.
-// NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess..
-static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* format)
-{
- while (ImCharIsBlankA(*buf))
- buf++;
-
- // We don't support '-' op because it would conflict with inputing negative value.
- // Instead you can use +-100 to subtract from an existing value
- char op = buf[0];
- if (op == '+' || op == '*' || op == '/')
- {
- buf++;
- while (ImCharIsBlankA(*buf))
- buf++;
- }
- else
- {
- op = 0;
- }
- if (!buf[0])
- return false;
-
- // Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all.
- IM_ASSERT(data_type < ImGuiDataType_COUNT);
- int data_backup[2];
- IM_ASSERT(GDataTypeInfo[data_type].Size <= sizeof(data_backup));
- memcpy(data_backup, data_ptr, GDataTypeInfo[data_type].Size);
-
- if (format == NULL)
- format = GDataTypeInfo[data_type].ScanFmt;
-
- int arg1i = 0;
- if (data_type == ImGuiDataType_S32)
- {
- int* v = (int*)data_ptr;
- int arg0i = *v;
- float arg1f = 0.0f;
- if (op && sscanf(initial_value_buf, format, &arg0i) < 1)
- return false;
- // Store operand in a float so we can use fractional value for multipliers (*1.1), but constant always parsed as integer so we can fit big integers (e.g. 2000000003) past float precision
- if (op == '+') { if (sscanf(buf, "%d", &arg1i)) *v = (int)(arg0i + arg1i); } // Add (use "+-" to subtract)
- else if (op == '*') { if (sscanf(buf, "%f", &arg1f)) *v = (int)(arg0i * arg1f); } // Multiply
- else if (op == '/') { if (sscanf(buf, "%f", &arg1f) && arg1f != 0.0f) *v = (int)(arg0i / arg1f); } // Divide
- else { if (sscanf(buf, format, &arg1i) == 1) *v = arg1i; } // Assign constant
- }
- else if (data_type == ImGuiDataType_U32 || data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64)
- {
- // Assign constant
- // FIXME: We don't bother handling support for legacy operators since they are a little too crappy. Instead we may implement a proper expression evaluator in the future.
- sscanf(buf, format, data_ptr);
- }
- else if (data_type == ImGuiDataType_Float)
- {
- // For floats we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in
- format = "%f";
- float* v = (float*)data_ptr;
- float arg0f = *v, arg1f = 0.0f;
- if (op && sscanf(initial_value_buf, format, &arg0f) < 1)
- return false;
- if (sscanf(buf, format, &arg1f) < 1)
- return false;
- if (op == '+') { *v = arg0f + arg1f; } // Add (use "+-" to subtract)
- else if (op == '*') { *v = arg0f * arg1f; } // Multiply
- else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide
- else { *v = arg1f; } // Assign constant
- }
- else if (data_type == ImGuiDataType_Double)
- {
- format = "%lf"; // scanf differentiate float/double unlike printf which forces everything to double because of ellipsis
- double* v = (double*)data_ptr;
- double arg0f = *v, arg1f = 0.0;
- if (op && sscanf(initial_value_buf, format, &arg0f) < 1)
- return false;
- if (sscanf(buf, format, &arg1f) < 1)
- return false;
- if (op == '+') { *v = arg0f + arg1f; } // Add (use "+-" to subtract)
- else if (op == '*') { *v = arg0f * arg1f; } // Multiply
- else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide
- else { *v = arg1f; } // Assign constant
- }
- return memcmp(data_backup, data_ptr, GDataTypeInfo[data_type].Size) != 0;
+ OpenPopupEx(g.CurrentWindow->GetID(str_id));
}
-// Create text input in place of a slider (when CTRL+Clicking on slider)
-// FIXME: Logic is messy and confusing.
-bool ImGui::InputScalarAsWidgetReplacement(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* data_ptr, const char* format)
+// Mark popup as open (toggle toward open state).
+// Popups are closed when user click outside, or activate a pressable item, or CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block.
+// Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level).
+// One open popup per level of the popup hierarchy (NB: when assigning we reset the Window member of ImGuiPopupRef to NULL)
+void ImGui::OpenPopupEx(ImGuiID id)
{
ImGuiContext& g = *GImGui;
- ImGuiWindow* window = GetCurrentWindow();
-
- // Our replacement widget will override the focus ID (registered previously to allow for a TAB focus to happen)
- // On the first frame, g.ScalarAsInputTextId == 0, then on subsequent frames it becomes == id
- SetActiveID(g.ScalarAsInputTextId, window);
- g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
- SetHoveredID(0);
- FocusableItemUnregister(window);
-
- char fmt_buf[32];
- char data_buf[32];
- format = ImParseFormatTrimDecorations(format, fmt_buf, IM_ARRAYSIZE(fmt_buf));
- DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, data_ptr, format);
- ImStrTrimBlanks(data_buf);
- ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | ((data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) ? ImGuiInputTextFlags_CharsScientific : ImGuiInputTextFlags_CharsDecimal);
- bool value_changed = InputTextEx(label, data_buf, IM_ARRAYSIZE(data_buf), bb.GetSize(), flags);
- if (g.ScalarAsInputTextId == 0) // First frame we started displaying the InputText widget
- {
- IM_ASSERT(g.ActiveId == id); // InputText ID expected to match the Slider ID
- g.ScalarAsInputTextId = g.ActiveId;
- SetHoveredID(id);
- }
- if (value_changed)
- return DataTypeApplyOpFromText(data_buf, g.InputTextState.InitialText.Data, data_type, data_ptr, NULL);
- return false;
-}
-
-// We don't use strchr() because our strings are usually very short and often start with '%'
-const char* ImParseFormatFindStart(const char* fmt)
-{
- while (char c = fmt[0])
- {
- if (c == '%' && fmt[1] != '%')
- return fmt;
- else if (c == '%')
- fmt++;
- fmt++;
- }
- return fmt;
-}
-
-const char* ImParseFormatFindEnd(const char* fmt)
-{
- // Printf/scanf types modifiers: I/L/h/j/l/t/w/z. Other uppercase letters qualify as types aka end of the format.
- if (fmt[0] != '%')
- return fmt;
- const unsigned int ignored_uppercase_mask = (1 << ('I'-'A')) | (1 << ('L'-'A'));
- const unsigned int ignored_lowercase_mask = (1 << ('h'-'a')) | (1 << ('j'-'a')) | (1 << ('l'-'a')) | (1 << ('t'-'a')) | (1 << ('w'-'a')) | (1 << ('z'-'a'));
- for (char c; (c = *fmt) != 0; fmt++)
- {
- if (c >= 'A' && c <= 'Z' && ((1 << (c - 'A')) & ignored_uppercase_mask) == 0)
- return fmt + 1;
- if (c >= 'a' && c <= 'z' && ((1 << (c - 'a')) & ignored_lowercase_mask) == 0)
- return fmt + 1;
- }
- return fmt;
-}
-
-// Extract the format out of a format string with leading or trailing decorations
-// fmt = "blah blah" -> return fmt
-// fmt = "%.3f" -> return fmt
-// fmt = "hello %.3f" -> return fmt + 6
-// fmt = "%.3f hello" -> return buf written with "%.3f"
-const char* ImParseFormatTrimDecorations(const char* fmt, char* buf, int buf_size)
-{
- const char* fmt_start = ImParseFormatFindStart(fmt);
- if (fmt_start[0] != '%')
- return fmt;
- const char* fmt_end = ImParseFormatFindEnd(fmt_start);
- if (fmt_end[0] == 0) // If we only have leading decoration, we don't need to copy the data.
- return fmt_start;
- ImStrncpy(buf, fmt_start, ImMin((int)(fmt_end + 1 - fmt_start), buf_size));
- return buf;
-}
+ ImGuiWindow* parent_window = g.CurrentWindow;
+ int current_stack_size = g.CurrentPopupStack.Size;
+ ImGuiPopupRef popup_ref; // Tagged as new ref as Window will be set back to NULL if we write this into OpenPopupStack.
+ popup_ref.PopupId = id;
+ popup_ref.Window = NULL;
+ popup_ref.ParentWindow = parent_window;
+ popup_ref.OpenFrameCount = g.FrameCount;
+ popup_ref.OpenParentId = parent_window->IDStack.back();
+ popup_ref.OpenMousePos = g.IO.MousePos;
+ popup_ref.OpenPopupPos = NavCalcPreferredRefPos();
-// Parse display precision back from the display format string
-// FIXME: This is still used by some navigation code path to infer a minimum tweak step, but we should aim to rework widgets so it isn't needed.
-int ImParseFormatPrecision(const char* fmt, int default_precision)
-{
- fmt = ImParseFormatFindStart(fmt);
- if (fmt[0] != '%')
- return default_precision;
- fmt++;
- while (*fmt >= '0' && *fmt <= '9')
- fmt++;
- int precision = INT_MAX;
- if (*fmt == '.')
+ //printf("[%05d] OpenPopupEx(0x%08X)\n", g.FrameCount, id);
+ if (g.OpenPopupStack.Size < current_stack_size + 1)
{
- fmt = ImAtoi<int>(fmt + 1, &precision);
- if (precision < 0 || precision > 99)
- precision = default_precision;
+ g.OpenPopupStack.push_back(popup_ref);
}
- if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation
- precision = -1;
- if ((*fmt == 'g' || *fmt == 'G') && precision == INT_MAX)
- precision = -1;
- return (precision == INT_MAX) ? default_precision : precision;
-}
-
-static float GetMinimumStepAtDecimalPrecision(int decimal_precision)
-{
- static const float min_steps[10] = { 1.0f, 0.1f, 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f, 0.0000001f, 0.00000001f, 0.000000001f };
- if (decimal_precision < 0)
- return FLT_MIN;
- return (decimal_precision >= 0 && decimal_precision < 10) ? min_steps[decimal_precision] : ImPow(10.0f, (float)-decimal_precision);
-}
-
-template<typename TYPE, typename SIGNEDTYPE>
-static inline TYPE RoundScalarWithFormat(const char* format, ImGuiDataType data_type, TYPE v)
-{
- const char* fmt_start = ImParseFormatFindStart(format);
- if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string
- return v;
- char v_str[64];
- ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v);
- const char* p = v_str;
- while (*p == ' ')
- p++;
- if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
- v = (TYPE)ImAtof(p);
else
- ImAtoi(p, (SIGNEDTYPE*)&v);
- return v;
-}
-
-template<typename TYPE, typename FLOATTYPE>
-static inline float SliderBehaviorCalcRatioFromValue(ImGuiDataType data_type, TYPE v, TYPE v_min, TYPE v_max, float power, float linear_zero_pos)
-{
- if (v_min == v_max)
- return 0.0f;
-
- const bool is_power = (power != 1.0f) && (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double);
- const TYPE v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min);
- if (is_power)
{
- if (v_clamped < 0.0f)
+ // Gently handle the user mistakenly calling OpenPopup() every frame. It is a programming mistake! However, if we were to run the regular code path, the ui
+ // would become completely unusable because the popup will always be in hidden-while-calculating-size state _while_ claiming focus. Which would be a very confusing
+ // situation for the programmer. Instead, we silently allow the popup to proceed, it will keep reappearing and the programming error will be more obvious to understand.
+ if (g.OpenPopupStack[current_stack_size].PopupId == id && g.OpenPopupStack[current_stack_size].OpenFrameCount == g.FrameCount - 1)
{
- const float f = 1.0f - (float)((v_clamped - v_min) / (ImMin((TYPE)0, v_max) - v_min));
- return (1.0f - ImPow(f, 1.0f/power)) * linear_zero_pos;
+ g.OpenPopupStack[current_stack_size].OpenFrameCount = popup_ref.OpenFrameCount;
}
else
{
- const float f = (float)((v_clamped - ImMax((TYPE)0, v_min)) / (v_max - ImMax((TYPE)0, v_min)));
- return linear_zero_pos + ImPow(f, 1.0f/power) * (1.0f - linear_zero_pos);
- }
- }
-
- // Linear slider
- return (float)((FLOATTYPE)(v_clamped - v_min) / (FLOATTYPE)(v_max - v_min));
-}
-
-// FIXME: Move some of the code into SliderBehavior(). Current responsability is larger than what the equivalent DragBehaviorT<> does, we also do some rendering, etc.
-template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
-static bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, TYPE* v, const TYPE v_min, const TYPE v_max, const char* format, float power, ImGuiSliderFlags flags, ImRect* out_grab_bb)
-{
- ImGuiContext& g = *GImGui;
- const ImGuiStyle& style = g.Style;
-
- const bool is_horizontal = (flags & ImGuiSliderFlags_Vertical) == 0;
- const bool is_decimal = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
- const bool is_power = (power != 1.0f) && is_decimal;
-
- const float grab_padding = 2.0f;
- const float slider_sz = is_horizontal ? (bb.GetWidth() - grab_padding * 2.0f) : (bb.GetHeight() - grab_padding * 2.0f);
- float grab_sz = style.GrabMinSize;
- SIGNEDTYPE v_range = (v_min < v_max ? v_max - v_min : v_min - v_max);
- if (!is_decimal && v_range >= 0) // v_range < 0 may happen on integer overflows
- grab_sz = ImMax((float)(slider_sz / (v_range + 1)), style.GrabMinSize); // For integer sliders: if possible have the grab size represent 1 unit
- grab_sz = ImMin(grab_sz, slider_sz);
- const float slider_usable_sz = slider_sz - grab_sz;
- const float slider_usable_pos_min = (is_horizontal ? bb.Min.x : bb.Min.y) + grab_padding + grab_sz*0.5f;
- const float slider_usable_pos_max = (is_horizontal ? bb.Max.x : bb.Max.y) - grab_padding - grab_sz*0.5f;
-
- // For power curve sliders that cross over sign boundary we want the curve to be symmetric around 0.0f
- float linear_zero_pos; // 0.0->1.0f
- if (is_power && v_min * v_max < 0.0f)
- {
- // Different sign
- const FLOATTYPE linear_dist_min_to_0 = ImPow(v_min >= 0 ? (FLOATTYPE)v_min : -(FLOATTYPE)v_min, (FLOATTYPE)1.0f/power);
- const FLOATTYPE linear_dist_max_to_0 = ImPow(v_max >= 0 ? (FLOATTYPE)v_max : -(FLOATTYPE)v_max, (FLOATTYPE)1.0f/power);
- linear_zero_pos = (float)(linear_dist_min_to_0 / (linear_dist_min_to_0 + linear_dist_max_to_0));
- }
- else
- {
- // Same sign
- linear_zero_pos = v_min < 0.0f ? 1.0f : 0.0f;
- }
-
- // Process interacting with the slider
- bool value_changed = false;
- if (g.ActiveId == id)
- {
- bool set_new_value = false;
- float clicked_t = 0.0f;
- if (g.ActiveIdSource == ImGuiInputSource_Mouse)
- {
- if (!g.IO.MouseDown[0])
- {
- ClearActiveID();
- }
- else
- {
- const float mouse_abs_pos = is_horizontal ? g.IO.MousePos.x : g.IO.MousePos.y;
- clicked_t = (slider_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - slider_usable_pos_min) / slider_usable_sz, 0.0f, 1.0f) : 0.0f;
- if (!is_horizontal)
- clicked_t = 1.0f - clicked_t;
- set_new_value = true;
- }
- }
- else if (g.ActiveIdSource == ImGuiInputSource_Nav)
- {
- const ImVec2 delta2 = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 0.0f, 0.0f);
- float delta = is_horizontal ? delta2.x : -delta2.y;
- if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
- {
- ClearActiveID();
- }
- else if (delta != 0.0f)
- {
- clicked_t = SliderBehaviorCalcRatioFromValue<TYPE,FLOATTYPE>(data_type, *v, v_min, v_max, power, linear_zero_pos);
- const int decimal_precision = is_decimal ? ImParseFormatPrecision(format, 3) : 0;
- if ((decimal_precision > 0) || is_power)
- {
- delta /= 100.0f; // Gamepad/keyboard tweak speeds in % of slider bounds
- if (IsNavInputDown(ImGuiNavInput_TweakSlow))
- delta /= 10.0f;
- }
- else
- {
- if ((v_range >= -100.0f && v_range <= 100.0f) || IsNavInputDown(ImGuiNavInput_TweakSlow))
- delta = ((delta < 0.0f) ? -1.0f : +1.0f) / (float)v_range; // Gamepad/keyboard tweak speeds in integer steps
- else
- delta /= 100.0f;
- }
- if (IsNavInputDown(ImGuiNavInput_TweakFast))
- delta *= 10.0f;
- set_new_value = true;
- if ((clicked_t >= 1.0f && delta > 0.0f) || (clicked_t <= 0.0f && delta < 0.0f)) // This is to avoid applying the saturation when already past the limits
- set_new_value = false;
- else
- clicked_t = ImSaturate(clicked_t + delta);
- }
- }
-
- if (set_new_value)
- {
- TYPE v_new;
- if (is_power)
- {
- // Account for power curve scale on both sides of the zero
- if (clicked_t < linear_zero_pos)
- {
- // Negative: rescale to the negative range before powering
- float a = 1.0f - (clicked_t / linear_zero_pos);
- a = ImPow(a, power);
- v_new = ImLerp(ImMin(v_max, (TYPE)0), v_min, a);
- }
- else
- {
- // Positive: rescale to the positive range before powering
- float a;
- if (ImFabs(linear_zero_pos - 1.0f) > 1.e-6f)
- a = (clicked_t - linear_zero_pos) / (1.0f - linear_zero_pos);
- else
- a = clicked_t;
- a = ImPow(a, power);
- v_new = ImLerp(ImMax(v_min, (TYPE)0), v_max, a);
- }
- }
- else
- {
- // Linear slider
- if (is_decimal)
- {
- v_new = ImLerp(v_min, v_max, clicked_t);
- }
- else
- {
- // For integer values we want the clicking position to match the grab box so we round above
- // This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property..
- FLOATTYPE v_new_off_f = (v_max - v_min) * clicked_t;
- TYPE v_new_off_floor = (TYPE)(v_new_off_f);
- TYPE v_new_off_round = (TYPE)(v_new_off_f + (FLOATTYPE)0.5);
- if (!is_decimal && v_new_off_floor < v_new_off_round)
- v_new = v_min + v_new_off_round;
- else
- v_new = v_min + v_new_off_floor;
- }
- }
-
- // Round to user desired precision based on format string
- v_new = RoundScalarWithFormat<TYPE,SIGNEDTYPE>(format, data_type, v_new);
-
- // Apply result
- if (*v != v_new)
- {
- *v = v_new;
- value_changed = true;
- }
+ // Close child popups if any, then flag popup for open/reopen
+ g.OpenPopupStack.resize(current_stack_size + 1);
+ g.OpenPopupStack[current_stack_size] = popup_ref;
}
- }
-
- // Output grab position so it can be displayed by the caller
- float grab_t = SliderBehaviorCalcRatioFromValue<TYPE,FLOATTYPE>(data_type, *v, v_min, v_max, power, linear_zero_pos);
- if (!is_horizontal)
- grab_t = 1.0f - grab_t;
- const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t);
- if (is_horizontal)
- *out_grab_bb = ImRect(grab_pos - grab_sz*0.5f, bb.Min.y + grab_padding, grab_pos + grab_sz*0.5f, bb.Max.y - grab_padding);
- else
- *out_grab_bb = ImRect(bb.Min.x + grab_padding, grab_pos - grab_sz*0.5f, bb.Max.x - grab_padding, grab_pos + grab_sz*0.5f);
-
- return value_changed;
-}
-// For 32-bits and larger types, slider bounds are limited to half the natural type range.
-// So e.g. an integer Slider between INT_MAX-10 and INT_MAX will fail, but an integer Slider between INT_MAX/2-10 and INT_MAX/2 will be ok.
-// It would be possible to lift that limitation with some work but it doesn't seem to be worth it for sliders.
-bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power, ImGuiSliderFlags flags, ImRect* out_grab_bb)
-{
- switch (data_type)
- {
- case ImGuiDataType_S32:
- IM_ASSERT(*(const ImS32*)v_min >= IM_S32_MIN/2 && *(const ImS32*)v_max <= IM_S32_MAX/2);
- return SliderBehaviorT<ImS32, ImS32, float >(bb, id, data_type, (ImS32*)v, *(const ImS32*)v_min, *(const ImS32*)v_max, format, power, flags, out_grab_bb);
- case ImGuiDataType_U32:
- IM_ASSERT(*(const ImU32*)v_min <= IM_U32_MAX/2);
- return SliderBehaviorT<ImU32, ImS32, float >(bb, id, data_type, (ImU32*)v, *(const ImU32*)v_min, *(const ImU32*)v_max, format, power, flags, out_grab_bb);
- case ImGuiDataType_S64:
- IM_ASSERT(*(const ImS64*)v_min >= IM_S64_MIN/2 && *(const ImS64*)v_max <= IM_S64_MAX/2);
- return SliderBehaviorT<ImS64, ImS64, double>(bb, id, data_type, (ImS64*)v, *(const ImS64*)v_min, *(const ImS64*)v_max, format, power, flags, out_grab_bb);
- case ImGuiDataType_U64:
- IM_ASSERT(*(const ImU64*)v_min <= IM_U64_MAX/2);
- return SliderBehaviorT<ImU64, ImS64, double>(bb, id, data_type, (ImU64*)v, *(const ImU64*)v_min, *(const ImU64*)v_max, format, power, flags, out_grab_bb);
- case ImGuiDataType_Float:
- IM_ASSERT(*(const float*)v_min >= -FLT_MAX/2.0f && *(const float*)v_max <= FLT_MAX/2.0f);
- return SliderBehaviorT<float, float, float >(bb, id, data_type, (float*)v, *(const float*)v_min, *(const float*)v_max, format, power, flags, out_grab_bb);
- case ImGuiDataType_Double:
- IM_ASSERT(*(const double*)v_min >= -DBL_MAX/2.0f && *(const double*)v_max <= DBL_MAX/2.0f);
- return SliderBehaviorT<double,double,double>(bb, id, data_type, (double*)v, *(const double*)v_min, *(const double*)v_max, format, power, flags, out_grab_bb);
- case ImGuiDataType_COUNT: break;
+ // When reopening a popup we first refocus its parent, otherwise if its parent is itself a popup it would get closed by ClosePopupsOverWindow().
+ // This is equivalent to what ClosePopupToLevel() does.
+ //if (g.OpenPopupStack[current_stack_size].PopupId == id)
+ // FocusWindow(parent_window);
}
- IM_ASSERT(0);
- return false;
}
-// FIXME-LEGACY: Prior to 1.61 our DragInt() function internally used floats and because of this the compile-time default value for format was "%.0f".
-// Even though we changed the compile-time default, we expect users to have carried %f around, which would break the display of DragInt() calls.
-// To honor backward compatibility we are rewriting the format string, unless IMGUI_DISABLE_OBSOLETE_FUNCTIONS is enabled. What could possibly go wrong?!
-static const char* PatchFormatStringFloatToInt(const char* fmt)
+bool ImGui::OpenPopupOnItemClick(const char* str_id, int mouse_button)
{
- if (fmt[0] == '%' && fmt[1] == '.' && fmt[2] == '0' && fmt[3] == 'f' && fmt[4] == 0) // Fast legacy path for "%.0f" which is expected to be the most common case.
- return "%d";
- const char* fmt_start = ImParseFormatFindStart(fmt); // Find % (if any, and ignore %%)
- const char* fmt_end = ImParseFormatFindEnd(fmt_start); // Find end of format specifier, which itself is an exercise of confidence/recklessness (because snprintf is dependent on libc or user).
- if (fmt_end > fmt_start && fmt_end[-1] == 'f')
+ ImGuiWindow* window = GImGui->CurrentWindow;
+ if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))
{
-#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
- if (fmt_start == fmt && fmt_end[0] == 0)
- return "%d";
- ImGuiContext& g = *GImGui;
- ImFormatString(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), "%.*s%%d%s", (int)(fmt_start - fmt), fmt, fmt_end); // Honor leading and trailing decorations, but lose alignment/precision.
- return g.TempBuffer;
-#else
- IM_ASSERT(0 && "DragInt(): Invalid format string!"); // Old versions used a default parameter of "%.0f", please replace with e.g. "%d"
-#endif
+ ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict!
+ IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item)
+ OpenPopupEx(id);
+ return true;
}
- return fmt;
+ return false;
}
-bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power)
+void ImGui::ClosePopupsOverWindow(ImGuiWindow* ref_window)
{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
-
ImGuiContext& g = *GImGui;
- const ImGuiStyle& style = g.Style;
- const ImGuiID id = window->GetID(label);
- const float w = CalcItemWidth();
-
- const ImVec2 label_size = CalcTextSize(label, NULL, true);
- const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
- const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
-
- // NB- we don't call ItemSize() yet because we may turn into a text edit box below
- if (!ItemAdd(total_bb, id, &frame_bb))
- {
- ItemSize(total_bb, style.FramePadding.y);
- return false;
- }
-
- // Default format string when passing NULL
- // Patch old "%.0f" format string to use "%d", read function comments for more details.
- IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
- if (format == NULL)
- format = GDataTypeInfo[data_type].PrintFmt;
- else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0)
- format = PatchFormatStringFloatToInt(format);
+ if (g.OpenPopupStack.empty())
+ return;
- // Tabbing or CTRL-clicking on Slider turns it into an input box
- bool start_text_input = false;
- const bool tab_focus_requested = FocusableItemRegister(window, id);
- const bool hovered = ItemHoverable(frame_bb, id);
- if (tab_focus_requested || (hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id))
+ // When popups are stacked, clicking on a lower level popups puts focus back to it and close popups above it.
+ // Don't close our own child popup windows.
+ int n = 0;
+ if (ref_window)
{
- SetActiveID(id, window);
- SetFocusID(id, window);
- FocusWindow(window);
- g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
- if (tab_focus_requested || g.IO.KeyCtrl || g.NavInputId == id)
+ for (n = 0; n < g.OpenPopupStack.Size; n++)
{
- start_text_input = true;
- g.ScalarAsInputTextId = 0;
+ ImGuiPopupRef& popup = g.OpenPopupStack[n];
+ if (!popup.Window)
+ continue;
+ IM_ASSERT((popup.Window->Flags & ImGuiWindowFlags_Popup) != 0);
+ if (popup.Window->Flags & ImGuiWindowFlags_ChildWindow)
+ continue;
+
+ // Trim the stack if popups are not direct descendant of the reference window (which is often the NavWindow)
+ bool has_focus = false;
+ for (int m = n; m < g.OpenPopupStack.Size && !has_focus; m++)
+ has_focus = (g.OpenPopupStack[m].Window && g.OpenPopupStack[m].Window->RootWindow == ref_window->RootWindow);
+ if (!has_focus)
+ break;
}
}
- if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id))
- return InputScalarAsWidgetReplacement(frame_bb, id, label, data_type, v, format);
-
- ItemSize(total_bb, style.FramePadding.y);
-
- // Draw frame
- const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
- RenderNavHighlight(frame_bb, id);
- RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
-
- // Slider behavior
- ImRect grab_bb;
- const bool value_changed = SliderBehavior(frame_bb, id, data_type, v, v_min, v_max, format, power, ImGuiSliderFlags_None, &grab_bb);
- if (value_changed)
- MarkItemEdited(id);
-
- // Render grab
- window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
-
- // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
- char value_buf[64];
- const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format);
- RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.5f));
-
- if (label_size.x > 0.0f)
- RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
-
- return value_changed;
-}
-
-bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, float power)
-{
- return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, power);
+ if (n < g.OpenPopupStack.Size) // This test is not required but it allows to set a convenient breakpoint on the block below
+ ClosePopupToLevel(n);
}
-bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power)
+void ImGui::ClosePopupToLevel(int remaining)
{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
-
+ IM_ASSERT(remaining >= 0);
ImGuiContext& g = *GImGui;
- const ImGuiStyle& style = g.Style;
- const ImGuiID id = window->GetID(label);
-
- const ImVec2 label_size = CalcTextSize(label, NULL, true);
- const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
- const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
-
- ItemSize(bb, style.FramePadding.y);
- if (!ItemAdd(frame_bb, id))
- return false;
-
- // Default format string when passing NULL
- // Patch old "%.0f" format string to use "%d", read function comments for more details.
- IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
- if (format == NULL)
- format = GDataTypeInfo[data_type].PrintFmt;
- else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0)
- format = PatchFormatStringFloatToInt(format);
-
- const bool hovered = ItemHoverable(frame_bb, id);
- if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavInputId == id)
- {
- SetActiveID(id, window);
- SetFocusID(id, window);
- FocusWindow(window);
- g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
- }
-
-
- // Draw frame
- const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
- RenderNavHighlight(frame_bb, id);
- RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
-
- // Slider behavior
- ImRect grab_bb;
- const bool value_changed = SliderBehavior(frame_bb, id, data_type, v, v_min, v_max, format, power, ImGuiSliderFlags_Vertical, &grab_bb);
- if (value_changed)
- MarkItemEdited(id);
-
- // Render grab
- window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
-
- // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
- // For the vertical slider we allow centered text to overlap the frame padding
- char value_buf[64];
- const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format);
- RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.0f));
- if (label_size.x > 0.0f)
- RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
-
- return value_changed;
-}
-
-bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max)
-{
- float v_deg = (*v_rad) * 360.0f / (2*IM_PI);
- bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, "%.0f deg", 1.0f);
- *v_rad = v_deg * (2*IM_PI) / 360.0f;
- return value_changed;
-}
-
-bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* format)
-{
- return SliderScalar(label, ImGuiDataType_S32, v, &v_min, &v_max, format);
-}
-
-bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format, float power)
-{
- return VSliderScalar(label, size, ImGuiDataType_Float, v, &v_min, &v_max, format, power);
-}
-
-bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format)
-{
- return VSliderScalar(label, size, ImGuiDataType_S32, v, &v_min, &v_max, format);
+ ImGuiWindow* focus_window = (remaining > 0) ? g.OpenPopupStack[remaining-1].Window : g.OpenPopupStack[0].ParentWindow;
+ if (g.NavLayer == 0)
+ focus_window = NavRestoreLastChildNavWindow(focus_window);
+ FocusWindow(focus_window);
+ focus_window->DC.NavHideHighlightOneFrame = true;
+ g.OpenPopupStack.resize(remaining);
}
-// Add multiple sliders on 1 line for compact edition of multiple components
-bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* v_min, const void* v_max, const char* format, float power)
+void ImGui::ClosePopup(ImGuiID id)
{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
-
+ if (!IsPopupOpen(id))
+ return;
ImGuiContext& g = *GImGui;
- bool value_changed = false;
- BeginGroup();
- PushID(label);
- PushMultiItemsWidths(components);
- size_t type_size = GDataTypeInfo[data_type].Size;
- for (int i = 0; i < components; i++)
- {
- PushID(i);
- value_changed |= SliderScalar("##v", data_type, v, v_min, v_max, format, power);
- SameLine(0, g.Style.ItemInnerSpacing.x);
- PopID();
- PopItemWidth();
- v = (void*)((char*)v + type_size);
- }
- PopID();
-
- TextUnformatted(label, FindRenderedTextEnd(label));
- EndGroup();
- return value_changed;
-}
-
-bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, float power)
-{
- return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, power);
-}
-
-bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, float power)
-{
- return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, power);
-}
-
-bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, float power)
-{
- return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, power);
-}
-
-bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format)
-{
- return SliderScalarN(label, ImGuiDataType_S32, v, 2, &v_min, &v_max, format);
-}
-
-bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format)
-{
- return SliderScalarN(label, ImGuiDataType_S32, v, 3, &v_min, &v_max, format);
+ ClosePopupToLevel(g.OpenPopupStack.Size - 1);
}
-bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format)
+// Close the popup we have begin-ed into.
+void ImGui::CloseCurrentPopup()
{
- return SliderScalarN(label, ImGuiDataType_S32, v, 4, &v_min, &v_max, format);
+ ImGuiContext& g = *GImGui;
+ int popup_idx = g.CurrentPopupStack.Size - 1;
+ if (popup_idx < 0 || popup_idx >= g.OpenPopupStack.Size || g.CurrentPopupStack[popup_idx].PopupId != g.OpenPopupStack[popup_idx].PopupId)
+ return;
+ while (popup_idx > 0 && g.OpenPopupStack[popup_idx].Window && (g.OpenPopupStack[popup_idx].Window->Flags & ImGuiWindowFlags_ChildMenu))
+ popup_idx--;
+ ClosePopupToLevel(popup_idx);
}
-// This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls)
-template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
-static bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, float power)
+bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags)
{
ImGuiContext& g = *GImGui;
-
- // Default tweak speed
- bool has_min_max = (v_min != v_max) && (v_max - v_max < FLT_MAX);
- if (v_speed == 0.0f && has_min_max)
- v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio);
-
- // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings
- float adjust_delta = 0.0f;
- if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && g.IO.MouseDragMaxDistanceSqr[0] > 1.0f*1.0f)
- {
- adjust_delta = g.IO.MouseDelta.x;
- if (g.IO.KeyAlt)
- adjust_delta *= 1.0f/100.0f;
- if (g.IO.KeyShift)
- adjust_delta *= 10.0f;
- }
- else if (g.ActiveIdSource == ImGuiInputSource_Nav)
- {
- int decimal_precision = (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) ? ImParseFormatPrecision(format, 3) : 0;
- adjust_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard|ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 1.0f/10.0f, 10.0f).x;
- v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision));
- }
- adjust_delta *= v_speed;
-
- // Clear current value on activation
- // Avoid altering values and clamping when we are _already_ past the limits and heading in the same direction, so e.g. if range is 0..255, current value is 300 and we are pushing to the right side, keep the 300.
- bool is_just_activated = g.ActiveIdIsJustActivated;
- bool is_already_past_limits_and_pushing_outward = has_min_max && ((*v >= v_max && adjust_delta > 0.0f) || (*v <= v_min && adjust_delta < 0.0f));
- if (is_just_activated || is_already_past_limits_and_pushing_outward)
- {
- g.DragCurrentAccum = 0.0f;
- g.DragCurrentAccumDirty = false;
- }
- else if (adjust_delta != 0.0f)
+ if (!IsPopupOpen(id))
{
- g.DragCurrentAccum += adjust_delta;
- g.DragCurrentAccumDirty = true;
- }
-
- if (!g.DragCurrentAccumDirty)
+ g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values
return false;
-
- TYPE v_cur = *v;
- FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f;
-
- const bool is_power = (power != 1.0f && (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) && has_min_max);
- if (is_power)
- {
- // Offset + round to user desired precision, with a curve on the v_min..v_max range to get more precision on one side of the range
- FLOATTYPE v_old_norm_curved = ImPow((FLOATTYPE)(v_cur - v_min) / (FLOATTYPE)(v_max - v_min), (FLOATTYPE)1.0f / power);
- FLOATTYPE v_new_norm_curved = v_old_norm_curved + (g.DragCurrentAccum / (v_max - v_min));
- v_cur = v_min + (TYPE)ImPow(ImSaturate((float)v_new_norm_curved), power) * (v_max - v_min);
- v_old_ref_for_accum_remainder = v_old_norm_curved;
}
- else
- {
- v_cur += (TYPE)g.DragCurrentAccum;
- }
-
- // Round to user desired precision based on format string
- v_cur = RoundScalarWithFormat<TYPE, SIGNEDTYPE>(format, data_type, v_cur);
- // Preserve remainder after rounding has been applied. This also allow slow tweaking of values.
- g.DragCurrentAccumDirty = false;
- if (is_power)
- {
- FLOATTYPE v_cur_norm_curved = ImPow((FLOATTYPE)(v_cur - v_min) / (FLOATTYPE)(v_max - v_min), (FLOATTYPE)1.0f / power);
- g.DragCurrentAccum -= (float)(v_cur_norm_curved - v_old_ref_for_accum_remainder);
- }
+ char name[20];
+ if (extra_flags & ImGuiWindowFlags_ChildMenu)
+ ImFormatString(name, IM_ARRAYSIZE(name), "##Menu_%02d", g.CurrentPopupStack.Size); // Recycle windows based on depth
else
- {
- g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v);
- }
-
- // Lose zero sign for float/double
- if (v_cur == (TYPE)-0)
- v_cur = (TYPE)0;
+ ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x", id); // Not recycling, so we can close/open during the same frame
- // Clamp values (handle overflow/wrap-around)
- if (*v != v_cur && has_min_max)
- {
- if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f))
- v_cur = v_min;
- if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f))
- v_cur = v_max;
- }
+ bool is_open = Begin(name, NULL, extra_flags | ImGuiWindowFlags_Popup);
+ if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display)
+ EndPopup();
- // Apply result
- if (*v == v_cur)
- return false;
- *v = v_cur;
- return true;
+ return is_open;
}
-bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* v, float v_speed, const void* v_min, const void* v_max, const char* format, float power)
+bool ImGui::BeginPopup(const char* str_id, ImGuiWindowFlags flags)
{
ImGuiContext& g = *GImGui;
- if (g.ActiveId == id)
+ if (g.OpenPopupStack.Size <= g.CurrentPopupStack.Size) // Early out for performance
{
- if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0])
- ClearActiveID();
- else if (g.ActiveIdSource == ImGuiInputSource_Nav && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
- ClearActiveID();
- }
- if (g.ActiveId != id)
+ g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values
return false;
-
- switch (data_type)
- {
- case ImGuiDataType_S32: return DragBehaviorT<ImS32, ImS32, float >(data_type, (ImS32*)v, v_speed, v_min ? *(const ImS32* )v_min : IM_S32_MIN, v_max ? *(const ImS32* )v_max : IM_S32_MAX, format, power);
- case ImGuiDataType_U32: return DragBehaviorT<ImU32, ImS32, float >(data_type, (ImU32*)v, v_speed, v_min ? *(const ImU32* )v_min : IM_U32_MIN, v_max ? *(const ImU32* )v_max : IM_U32_MAX, format, power);
- case ImGuiDataType_S64: return DragBehaviorT<ImS64, ImS64, double>(data_type, (ImS64*)v, v_speed, v_min ? *(const ImS64* )v_min : IM_S64_MIN, v_max ? *(const ImS64* )v_max : IM_S64_MAX, format, power);
- case ImGuiDataType_U64: return DragBehaviorT<ImU64, ImS64, double>(data_type, (ImU64*)v, v_speed, v_min ? *(const ImU64* )v_min : IM_U64_MIN, v_max ? *(const ImU64* )v_max : IM_U64_MAX, format, power);
- case ImGuiDataType_Float: return DragBehaviorT<float, float, float >(data_type, (float*)v, v_speed, v_min ? *(const float* )v_min : -FLT_MAX, v_max ? *(const float* )v_max : FLT_MAX, format, power);
- case ImGuiDataType_Double: return DragBehaviorT<double,double,double>(data_type, (double*)v, v_speed, v_min ? *(const double*)v_min : -DBL_MAX, v_max ? *(const double*)v_max : DBL_MAX, format, power);
- case ImGuiDataType_COUNT: break;
}
- IM_ASSERT(0);
- return false;
+ return BeginPopupEx(g.CurrentWindow->GetID(str_id), flags|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings);
}
-bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* v, float v_speed, const void* v_min, const void* v_max, const char* format, float power)
+bool ImGui::BeginPopupModal(const char* name, bool* p_open, ImGuiWindowFlags flags)
{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
-
- if (power != 1.0f)
- IM_ASSERT(v_min != NULL && v_max != NULL); // When using a power curve the drag needs to have known bounds
-
ImGuiContext& g = *GImGui;
- const ImGuiStyle& style = g.Style;
- const ImGuiID id = window->GetID(label);
- const float w = CalcItemWidth();
-
- const ImVec2 label_size = CalcTextSize(label, NULL, true);
- const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
- const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
- const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
-
- // NB- we don't call ItemSize() yet because we may turn into a text edit box below
- if (!ItemAdd(total_bb, id, &frame_bb))
+ ImGuiWindow* window = g.CurrentWindow;
+ const ImGuiID id = window->GetID(name);
+ if (!IsPopupOpen(id))
{
- ItemSize(total_bb, style.FramePadding.y);
+ g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values
return false;
}
- const bool hovered = ItemHoverable(frame_bb, id);
- // Default format string when passing NULL
- // Patch old "%.0f" format string to use "%d", read function comments for more details.
- IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
- if (format == NULL)
- format = GDataTypeInfo[data_type].PrintFmt;
- else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0)
- format = PatchFormatStringFloatToInt(format);
+ // Center modal windows by default
+ // FIXME: Should test for (PosCond & window->SetWindowPosAllowFlags) with the upcoming window.
+ if (g.NextWindowData.PosCond == 0)
+ SetNextWindowPos(g.IO.DisplaySize * 0.5f, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
- // Tabbing or CTRL-clicking on Drag turns it into an input box
- bool start_text_input = false;
- const bool tab_focus_requested = FocusableItemRegister(window, id);
- if (tab_focus_requested || (hovered && (g.IO.MouseClicked[0] || g.IO.MouseDoubleClicked[0])) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id))
+ bool is_open = Begin(name, p_open, flags | ImGuiWindowFlags_Popup | ImGuiWindowFlags_Modal | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings);
+ if (!is_open || (p_open && !*p_open)) // NB: is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
{
- SetActiveID(id, window);
- SetFocusID(id, window);
- FocusWindow(window);
- g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
- if (tab_focus_requested || g.IO.KeyCtrl || g.IO.MouseDoubleClicked[0] || g.NavInputId == id)
- {
- start_text_input = true;
- g.ScalarAsInputTextId = 0;
- }
- }
- if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id))
- return InputScalarAsWidgetReplacement(frame_bb, id, label, data_type, v, format);
-
- // Actual drag behavior
- ItemSize(total_bb, style.FramePadding.y);
- const bool value_changed = DragBehavior(id, data_type, v, v_speed, v_min, v_max, format, power);
- if (value_changed)
- MarkItemEdited(id);
-
- // Draw frame
- const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
- RenderNavHighlight(frame_bb, id);
- RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding);
-
- // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
- char value_buf[64];
- const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format);
- RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
-
- if (label_size.x > 0.0f)
- RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
-
- return value_changed;
-}
-
-bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* v, int components, float v_speed, const void* v_min, const void* v_max, const char* format, float power)
-{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
+ EndPopup();
+ if (is_open)
+ ClosePopup(id);
return false;
-
- ImGuiContext& g = *GImGui;
- bool value_changed = false;
- BeginGroup();
- PushID(label);
- PushMultiItemsWidths(components);
- size_t type_size = GDataTypeInfo[data_type].Size;
- for (int i = 0; i < components; i++)
- {
- PushID(i);
- value_changed |= DragScalar("##v", data_type, v, v_speed, v_min, v_max, format, power);
- SameLine(0, g.Style.ItemInnerSpacing.x);
- PopID();
- PopItemWidth();
- v = (void*)((char*)v + type_size);
}
- PopID();
-
- TextUnformatted(label, FindRenderedTextEnd(label));
- EndGroup();
- return value_changed;
-}
-
-bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, float power)
-{
- return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, power);
-}
-
-bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, float power)
-{
- return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, power);
-}
-
-bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, float power)
-{
- return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, power);
-}
-
-bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, float power)
-{
- return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, power);
+ return is_open;
}
-bool ImGui::DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed, float v_min, float v_max, const char* format, const char* format_max, float power)
+void ImGui::EndPopup()
{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
-
- ImGuiContext& g = *GImGui;
- PushID(label);
- BeginGroup();
- PushMultiItemsWidths(2);
-
- bool value_changed = DragFloat("##min", v_current_min, v_speed, (v_min >= v_max) ? -FLT_MAX : v_min, (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max), format, power);
- PopItemWidth();
- SameLine(0, g.Style.ItemInnerSpacing.x);
- value_changed |= DragFloat("##max", v_current_max, v_speed, (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min), (v_min >= v_max) ? FLT_MAX : v_max, format_max ? format_max : format, power);
- PopItemWidth();
- SameLine(0, g.Style.ItemInnerSpacing.x);
+ ImGuiContext& g = *GImGui; (void)g;
+ IM_ASSERT(g.CurrentWindow->Flags & ImGuiWindowFlags_Popup); // Mismatched BeginPopup()/EndPopup() calls
+ IM_ASSERT(g.CurrentPopupStack.Size > 0);
- TextUnformatted(label, FindRenderedTextEnd(label));
- EndGroup();
- PopID();
- return value_changed;
-}
+ // Make all menus and popups wrap around for now, may need to expose that policy.
+ NavMoveRequestTryWrapping(g.CurrentWindow, ImGuiNavMoveFlags_LoopY);
-// NB: v_speed is float to allow adjusting the drag speed with more precision
-bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* format)
-{
- return DragScalar(label, ImGuiDataType_S32, v, v_speed, &v_min, &v_max, format);
+ End();
}
-bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* format)
+// This is a helper to handle the simplest case of associating one named popup to one given widget.
+// You may want to handle this on user side if you have specific needs (e.g. tweaking IsItemHovered() parameters).
+// You can pass a NULL str_id to use the identifier of the last item.
+bool ImGui::BeginPopupContextItem(const char* str_id, int mouse_button)
{
- return DragScalarN(label, ImGuiDataType_S32, v, 2, v_speed, &v_min, &v_max, format);
+ ImGuiWindow* window = GImGui->CurrentWindow;
+ ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict!
+ IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item)
+ if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))
+ OpenPopupEx(id);
+ return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings);
}
-bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* format)
+bool ImGui::BeginPopupContextWindow(const char* str_id, int mouse_button, bool also_over_items)
{
- return DragScalarN(label, ImGuiDataType_S32, v, 3, v_speed, &v_min, &v_max, format);
+ if (!str_id)
+ str_id = "window_context";
+ ImGuiID id = GImGui->CurrentWindow->GetID(str_id);
+ if (IsMouseReleased(mouse_button) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))
+ if (also_over_items || !IsAnyItemHovered())
+ OpenPopupEx(id);
+ return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings);
}
-bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* format)
+bool ImGui::BeginPopupContextVoid(const char* str_id, int mouse_button)
{
- return DragScalarN(label, ImGuiDataType_S32, v, 4, v_speed, &v_min, &v_max, format);
+ if (!str_id)
+ str_id = "void_context";
+ ImGuiID id = GImGui->CurrentWindow->GetID(str_id);
+ if (IsMouseReleased(mouse_button) && !IsWindowHovered(ImGuiHoveredFlags_AnyWindow))
+ OpenPopupEx(id);
+ return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings);
}
-bool ImGui::DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed, int v_min, int v_max, const char* format, const char* format_max)
+ImRect ImGui::GetWindowAllowedExtentRect(ImGuiWindow*)
{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
-
- ImGuiContext& g = *GImGui;
- PushID(label);
- BeginGroup();
- PushMultiItemsWidths(2);
-
- bool value_changed = DragInt("##min", v_current_min, v_speed, (v_min >= v_max) ? INT_MIN : v_min, (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max), format);
- PopItemWidth();
- SameLine(0, g.Style.ItemInnerSpacing.x);
- value_changed |= DragInt("##max", v_current_max, v_speed, (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min), (v_min >= v_max) ? INT_MAX : v_max, format_max ? format_max : format);
- PopItemWidth();
- SameLine(0, g.Style.ItemInnerSpacing.x);
-
- TextUnformatted(label, FindRenderedTextEnd(label));
- EndGroup();
- PopID();
-
- return value_changed;
+ ImVec2 padding = GImGui->Style.DisplaySafeAreaPadding;
+ ImRect r_screen = GetViewportRect();
+ r_screen.Expand(ImVec2((r_screen.GetWidth() > padding.x * 2) ? -padding.x : 0.0f, (r_screen.GetHeight() > padding.y * 2) ? -padding.y : 0.0f));
+ return r_screen;
}
-void ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size)
+// r_avoid = the rectangle to avoid (e.g. for tooltip it is a rectangle around the mouse cursor which we want to avoid. for popups it's a small point around the cursor.)
+// r_outer = the visible area rectangle, minus safe area padding. If our popup size won't fit because of safe area padding we ignore it.
+ImVec2 ImGui::FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_outer, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy)
{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return;
-
- ImGuiContext& g = *GImGui;
- const ImGuiStyle& style = g.Style;
-
- const ImVec2 label_size = CalcTextSize(label, NULL, true);
- if (graph_size.x == 0.0f)
- graph_size.x = CalcItemWidth();
- if (graph_size.y == 0.0f)
- graph_size.y = label_size.y + (style.FramePadding.y * 2);
-
- const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(graph_size.x, graph_size.y));
- const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
- const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0));
- ItemSize(total_bb, style.FramePadding.y);
- if (!ItemAdd(total_bb, 0, &frame_bb))
- return;
- const bool hovered = ItemHoverable(inner_bb, 0);
-
- // Determine scale from values if not specified
- if (scale_min == FLT_MAX || scale_max == FLT_MAX)
- {
- float v_min = FLT_MAX;
- float v_max = -FLT_MAX;
- for (int i = 0; i < values_count; i++)
- {
- const float v = values_getter(data, i);
- v_min = ImMin(v_min, v);
- v_max = ImMax(v_max, v);
- }
- if (scale_min == FLT_MAX)
- scale_min = v_min;
- if (scale_max == FLT_MAX)
- scale_max = v_max;
- }
-
- RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
+ ImVec2 base_pos_clamped = ImClamp(ref_pos, r_outer.Min, r_outer.Max - size);
+ //GImGui->OverlayDrawList.AddRect(r_avoid.Min, r_avoid.Max, IM_COL32(255,0,0,255));
+ //GImGui->OverlayDrawList.AddRect(r_outer.Min, r_outer.Max, IM_COL32(0,255,0,255));
- if (values_count > 0)
+ // Combo Box policy (we want a connecting edge)
+ if (policy == ImGuiPopupPositionPolicy_ComboBox)
{
- int res_w = ImMin((int)graph_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
- int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
-
- // Tooltip on hover
- int v_hovered = -1;
- if (hovered)
- {
- const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f);
- const int v_idx = (int)(t * item_count);
- IM_ASSERT(v_idx >= 0 && v_idx < values_count);
-
- const float v0 = values_getter(data, (v_idx + values_offset) % values_count);
- const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count);
- if (plot_type == ImGuiPlotType_Lines)
- SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx+1, v1);
- else if (plot_type == ImGuiPlotType_Histogram)
- SetTooltip("%d: %8.4g", v_idx, v0);
- v_hovered = v_idx;
- }
-
- const float t_step = 1.0f / (float)res_w;
- const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min));
-
- float v0 = values_getter(data, (0 + values_offset) % values_count);
- float t0 = 0.0f;
- ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) ); // Point in the normalized space of our target rectangle
- float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (-scale_min * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f); // Where does the zero line stands
-
- const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram);
- const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered);
-
- for (int n = 0; n < res_w; n++)
+ const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = { ImGuiDir_Down, ImGuiDir_Right, ImGuiDir_Left, ImGuiDir_Up };
+ for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++)
{
- const float t1 = t0 + t_step;
- const int v1_idx = (int)(t0 * item_count + 0.5f);
- IM_ASSERT(v1_idx >= 0 && v1_idx < values_count);
- const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count);
- const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) );
-
- // NB: Draw calls are merged together by the DrawList system. Still, we should render our batch are lower level to save a bit of CPU.
- ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0);
- ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t));
- if (plot_type == ImGuiPlotType_Lines)
- {
- window->DrawList->AddLine(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);
- }
- else if (plot_type == ImGuiPlotType_Histogram)
- {
- if (pos1.x >= pos0.x + 2.0f)
- pos1.x -= 1.0f;
- window->DrawList->AddRectFilled(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);
- }
-
- t0 = t1;
- tp0 = tp1;
+ const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n];
+ if (n != -1 && dir == *last_dir) // Already tried this direction?
+ continue;
+ ImVec2 pos;
+ if (dir == ImGuiDir_Down) pos = ImVec2(r_avoid.Min.x, r_avoid.Max.y); // Below, Toward Right (default)
+ if (dir == ImGuiDir_Right) pos = ImVec2(r_avoid.Min.x, r_avoid.Min.y - size.y); // Above, Toward Right
+ if (dir == ImGuiDir_Left) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Max.y); // Below, Toward Left
+ if (dir == ImGuiDir_Up) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Min.y - size.y); // Above, Toward Left
+ if (!r_outer.Contains(ImRect(pos, pos + size)))
+ continue;
+ *last_dir = dir;
+ return pos;
}
}
- // Text overlay
- if (overlay_text)
- RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, overlay_text, NULL, NULL, ImVec2(0.5f,0.0f));
-
- if (label_size.x > 0.0f)
- RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
-}
-
-struct ImGuiPlotArrayGetterData
-{
- const float* Values;
- int Stride;
-
- ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; }
-};
-
-static float Plot_ArrayGetter(void* data, int idx)
-{
- ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data;
- const float v = *(const float*)(const void*)((const unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride);
- return v;
-}
-
-void ImGui::PlotLines(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride)
-{
- ImGuiPlotArrayGetterData data(values, stride);
- PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
-}
-
-void ImGui::PlotLines(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size)
-{
- PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
-}
-
-void ImGui::PlotHistogram(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride)
-{
- ImGuiPlotArrayGetterData data(values, stride);
- PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
-}
-
-void ImGui::PlotHistogram(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size)
-{
- PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
-}
-
-// size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size
-void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay)
-{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return;
-
- ImGuiContext& g = *GImGui;
- const ImGuiStyle& style = g.Style;
-
- ImVec2 pos = window->DC.CursorPos;
- ImRect bb(pos, pos + CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y*2.0f));
- ItemSize(bb, style.FramePadding.y);
- if (!ItemAdd(bb, 0))
- return;
-
- // Render
- fraction = ImSaturate(fraction);
- RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
- bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize));
- const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y);
- RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0f, fraction, style.FrameRounding);
-
- // Default displaying the fraction as percentage string, but user can override it
- char overlay_buf[32];
- if (!overlay)
+ // Default popup policy
+ const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = { ImGuiDir_Right, ImGuiDir_Down, ImGuiDir_Up, ImGuiDir_Left };
+ for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++)
{
- ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction*100+0.01f);
- overlay = overlay_buf;
+ const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n];
+ if (n != -1 && dir == *last_dir) // Already tried this direction?
+ continue;
+ float avail_w = (dir == ImGuiDir_Left ? r_avoid.Min.x : r_outer.Max.x) - (dir == ImGuiDir_Right ? r_avoid.Max.x : r_outer.Min.x);
+ float avail_h = (dir == ImGuiDir_Up ? r_avoid.Min.y : r_outer.Max.y) - (dir == ImGuiDir_Down ? r_avoid.Max.y : r_outer.Min.y);
+ if (avail_w < size.x || avail_h < size.y)
+ continue;
+ ImVec2 pos;
+ pos.x = (dir == ImGuiDir_Left) ? r_avoid.Min.x - size.x : (dir == ImGuiDir_Right) ? r_avoid.Max.x : base_pos_clamped.x;
+ pos.y = (dir == ImGuiDir_Up) ? r_avoid.Min.y - size.y : (dir == ImGuiDir_Down) ? r_avoid.Max.y : base_pos_clamped.y;
+ *last_dir = dir;
+ return pos;
}
- ImVec2 overlay_size = CalcTextSize(overlay, NULL);
- if (overlay_size.x > 0.0f)
- RenderTextClipped(ImVec2(ImClamp(fill_br.x + style.ItemSpacing.x, bb.Min.x, bb.Max.x - overlay_size.x - style.ItemInnerSpacing.x), bb.Min.y), bb.Max, overlay, NULL, &overlay_size, ImVec2(0.0f,0.5f), &bb);
+ // Fallback, try to keep within display
+ *last_dir = ImGuiDir_None;
+ ImVec2 pos = ref_pos;
+ pos.x = ImMax(ImMin(pos.x + size.x, r_outer.Max.x) - size.x, r_outer.Min.x);
+ pos.y = ImMax(ImMin(pos.y + size.y, r_outer.Max.y) - size.y, r_outer.Min.y);
+ return pos;
}
-bool ImGui::Checkbox(const char* label, bool* v)
+ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window)
{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
-
ImGuiContext& g = *GImGui;
- const ImGuiStyle& style = g.Style;
- const ImGuiID id = window->GetID(label);
- const ImVec2 label_size = CalcTextSize(label, NULL, true);
-
- const ImRect check_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(label_size.y + style.FramePadding.y*2, label_size.y + style.FramePadding.y*2)); // We want a square shape to we use Y twice
- ItemSize(check_bb, style.FramePadding.y);
-
- ImRect total_bb = check_bb;
- if (label_size.x > 0)
- SameLine(0, style.ItemInnerSpacing.x);
- const ImRect text_bb(window->DC.CursorPos + ImVec2(0,style.FramePadding.y), window->DC.CursorPos + ImVec2(0,style.FramePadding.y) + label_size);
- if (label_size.x > 0)
- {
- ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y);
- total_bb = ImRect(ImMin(check_bb.Min, text_bb.Min), ImMax(check_bb.Max, text_bb.Max));
- }
-
- if (!ItemAdd(total_bb, id))
- return false;
- bool hovered, held;
- bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
- if (pressed)
+ ImRect r_outer = GetWindowAllowedExtentRect(window);
+ if (window->Flags & ImGuiWindowFlags_ChildMenu)
{
- *v = !(*v);
- MarkItemEdited(id);
+ // Child menus typically request _any_ position within the parent menu item, and then our FindBestWindowPosForPopup() function will move the new menu outside the parent bounds.
+ // This is how we end up with child menus appearing (most-commonly) on the right of the parent menu.
+ IM_ASSERT(g.CurrentWindow == window);
+ ImGuiWindow* parent_window = g.CurrentWindowStack[g.CurrentWindowStack.Size - 2];
+ float horizontal_overlap = g.Style.ItemSpacing.x; // We want some overlap to convey the relative depth of each menu (currently the amount of overlap is hard-coded to style.ItemSpacing.x).
+ ImRect r_avoid;
+ if (parent_window->DC.MenuBarAppending)
+ r_avoid = ImRect(-FLT_MAX, parent_window->Pos.y + parent_window->TitleBarHeight(), FLT_MAX, parent_window->Pos.y + parent_window->TitleBarHeight() + parent_window->MenuBarHeight());
+ else
+ r_avoid = ImRect(parent_window->Pos.x + horizontal_overlap, -FLT_MAX, parent_window->Pos.x + parent_window->Size.x - horizontal_overlap - parent_window->ScrollbarSizes.x, FLT_MAX);
+ return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid);
}
-
- RenderNavHighlight(total_bb, id);
- RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding);
- if (*v)
+ if (window->Flags & ImGuiWindowFlags_Popup)
{
- const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight());
- const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f));
- RenderCheckMark(check_bb.Min + ImVec2(pad,pad), GetColorU32(ImGuiCol_CheckMark), check_bb.GetWidth() - pad*2.0f);
+ ImRect r_avoid = ImRect(window->Pos.x - 1, window->Pos.y - 1, window->Pos.x + 1, window->Pos.y + 1);
+ return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid);
}
-
- if (g.LogEnabled)
- LogRenderedText(&text_bb.Min, *v ? "[x]" : "[ ]");
- if (label_size.x > 0.0f)
- RenderText(text_bb.Min, label);
-
- return pressed;
-}
-
-bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value)
-{
- bool v = ((*flags & flags_value) == flags_value);
- bool pressed = Checkbox(label, &v);
- if (pressed)
+ if (window->Flags & ImGuiWindowFlags_Tooltip)
{
- if (v)
- *flags |= flags_value;
+ // Position tooltip (always follows mouse)
+ float sc = g.Style.MouseCursorScale;
+ ImVec2 ref_pos = NavCalcPreferredRefPos();
+ ImRect r_avoid;
+ if (!g.NavDisableHighlight && g.NavDisableMouseHover && !(g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos))
+ r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 16, ref_pos.y + 8);
else
- *flags &= ~flags_value;
+ r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 24 * sc, ref_pos.y + 24 * sc); // FIXME: Hard-coded based on mouse cursor shape expectation. Exact dimension not very important.
+ ImVec2 pos = FindBestWindowPosForPopupEx(ref_pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid);
+ if (window->AutoPosLastDirection == ImGuiDir_None)
+ pos = ref_pos + ImVec2(2, 2); // If there's not enough room, for tooltip we prefer avoiding the cursor at all cost even if it means that part of the tooltip won't be visible.
+ return pos;
}
-
- return pressed;
+ IM_ASSERT(0);
+ return window->Pos;
}
-bool ImGui::RadioButton(const char* label, bool active)
-{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
-
- ImGuiContext& g = *GImGui;
- const ImGuiStyle& style = g.Style;
- const ImGuiID id = window->GetID(label);
- const ImVec2 label_size = CalcTextSize(label, NULL, true);
-
- const ImRect check_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(label_size.y + style.FramePadding.y*2-1, label_size.y + style.FramePadding.y*2-1));
- ItemSize(check_bb, style.FramePadding.y);
-
- ImRect total_bb = check_bb;
- if (label_size.x > 0)
- SameLine(0, style.ItemInnerSpacing.x);
- const ImRect text_bb(window->DC.CursorPos + ImVec2(0, style.FramePadding.y), window->DC.CursorPos + ImVec2(0, style.FramePadding.y) + label_size);
- if (label_size.x > 0)
- {
- ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y);
- total_bb.Add(text_bb);
- }
-
- if (!ItemAdd(total_bb, id))
- return false;
-
- ImVec2 center = check_bb.GetCenter();
- center.x = (float)(int)center.x + 0.5f;
- center.y = (float)(int)center.y + 0.5f;
- const float radius = check_bb.GetHeight() * 0.5f;
-
- bool hovered, held;
- bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
- if (pressed)
- MarkItemEdited(id);
-
- RenderNavHighlight(total_bb, id);
- window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16);
- if (active)
- {
- const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight());
- const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f));
- window->DrawList->AddCircleFilled(center, radius-pad, GetColorU32(ImGuiCol_CheckMark), 16);
- }
-
- if (style.FrameBorderSize > 0.0f)
- {
- window->DrawList->AddCircle(center+ImVec2(1,1), radius, GetColorU32(ImGuiCol_BorderShadow), 16, style.FrameBorderSize);
- window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), 16, style.FrameBorderSize);
- }
-
- if (g.LogEnabled)
- LogRenderedText(&text_bb.Min, active ? "(x)" : "( )");
- if (label_size.x > 0.0f)
- RenderText(text_bb.Min, label);
-
- return pressed;
-}
+//-----------------------------------------------------------------------------
+// NAVIGATION
+//-----------------------------------------------------------------------------
-bool ImGui::RadioButton(const char* label, int* v, int v_button)
+static ImGuiDir inline NavScoreItemGetQuadrant(float dx, float dy)
{
- const bool pressed = RadioButton(label, *v == v_button);
- if (pressed)
- *v = v_button;
- return pressed;
+ if (ImFabs(dx) > ImFabs(dy))
+ return (dx > 0.0f) ? ImGuiDir_Right : ImGuiDir_Left;
+ return (dy > 0.0f) ? ImGuiDir_Down : ImGuiDir_Up;
}
-static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end)
+static float inline NavScoreItemDistInterval(float a0, float a1, float b0, float b1)
{
- int line_count = 0;
- const char* s = text_begin;
- while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding
- if (c == '\n')
- line_count++;
- s--;
- if (s[0] != '\n' && s[0] != '\r')
- line_count++;
- *out_text_end = s;
- return line_count;
+ if (a1 < b0)
+ return a1 - b0;
+ if (b1 < a0)
+ return a0 - b1;
+ return 0.0f;
}
-static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line)
+static void inline NavClampRectToVisibleAreaForMoveDir(ImGuiDir move_dir, ImRect& r, const ImRect& clip_rect)
{
- ImFont* font = GImGui->Font;
- const float line_height = GImGui->FontSize;
- const float scale = line_height / font->FontSize;
-
- ImVec2 text_size = ImVec2(0,0);
- float line_width = 0.0f;
-
- const ImWchar* s = text_begin;
- while (s < text_end)
+ if (move_dir == ImGuiDir_Left || move_dir == ImGuiDir_Right)
{
- unsigned int c = (unsigned int)(*s++);
- if (c == '\n')
- {
- text_size.x = ImMax(text_size.x, line_width);
- text_size.y += line_height;
- line_width = 0.0f;
- if (stop_on_new_line)
- break;
- continue;
- }
- if (c == '\r')
- continue;
-
- const float char_width = font->GetCharAdvance((unsigned short)c) * scale;
- line_width += char_width;
+ r.Min.y = ImClamp(r.Min.y, clip_rect.Min.y, clip_rect.Max.y);
+ r.Max.y = ImClamp(r.Max.y, clip_rect.Min.y, clip_rect.Max.y);
}
-
- if (text_size.x < line_width)
- text_size.x = line_width;
-
- if (out_offset)
- *out_offset = ImVec2(line_width, text_size.y + line_height); // offset allow for the possibility of sitting after a trailing \n
-
- if (line_width > 0 || text_size.y == 0.0f) // whereas size.y will ignore the trailing \n
- text_size.y += line_height;
-
- if (remaining)
- *remaining = s;
-
- return text_size;
-}
-
-// Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, wchar characters. InputText converts between UTF-8 and wchar)
-namespace ImGuiStb
-{
-
-static int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return obj->CurLenW; }
-static ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx) { return obj->TextW[idx]; }
-static float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { ImWchar c = obj->TextW[line_start_idx+char_idx]; if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; return GImGui->Font->GetCharAdvance(c) * (GImGui->FontSize / GImGui->Font->FontSize); }
-static int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x10000 ? 0 : key; }
-static ImWchar STB_TEXTEDIT_NEWLINE = '\n';
-static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx)
-{
- const ImWchar* text = obj->TextW.Data;
- const ImWchar* text_remaining = NULL;
- const ImVec2 size = InputTextCalcTextSizeW(text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true);
- r->x0 = 0.0f;
- r->x1 = size.x;
- r->baseline_y_delta = size.y;
- r->ymin = 0.0f;
- r->ymax = size.y;
- r->num_chars = (int)(text_remaining - (text + line_start_idx));
-}
-
-static bool is_separator(unsigned int c) { return ImCharIsBlankW(c) || c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|'; }
-static int is_word_boundary_from_right(STB_TEXTEDIT_STRING* obj, int idx) { return idx > 0 ? (is_separator( obj->TextW[idx-1] ) && !is_separator( obj->TextW[idx] ) ) : 1; }
-static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; }
-#ifdef __APPLE__ // FIXME: Move setting to IO structure
-static int is_word_boundary_from_left(STB_TEXTEDIT_STRING* obj, int idx) { return idx > 0 ? (!is_separator( obj->TextW[idx-1] ) && is_separator( obj->TextW[idx] ) ) : 1; }
-static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; }
-#else
-static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; }
-#endif
-#define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h
-#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL
-
-static void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int pos, int n)
-{
- ImWchar* dst = obj->TextW.Data + pos;
-
- // We maintain our buffer length in both UTF-8 and wchar formats
- obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n);
- obj->CurLenW -= n;
-
- // Offset remaining text
- const ImWchar* src = obj->TextW.Data + pos + n;
- while (ImWchar c = *src++)
- *dst++ = c;
- *dst = '\0';
-}
-
-static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int pos, const ImWchar* new_text, int new_text_len)
-{
- const bool is_resizable = (obj->UserFlags & ImGuiInputTextFlags_CallbackResize) != 0;
- const int text_len = obj->CurLenW;
- IM_ASSERT(pos <= text_len);
-
- const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len);
- if (!is_resizable && (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufCapacityA))
- return false;
-
- // Grow internal buffer if needed
- if (new_text_len + text_len + 1 > obj->TextW.Size)
+ else
{
- if (!is_resizable)
- return false;
- IM_ASSERT(text_len < obj->TextW.Size);
- obj->TextW.resize(text_len + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1);
+ r.Min.x = ImClamp(r.Min.x, clip_rect.Min.x, clip_rect.Max.x);
+ r.Max.x = ImClamp(r.Max.x, clip_rect.Min.x, clip_rect.Max.x);
}
-
- ImWchar* text = obj->TextW.Data;
- if (pos != text_len)
- memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar));
- memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar));
-
- obj->CurLenW += new_text_len;
- obj->CurLenA += new_text_len_utf8;
- obj->TextW[obj->CurLenW] = '\0';
-
- return true;
}
-// We don't use an enum so we can build even with conflicting symbols (if another user of stb_textedit.h leak their STB_TEXTEDIT_K_* symbols)
-#define STB_TEXTEDIT_K_LEFT 0x10000 // keyboard input to move cursor left
-#define STB_TEXTEDIT_K_RIGHT 0x10001 // keyboard input to move cursor right
-#define STB_TEXTEDIT_K_UP 0x10002 // keyboard input to move cursor up
-#define STB_TEXTEDIT_K_DOWN 0x10003 // keyboard input to move cursor down
-#define STB_TEXTEDIT_K_LINESTART 0x10004 // keyboard input to move cursor to start of line
-#define STB_TEXTEDIT_K_LINEEND 0x10005 // keyboard input to move cursor to end of line
-#define STB_TEXTEDIT_K_TEXTSTART 0x10006 // keyboard input to move cursor to start of text
-#define STB_TEXTEDIT_K_TEXTEND 0x10007 // keyboard input to move cursor to end of text
-#define STB_TEXTEDIT_K_DELETE 0x10008 // keyboard input to delete selection or character under cursor
-#define STB_TEXTEDIT_K_BACKSPACE 0x10009 // keyboard input to delete selection or character left of cursor
-#define STB_TEXTEDIT_K_UNDO 0x1000A // keyboard input to perform undo
-#define STB_TEXTEDIT_K_REDO 0x1000B // keyboard input to perform redo
-#define STB_TEXTEDIT_K_WORDLEFT 0x1000C // keyboard input to move cursor left one word
-#define STB_TEXTEDIT_K_WORDRIGHT 0x1000D // keyboard input to move cursor right one word
-#define STB_TEXTEDIT_K_SHIFT 0x20000
-
-#define STB_TEXTEDIT_IMPLEMENTATION
-#include "stb_textedit.h"
-
-}
-
-void ImGuiInputTextState::OnKeyPressed(int key)
-{
- stb_textedit_key(this, &StbState, key);
- CursorFollow = true;
- CursorAnimReset();
-}
-
-ImGuiInputTextCallbackData::ImGuiInputTextCallbackData()
-{
- memset(this, 0, sizeof(*this));
-}
-
-// Public API to manipulate UTF-8 text
-// We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar)
-// FIXME: The existence of this rarely exercised code path is a bit of a nuisance.
-void ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count)
-{
- IM_ASSERT(pos + bytes_count <= BufTextLen);
- char* dst = Buf + pos;
- const char* src = Buf + pos + bytes_count;
- while (char c = *src++)
- *dst++ = c;
- *dst = '\0';
-
- if (CursorPos + bytes_count >= pos)
- CursorPos -= bytes_count;
- else if (CursorPos >= pos)
- CursorPos = pos;
- SelectionStart = SelectionEnd = CursorPos;
- BufDirty = true;
- BufTextLen -= bytes_count;
-}
-
-void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end)
+// Scoring function for directional navigation. Based on https://gist.github.com/rygorous/6981057
+static bool NavScoreItem(ImGuiNavMoveResult* result, ImRect cand)
{
- const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0;
- const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text);
- if (new_text_len + BufTextLen >= BufSize)
- {
- if (!is_resizable)
- return;
-
- // Contrary to STB_TEXTEDIT_INSERTCHARS() this is working in the UTF8 buffer, hence the midly similar code (until we remove the U16 buffer alltogether!)
- ImGuiContext& g = *GImGui;
- ImGuiInputTextState* edit_state = &g.InputTextState;
- IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID);
- IM_ASSERT(Buf == edit_state->TempBuffer.Data);
- int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1;
- edit_state->TempBuffer.reserve(new_buf_size + 1);
- Buf = edit_state->TempBuffer.Data;
- BufSize = edit_state->BufCapacityA = new_buf_size;
- }
-
- if (BufTextLen != pos)
- memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos));
- memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char));
- Buf[BufTextLen + new_text_len] = '\0';
-
- if (CursorPos >= pos)
- CursorPos += new_text_len;
- SelectionStart = SelectionEnd = CursorPos;
- BufDirty = true;
- BufTextLen += new_text_len;
-}
+ ImGuiContext& g = *GImGui;
+ ImGuiWindow* window = g.CurrentWindow;
+ if (g.NavLayer != window->DC.NavLayerCurrent)
+ return false;
-// Return false to discard a character.
-static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
-{
- unsigned int c = *p_char;
+ const ImRect& curr = g.NavScoringRectScreen; // Current modified source rect (NB: we've applied Max.x = Min.x in NavUpdate() to inhibit the effect of having varied item width)
+ g.NavScoringCount++;
- if (c < 128 && c != ' ' && !isprint((int)(c & 0xFF)))
+ // When entering through a NavFlattened border, we consider child window items as fully clipped for scoring
+ if (window->ParentWindow == g.NavWindow)
{
- bool pass = false;
- pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline));
- pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput));
- if (!pass)
+ IM_ASSERT((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened);
+ if (!window->ClipRect.Contains(cand))
return false;
+ cand.ClipWithFull(window->ClipRect); // This allows the scored item to not overlap other candidates in the parent window
}
- if (c >= 0xE000 && c <= 0xF8FF) // Filter private Unicode range. I don't imagine anybody would want to input them. GLFW on OSX seems to send private characters for special keys like arrow keys.
- return false;
-
- if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific))
- {
- if (flags & ImGuiInputTextFlags_CharsDecimal)
- if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/'))
- return false;
-
- if (flags & ImGuiInputTextFlags_CharsScientific)
- if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E'))
- return false;
-
- if (flags & ImGuiInputTextFlags_CharsHexadecimal)
- if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F'))
- return false;
+ // We perform scoring on items bounding box clipped by the current clipping rectangle on the other axis (clipping on our movement axis would give us equal scores for all clipped items)
+ // For example, this ensure that items in one column are not reached when moving vertically from items in another column.
+ NavClampRectToVisibleAreaForMoveDir(g.NavMoveClipDir, cand, window->ClipRect);
- if (flags & ImGuiInputTextFlags_CharsUppercase)
- if (c >= 'a' && c <= 'z')
- *p_char = (c += (unsigned int)('A'-'a'));
+ // Compute distance between boxes
+ // FIXME-NAV: Introducing biases for vertical navigation, needs to be removed.
+ float dbx = NavScoreItemDistInterval(cand.Min.x, cand.Max.x, curr.Min.x, curr.Max.x);
+ float dby = NavScoreItemDistInterval(ImLerp(cand.Min.y, cand.Max.y, 0.2f), ImLerp(cand.Min.y, cand.Max.y, 0.8f), ImLerp(curr.Min.y, curr.Max.y, 0.2f), ImLerp(curr.Min.y, curr.Max.y, 0.8f)); // Scale down on Y to keep using box-distance for vertically touching items
+ if (dby != 0.0f && dbx != 0.0f)
+ dbx = (dbx/1000.0f) + ((dbx > 0.0f) ? +1.0f : -1.0f);
+ float dist_box = ImFabs(dbx) + ImFabs(dby);
- if (flags & ImGuiInputTextFlags_CharsNoBlank)
- if (ImCharIsBlankW(c))
- return false;
- }
+ // Compute distance between centers (this is off by a factor of 2, but we only compare center distances with each other so it doesn't matter)
+ float dcx = (cand.Min.x + cand.Max.x) - (curr.Min.x + curr.Max.x);
+ float dcy = (cand.Min.y + cand.Max.y) - (curr.Min.y + curr.Max.y);
+ float dist_center = ImFabs(dcx) + ImFabs(dcy); // L1 metric (need this for our connectedness guarantee)
- if (flags & ImGuiInputTextFlags_CallbackCharFilter)
+ // Determine which quadrant of 'curr' our candidate item 'cand' lies in based on distance
+ ImGuiDir quadrant;
+ float dax = 0.0f, day = 0.0f, dist_axial = 0.0f;
+ if (dbx != 0.0f || dby != 0.0f)
{
- ImGuiInputTextCallbackData callback_data;
- memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData));
- callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter;
- callback_data.EventChar = (ImWchar)c;
- callback_data.Flags = flags;
- callback_data.UserData = user_data;
- if (callback(&callback_data) != 0)
- return false;
- *p_char = callback_data.EventChar;
- if (!callback_data.EventChar)
- return false;
+ // For non-overlapping boxes, use distance between boxes
+ dax = dbx;
+ day = dby;
+ dist_axial = dist_box;
+ quadrant = NavScoreItemGetQuadrant(dbx, dby);
}
-
- return true;
-}
-
-// Edit a string of text
-// - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!".
-// This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match
-// Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator.
-// - When active, hold on a privately held copy of the text (and apply back to 'buf'). So changing 'buf' while the InputText is active has no effect.
-// - If you want to use ImGui::InputText() with std::string, see misc/stl/imgui_stl.h
-// (FIXME: Rather messy function partly because we are doing UTF8 > u16 > UTF8 conversions on the go to more easily handle stb_textedit calls. Ideally we should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188)
-bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* callback_user_data)
-{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
-
- IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys)
- IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key)
-
- ImGuiContext& g = *GImGui;
- const ImGuiIO& io = g.IO;
- const ImGuiStyle& style = g.Style;
-
- const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;
- const bool is_editable = (flags & ImGuiInputTextFlags_ReadOnly) == 0;
- const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;
- const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0;
- const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0;
- if (is_resizable)
- IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag!
-
- if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope,
- BeginGroup();
- const ImGuiID id = window->GetID(label);
- const ImVec2 label_size = CalcTextSize(label, NULL, true);
- ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), (is_multiline ? GetTextLineHeight() * 8.0f : label_size.y) + style.FramePadding.y*2.0f); // Arbitrary default of 8 lines high for multi-line
- const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
- const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? (style.ItemInnerSpacing.x + label_size.x) : 0.0f, 0.0f));
-
- ImGuiWindow* draw_window = window;
- if (is_multiline)
- {
- ItemAdd(total_bb, id, &frame_bb);
- if (!BeginChildFrame(id, frame_bb.GetSize()))
- {
- EndChildFrame();
- EndGroup();
- return false;
- }
- draw_window = GetCurrentWindow();
- draw_window->DC.NavLayerActiveMaskNext |= draw_window->DC.NavLayerCurrentMask; // This is to ensure that EndChild() will display a navigation highlight
- size.x -= draw_window->ScrollbarSizes.x;
+ else if (dcx != 0.0f || dcy != 0.0f)
+ {
+ // For overlapping boxes with different centers, use distance between centers
+ dax = dcx;
+ day = dcy;
+ dist_axial = dist_center;
+ quadrant = NavScoreItemGetQuadrant(dcx, dcy);
}
else
{
- ItemSize(total_bb, style.FramePadding.y);
- if (!ItemAdd(total_bb, id, &frame_bb))
- return false;
+ // Degenerate case: two overlapping buttons with same center, break ties arbitrarily (note that LastItemId here is really the _previous_ item order, but it doesn't matter)
+ quadrant = (window->DC.LastItemId < g.NavId) ? ImGuiDir_Left : ImGuiDir_Right;
}
- const bool hovered = ItemHoverable(frame_bb, id);
- if (hovered)
- g.MouseCursor = ImGuiMouseCursor_TextInput;
- // Password pushes a temporary font with only a fallback glyph
- if (is_password)
+#if IMGUI_DEBUG_NAV_SCORING
+ char buf[128];
+ if (ImGui::IsMouseHoveringRect(cand.Min, cand.Max))
{
- const ImFontGlyph* glyph = g.Font->FindGlyph('*');
- ImFont* password_font = &g.InputTextPasswordFont;
- password_font->FontSize = g.Font->FontSize;
- password_font->Scale = g.Font->Scale;
- password_font->DisplayOffset = g.Font->DisplayOffset;
- password_font->Ascent = g.Font->Ascent;
- password_font->Descent = g.Font->Descent;
- password_font->ContainerAtlas = g.Font->ContainerAtlas;
- password_font->FallbackGlyph = glyph;
- password_font->FallbackAdvanceX = glyph->AdvanceX;
- IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty());
- PushFont(password_font);
+ ImFormatString(buf, IM_ARRAYSIZE(buf), "dbox (%.2f,%.2f->%.4f)\ndcen (%.2f,%.2f->%.4f)\nd (%.2f,%.2f->%.4f)\nnav %c, quadrant %c", dbx, dby, dist_box, dcx, dcy, dist_center, dax, day, dist_axial, "WENS"[g.NavMoveDir], "WENS"[quadrant]);
+ ImDrawList* draw_list = ImGui::GetOverlayDrawList();
+ draw_list->AddRect(curr.Min, curr.Max, IM_COL32(255,200,0,100));
+ draw_list->AddRect(cand.Min, cand.Max, IM_COL32(255,255,0,200));
+ draw_list->AddRectFilled(cand.Max-ImVec2(4,4), cand.Max+ImGui::CalcTextSize(buf)+ImVec2(4,4), IM_COL32(40,0,0,150));
+ draw_list->AddText(g.IO.FontDefault, 13.0f, cand.Max, ~0U, buf);
}
-
- // NB: we are only allowed to access 'edit_state' if we are the active widget.
- ImGuiInputTextState& edit_state = g.InputTextState;
-
- const bool focus_requested = FocusableItemRegister(window, id, (flags & (ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_AllowTabInput)) == 0); // Using completion callback disable keyboard tabbing
- const bool focus_requested_by_code = focus_requested && (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent);
- const bool focus_requested_by_tab = focus_requested && !focus_requested_by_code;
-
- const bool user_clicked = hovered && io.MouseClicked[0];
- const bool user_scrolled = is_multiline && g.ActiveId == 0 && edit_state.ID == id && g.ActiveIdPreviousFrame == draw_window->GetIDNoKeepAlive("#SCROLLY");
- const bool user_nav_input_start = (g.ActiveId != id) && ((g.NavInputId == id) || (g.NavActivateId == id && g.NavInputSource == ImGuiInputSource_NavKeyboard));
-
- bool clear_active_id = false;
-
- bool select_all = (g.ActiveId != id) && ((flags & ImGuiInputTextFlags_AutoSelectAll) != 0 || user_nav_input_start) && (!is_multiline);
- if (focus_requested || user_clicked || user_scrolled || user_nav_input_start)
+ else if (g.IO.KeyCtrl) // Hold to preview score in matching quadrant. Press C to rotate.
{
- if (g.ActiveId != id)
+ if (IsKeyPressedMap(ImGuiKey_C)) { g.NavMoveDirLast = (ImGuiDir)((g.NavMoveDirLast + 1) & 3); g.IO.KeysDownDuration[g.IO.KeyMap[ImGuiKey_C]] = 0.01f; }
+ if (quadrant == g.NavMoveDir)
{
- // Start edition
- // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar)
- // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode)
- const int prev_len_w = edit_state.CurLenW;
- const int init_buf_len = (int)strlen(buf);
- edit_state.TextW.resize(buf_size+1); // wchar count <= UTF-8 count. we use +1 to make sure that .Data isn't NULL so it doesn't crash.
- edit_state.InitialText.resize(init_buf_len + 1); // UTF-8. we use +1 to make sure that .Data isn't NULL so it doesn't crash.
- memcpy(edit_state.InitialText.Data, buf, init_buf_len + 1);
- const char* buf_end = NULL;
- edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, buf_size, buf, NULL, &buf_end);
- edit_state.CurLenA = (int)(buf_end - buf); // We can't get the result from ImStrncpy() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8.
- edit_state.CursorAnimReset();
-
- // Preserve cursor position and undo/redo stack if we come back to same widget
- // FIXME: We should probably compare the whole buffer to be on the safety side. Comparing buf (utf8) and edit_state.Text (wchar).
- const bool recycle_state = (edit_state.ID == id) && (prev_len_w == edit_state.CurLenW);
- if (recycle_state)
- {
- // Recycle existing cursor/selection/undo stack but clamp position
- // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler.
- edit_state.CursorClamp();
- }
- else
- {
- edit_state.ID = id;
- edit_state.ScrollX = 0.0f;
- stb_textedit_initialize_state(&edit_state.StbState, !is_multiline);
- if (!is_multiline && focus_requested_by_code)
- select_all = true;
- }
- if (flags & ImGuiInputTextFlags_AlwaysInsertMode)
- edit_state.StbState.insert_mode = true;
- if (!is_multiline && (focus_requested_by_tab || (user_clicked && io.KeyCtrl)))
- select_all = true;
+ ImFormatString(buf, IM_ARRAYSIZE(buf), "%.0f/%.0f", dist_box, dist_center);
+ ImDrawList* draw_list = ImGui::GetOverlayDrawList();
+ draw_list->AddRectFilled(cand.Min, cand.Max, IM_COL32(255, 0, 0, 200));
+ draw_list->AddText(g.IO.FontDefault, 13.0f, cand.Min, IM_COL32(255, 255, 255, 255), buf);
}
- SetActiveID(id, window);
- SetFocusID(id, window);
- FocusWindow(window);
- if (!is_multiline && !(flags & ImGuiInputTextFlags_CallbackHistory))
- g.ActiveIdAllowNavDirFlags |= ((1 << ImGuiDir_Up) | (1 << ImGuiDir_Down));
- }
- else if (io.MouseClicked[0])
- {
- // Release focus when we click outside
- clear_active_id = true;
}
+ #endif
- bool value_changed = false;
- bool enter_pressed = false;
- int backup_current_text_length = 0;
-
- if (g.ActiveId == id)
+ // Is it in the quadrant we're interesting in moving to?
+ bool new_best = false;
+ if (quadrant == g.NavMoveDir)
{
- if (!is_editable && !g.ActiveIdIsJustActivated)
- {
- // When read-only we always use the live data passed to the function
- edit_state.TextW.resize(buf_size+1);
- const char* buf_end = NULL;
- edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, edit_state.TextW.Size, buf, NULL, &buf_end);
- edit_state.CurLenA = (int)(buf_end - buf);
- edit_state.CursorClamp();
- }
-
- backup_current_text_length = edit_state.CurLenA;
- edit_state.BufCapacityA = buf_size;
- edit_state.UserFlags = flags;
- edit_state.UserCallback = callback;
- edit_state.UserCallbackData = callback_user_data;
-
- // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget.
- // Down the line we should have a cleaner library-wide concept of Selected vs Active.
- g.ActiveIdAllowOverlap = !io.MouseDown[0];
- g.WantTextInputNextFrame = 1;
-
- // Edit in progress
- const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + edit_state.ScrollX;
- const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y - style.FramePadding.y) : (g.FontSize*0.5f));
-
- const bool is_osx = io.ConfigMacOSXBehaviors;
- if (select_all || (hovered && !is_osx && io.MouseDoubleClicked[0]))
- {
- edit_state.SelectAll();
- edit_state.SelectedAllMouseLock = true;
- }
- else if (hovered && is_osx && io.MouseDoubleClicked[0])
- {
- // Double-click select a word only, OS X style (by simulating keystrokes)
- edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT);
- edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
- }
- else if (io.MouseClicked[0] && !edit_state.SelectedAllMouseLock)
- {
- if (hovered)
- {
- stb_textedit_click(&edit_state, &edit_state.StbState, mouse_x, mouse_y);
- edit_state.CursorAnimReset();
- }
- }
- else if (io.MouseDown[0] && !edit_state.SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f))
- {
- stb_textedit_drag(&edit_state, &edit_state.StbState, mouse_x, mouse_y);
- edit_state.CursorAnimReset();
- edit_state.CursorFollow = true;
- }
- if (edit_state.SelectedAllMouseLock && !io.MouseDown[0])
- edit_state.SelectedAllMouseLock = false;
-
- if (io.InputCharacters[0])
- {
- // Process text input (before we check for Return because using some IME will effectively send a Return?)
- // We ignore CTRL inputs, but need to allow ALT+CTRL as some keyboards (e.g. German) use AltGR (which _is_ Alt+Ctrl) to input certain characters.
- bool ignore_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeySuper);
- if (!ignore_inputs && is_editable && !user_nav_input_start)
- for (int n = 0; n < IM_ARRAYSIZE(io.InputCharacters) && io.InputCharacters[n]; n++)
- {
- // Insert character if they pass filtering
- unsigned int c = (unsigned int)io.InputCharacters[n];
- if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))
- edit_state.OnKeyPressed((int)c);
- }
-
- // Consume characters
- memset(g.IO.InputCharacters, 0, sizeof(g.IO.InputCharacters));
- }
- }
-
- bool cancel_edit = false;
- if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id)
- {
- // Handle key-presses
- const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);
- const bool is_osx = io.ConfigMacOSXBehaviors;
- const bool is_shortcut_key = (is_osx ? (io.KeySuper && !io.KeyCtrl) : (io.KeyCtrl && !io.KeySuper)) && !io.KeyAlt && !io.KeyShift; // OS X style: Shortcuts using Cmd/Super instead of Ctrl
- const bool is_osx_shift_shortcut = is_osx && io.KeySuper && io.KeyShift && !io.KeyCtrl && !io.KeyAlt;
- const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl
- const bool is_startend_key_down = is_osx && io.KeySuper && !io.KeyCtrl && !io.KeyAlt; // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End
- const bool is_ctrl_key_only = io.KeyCtrl && !io.KeyShift && !io.KeyAlt && !io.KeySuper;
- const bool is_shift_key_only = io.KeyShift && !io.KeyCtrl && !io.KeyAlt && !io.KeySuper;
-
- const bool is_cut = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Delete))) && is_editable && !is_password && (!is_multiline || edit_state.HasSelection());
- const bool is_copy = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_C)) || (is_ctrl_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && !is_password && (!is_multiline || edit_state.HasSelection());
- const bool is_paste = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && is_editable;
- const bool is_undo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Z)) && is_editable && is_undoable);
- const bool is_redo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Y)) || (is_osx_shift_shortcut && IsKeyPressedMap(ImGuiKey_Z))) && is_editable && is_undoable;
-
- if (IsKeyPressedMap(ImGuiKey_LeftArrow)) { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); }
- else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); }
- else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); }
- else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); }
- else if (IsKeyPressedMap(ImGuiKey_Home)) { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); }
- else if (IsKeyPressedMap(ImGuiKey_End)) { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); }
- else if (IsKeyPressedMap(ImGuiKey_Delete) && is_editable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); }
- else if (IsKeyPressedMap(ImGuiKey_Backspace) && is_editable)
- {
- if (!edit_state.HasSelection())
- {
- if (is_wordmove_key_down) edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT|STB_TEXTEDIT_K_SHIFT);
- else if (is_osx && io.KeySuper && !io.KeyAlt && !io.KeyCtrl) edit_state.OnKeyPressed(STB_TEXTEDIT_K_LINESTART|STB_TEXTEDIT_K_SHIFT);
- }
- edit_state.OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);
- }
- else if (IsKeyPressedMap(ImGuiKey_Enter))
- {
- bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0;
- if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl))
- {
- enter_pressed = clear_active_id = true;
- }
- else if (is_editable)
- {
- unsigned int c = '\n'; // Insert new line
- if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))
- edit_state.OnKeyPressed((int)c);
- }
- }
- else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !io.KeyCtrl && !io.KeyShift && !io.KeyAlt && is_editable)
- {
- unsigned int c = '\t'; // Insert TAB
- if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))
- edit_state.OnKeyPressed((int)c);
- }
- else if (IsKeyPressedMap(ImGuiKey_Escape))
- {
- clear_active_id = cancel_edit = true;
- }
- else if (is_undo || is_redo)
- {
- edit_state.OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO);
- edit_state.ClearSelection();
- }
- else if (is_shortcut_key && IsKeyPressedMap(ImGuiKey_A))
+ // Does it beat the current best candidate?
+ if (dist_box < result->DistBox)
{
- edit_state.SelectAll();
- edit_state.CursorFollow = true;
+ result->DistBox = dist_box;
+ result->DistCenter = dist_center;
+ return true;
}
- else if (is_cut || is_copy)
+ if (dist_box == result->DistBox)
{
- // Cut, Copy
- if (io.SetClipboardTextFn)
- {
- const int ib = edit_state.HasSelection() ? ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end) : 0;
- const int ie = edit_state.HasSelection() ? ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end) : edit_state.CurLenW;
- edit_state.TempBuffer.resize((ie-ib) * 4 + 1);
- ImTextStrToUtf8(edit_state.TempBuffer.Data, edit_state.TempBuffer.Size, edit_state.TextW.Data+ib, edit_state.TextW.Data+ie);
- SetClipboardText(edit_state.TempBuffer.Data);
- }
- if (is_cut)
+ // Try using distance between center points to break ties
+ if (dist_center < result->DistCenter)
{
- if (!edit_state.HasSelection())
- edit_state.SelectAll();
- edit_state.CursorFollow = true;
- stb_textedit_cut(&edit_state, &edit_state.StbState);
+ result->DistCenter = dist_center;
+ new_best = true;
}
- }
- else if (is_paste)
- {
- if (const char* clipboard = GetClipboardText())
+ else if (dist_center == result->DistCenter)
{
- // Filter pasted buffer
- const int clipboard_len = (int)strlen(clipboard);
- ImWchar* clipboard_filtered = (ImWchar*)ImGui::MemAlloc((clipboard_len+1) * sizeof(ImWchar));
- int clipboard_filtered_len = 0;
- for (const char* s = clipboard; *s; )
- {
- unsigned int c;
- s += ImTextCharFromUtf8(&c, s, NULL);
- if (c == 0)
- break;
- if (c >= 0x10000 || !InputTextFilterCharacter(&c, flags, callback, callback_user_data))
- continue;
- clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c;
- }
- clipboard_filtered[clipboard_filtered_len] = 0;
- if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation
- {
- stb_textedit_paste(&edit_state, &edit_state.StbState, clipboard_filtered, clipboard_filtered_len);
- edit_state.CursorFollow = true;
- }
- ImGui::MemFree(clipboard_filtered);
+ // Still tied! we need to be extra-careful to make sure everything gets linked properly. We consistently break ties by symbolically moving "later" items
+ // (with higher index) to the right/downwards by an infinitesimal amount since we the current "best" button already (so it must have a lower index),
+ // this is fairly easy. This rule ensures that all buttons with dx==dy==0 will end up being linked in order of appearance along the x axis.
+ if (((g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) ? dby : dbx) < 0.0f) // moving bj to the right/down decreases distance
+ new_best = true;
}
}
}
- if (g.ActiveId == id)
- {
- const char* apply_new_text = NULL;
- int apply_new_text_length = 0;
- if (cancel_edit)
- {
- // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents.
- if (is_editable && strcmp(buf, edit_state.InitialText.Data) != 0)
- {
- apply_new_text = edit_state.InitialText.Data;
- apply_new_text_length = edit_state.InitialText.Size - 1;
- }
- }
-
- // When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer before clearing ActiveId, even though strictly speaking it wasn't modified on this frame.
- // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail. Also this allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage.
- bool apply_edit_back_to_user_buffer = !cancel_edit || (enter_pressed && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0);
- if (apply_edit_back_to_user_buffer)
- {
- // Apply new value immediately - copy modified buffer back
- // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer
- // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect.
- // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks.
- if (is_editable)
+ // Axial check: if 'curr' has no link at all in some direction and 'cand' lies roughly in that direction, add a tentative link. This will only be kept if no "real" matches
+ // are found, so it only augments the graph produced by the above method using extra links. (important, since it doesn't guarantee strong connectedness)
+ // This is just to avoid buttons having no links in a particular direction when there's a suitable neighbor. you get good graphs without this too.
+ // 2017/09/29: FIXME: This now currently only enabled inside menu bars, ideally we'd disable it everywhere. Menus in particular need to catch failure. For general navigation it feels awkward.
+ // Disabling it may lead to disconnected graphs when nodes are very spaced out on different axis. Perhaps consider offering this as an option?
+ if (result->DistBox == FLT_MAX && dist_axial < result->DistAxial) // Check axial match
+ if (g.NavLayer == 1 && !(g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
+ if ((g.NavMoveDir == ImGuiDir_Left && dax < 0.0f) || (g.NavMoveDir == ImGuiDir_Right && dax > 0.0f) || (g.NavMoveDir == ImGuiDir_Up && day < 0.0f) || (g.NavMoveDir == ImGuiDir_Down && day > 0.0f))
{
- edit_state.TempBuffer.resize(edit_state.TextW.Size * 4 + 1);
- ImTextStrToUtf8(edit_state.TempBuffer.Data, edit_state.TempBuffer.Size, edit_state.TextW.Data, NULL);
+ result->DistAxial = dist_axial;
+ new_best = true;
}
- // User callback
- if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackAlways)) != 0)
- {
- IM_ASSERT(callback != NULL);
+ return new_best;
+}
- // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment.
- ImGuiInputTextFlags event_flag = 0;
- ImGuiKey event_key = ImGuiKey_COUNT;
- if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && IsKeyPressedMap(ImGuiKey_Tab))
- {
- event_flag = ImGuiInputTextFlags_CallbackCompletion;
- event_key = ImGuiKey_Tab;
- }
- else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_UpArrow))
- {
- event_flag = ImGuiInputTextFlags_CallbackHistory;
- event_key = ImGuiKey_UpArrow;
- }
- else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_DownArrow))
- {
- event_flag = ImGuiInputTextFlags_CallbackHistory;
- event_key = ImGuiKey_DownArrow;
- }
- else if (flags & ImGuiInputTextFlags_CallbackAlways)
- event_flag = ImGuiInputTextFlags_CallbackAlways;
+// 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)
+{
+ ImGuiContext& g = *GImGui;
+ //if (!g.IO.NavActive) // [2017/10/06] Removed this possibly redundant test but I am not sure of all the side-effects yet. Some of the feature here will need to work regardless of using a _NoNavInputs flag.
+ // return;
- if (event_flag)
- {
- ImGuiInputTextCallbackData callback_data;
- memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData));
- callback_data.EventFlag = event_flag;
- callback_data.Flags = flags;
- callback_data.UserData = callback_user_data;
-
- callback_data.EventKey = event_key;
- callback_data.Buf = edit_state.TempBuffer.Data;
- callback_data.BufTextLen = edit_state.CurLenA;
- callback_data.BufSize = edit_state.BufCapacityA;
- callback_data.BufDirty = false;
-
- // We have to convert from wchar-positions to UTF-8-positions, which can be pretty slow (an incentive to ditch the ImWchar buffer, see https://github.com/nothings/stb/issues/188)
- ImWchar* text = edit_state.TextW.Data;
- const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.cursor);
- const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_start);
- const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_end);
-
- // Call user code
- callback(&callback_data);
-
- // Read back what user may have modified
- IM_ASSERT(callback_data.Buf == edit_state.TempBuffer.Data); // Invalid to modify those fields
- IM_ASSERT(callback_data.BufSize == edit_state.BufCapacityA);
- IM_ASSERT(callback_data.Flags == flags);
- if (callback_data.CursorPos != utf8_cursor_pos) { edit_state.StbState.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); edit_state.CursorFollow = true; }
- if (callback_data.SelectionStart != utf8_selection_start) { edit_state.StbState.select_start = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); }
- if (callback_data.SelectionEnd != utf8_selection_end) { edit_state.StbState.select_end = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); }
- if (callback_data.BufDirty)
- {
- 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)
- edit_state.TextW.resize(edit_state.TextW.Size + (callback_data.BufTextLen - backup_current_text_length));
- edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, edit_state.TextW.Size, callback_data.Buf, NULL);
- edit_state.CurLenA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
- edit_state.CursorAnimReset();
- }
- }
- }
+ const ImGuiItemFlags item_flags = window->DC.ItemFlags;
+ const ImRect nav_bb_rel(nav_bb.Min - window->Pos, nav_bb.Max - window->Pos);
- // Will copy result string if modified
- if (is_editable && strcmp(edit_state.TempBuffer.Data, buf) != 0)
- {
- apply_new_text = edit_state.TempBuffer.Data;
- apply_new_text_length = edit_state.CurLenA;
- }
+ // Process Init Request
+ if (g.NavInitRequest && g.NavLayer == window->DC.NavLayerCurrent)
+ {
+ // Even if 'ImGuiItemFlags_NoNavDefaultFocus' is on (typically collapse/close button) we record the first ResultId so they can be used as a fallback
+ if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus) || g.NavInitResultId == 0)
+ {
+ g.NavInitResultId = id;
+ g.NavInitResultRectRel = nav_bb_rel;
}
-
- // Copy result to user buffer
- if (apply_new_text)
+ if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus))
{
- IM_ASSERT(apply_new_text_length >= 0);
- if (backup_current_text_length != apply_new_text_length && is_resizable)
- {
- ImGuiInputTextCallbackData callback_data;
- callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;
- callback_data.Flags = flags;
- callback_data.Buf = buf;
- callback_data.BufTextLen = apply_new_text_length;
- callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1);
- callback_data.UserData = callback_user_data;
- callback(&callback_data);
- buf = callback_data.Buf;
- buf_size = callback_data.BufSize;
- apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1);
- IM_ASSERT(apply_new_text_length <= buf_size);
- }
-
- // If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size.
- ImStrncpy(buf, edit_state.TempBuffer.Data, ImMin(apply_new_text_length + 1, buf_size));
- value_changed = true;
+ g.NavInitRequest = false; // Found a match, clear request
+ NavUpdateAnyRequestFlag();
}
-
- // Clear temporary user storage
- edit_state.UserFlags = 0;
- edit_state.UserCallback = NULL;
- edit_state.UserCallbackData = NULL;
}
- // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)
- if (clear_active_id && g.ActiveId == id)
- ClearActiveID();
-
- // Render
- // Select which buffer we are going to display. When ImGuiInputTextFlags_NoLiveEdit is set 'buf' might still be the old value. We set buf to NULL to prevent accidental usage from now on.
- const char* buf_display = (g.ActiveId == id && is_editable) ? edit_state.TempBuffer.Data : buf; buf = NULL;
-
- // Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line
- // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether.
- // Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash.
- const int buf_display_max_length = 2 * 1024 * 1024;
-
- if (!is_multiline)
- {
- RenderNavHighlight(frame_bb, id);
- RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
- }
-
- const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x, frame_bb.Min.y + size.y); // Not using frame_bb.Max because we have adjusted size
- ImVec2 render_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding;
- ImVec2 text_size(0.f, 0.f);
- const bool is_currently_scrolling = (edit_state.ID == id && is_multiline && g.ActiveId == draw_window->GetIDNoKeepAlive("#SCROLLY"));
- if (g.ActiveId == id || is_currently_scrolling)
+ // Process Move Request (scoring for navigation)
+ // FIXME-NAV: Consider policy for double scoring (scoring from NavScoringRectScreen + scoring from a rect wrapped according to current wrapping policy)
+ if ((g.NavId != id || (g.NavMoveRequestFlags & ImGuiNavMoveFlags_AllowCurrentNavId)) && !(item_flags & ImGuiItemFlags_NoNav))
{
- edit_state.CursorAnim += io.DeltaTime;
-
- // This is going to be messy. We need to:
- // - Display the text (this alone can be more easily clipped)
- // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation)
- // - Measure text height (for scrollbar)
- // We are attempting to do most of that in **one main pass** to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for selection rendering (we could merge them by an extra refactoring effort)
- // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.
- const ImWchar* text_begin = edit_state.TextW.Data;
- ImVec2 cursor_offset, select_start_offset;
-
- {
- // Count lines + find lines numbers straddling 'cursor' and 'select_start' position.
- const ImWchar* searches_input_ptr[2];
- searches_input_ptr[0] = text_begin + edit_state.StbState.cursor;
- searches_input_ptr[1] = NULL;
- int searches_remaining = 1;
- int searches_result_line_number[2] = { -1, -999 };
- if (edit_state.StbState.select_start != edit_state.StbState.select_end)
- {
- searches_input_ptr[1] = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end);
- searches_result_line_number[1] = -1;
- searches_remaining++;
- }
-
- // Iterate all lines to find our line numbers
- // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter.
- searches_remaining += is_multiline ? 1 : 0;
- int line_count = 0;
- for (const ImWchar* s = text_begin; *s != 0; s++)
- if (*s == '\n')
- {
- line_count++;
- if (searches_result_line_number[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_number[0] = line_count; if (--searches_remaining <= 0) break; }
- if (searches_result_line_number[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_number[1] = line_count; if (--searches_remaining <= 0) break; }
- }
- line_count++;
- if (searches_result_line_number[0] == -1) searches_result_line_number[0] = line_count;
- if (searches_result_line_number[1] == -1) searches_result_line_number[1] = line_count;
-
- // Calculate 2d position by finding the beginning of the line and measuring distance
- cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x;
- cursor_offset.y = searches_result_line_number[0] * g.FontSize;
- if (searches_result_line_number[1] >= 0)
- {
- select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x;
- select_start_offset.y = searches_result_line_number[1] * g.FontSize;
- }
-
- // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224)
- if (is_multiline)
- text_size = ImVec2(size.x, line_count * g.FontSize);
- }
-
- // Scroll
- if (edit_state.CursorFollow)
+ ImGuiNavMoveResult* result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther;
+#if IMGUI_DEBUG_NAV_SCORING
+ // [DEBUG] Score all items in NavWindow at all times
+ if (!g.NavMoveRequest)
+ g.NavMoveDir = g.NavMoveDirLast;
+ bool new_best = NavScoreItem(result, nav_bb) && g.NavMoveRequest;
+#else
+ bool new_best = g.NavMoveRequest && NavScoreItem(result, nav_bb);
+#endif
+ if (new_best)
{
- // Horizontal scroll in chunks of quarter width
- if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll))
- {
- const float scroll_increment_x = size.x * 0.25f;
- if (cursor_offset.x < edit_state.ScrollX)
- edit_state.ScrollX = (float)(int)ImMax(0.0f, cursor_offset.x - scroll_increment_x);
- else if (cursor_offset.x - size.x >= edit_state.ScrollX)
- edit_state.ScrollX = (float)(int)(cursor_offset.x - size.x + scroll_increment_x);
- }
- else
- {
- edit_state.ScrollX = 0.0f;
- }
-
- // Vertical scroll
- if (is_multiline)
- {
- float scroll_y = draw_window->Scroll.y;
- if (cursor_offset.y - g.FontSize < scroll_y)
- scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);
- else if (cursor_offset.y - size.y >= scroll_y)
- scroll_y = cursor_offset.y - size.y;
- draw_window->DC.CursorPos.y += (draw_window->Scroll.y - scroll_y); // To avoid a frame of lag
- draw_window->Scroll.y = scroll_y;
- render_pos.y = draw_window->DC.CursorPos.y;
- }
+ result->ID = id;
+ result->Window = window;
+ result->RectRel = nav_bb_rel;
}
- edit_state.CursorFollow = false;
- const ImVec2 render_scroll = ImVec2(edit_state.ScrollX, 0.0f);
- // Draw selection
- if (edit_state.StbState.select_start != edit_state.StbState.select_end)
- {
- const ImWchar* text_selected_begin = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end);
- const ImWchar* text_selected_end = text_begin + ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end);
-
- float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection.
- float bg_offy_dn = is_multiline ? 0.0f : 2.0f;
- ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg);
- ImVec2 rect_pos = render_pos + select_start_offset - render_scroll;
- for (const ImWchar* p = text_selected_begin; p < text_selected_end; )
- {
- if (rect_pos.y > clip_rect.w + g.FontSize)
- break;
- if (rect_pos.y < clip_rect.y)
- {
- while (p < text_selected_end)
- if (*p++ == '\n')
- break;
- }
- else
+ 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))
{
- ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true);
- if (rect_size.x <= 0.0f) rect_size.x = (float)(int)(g.Font->GetCharAdvance((unsigned short)' ') * 0.50f); // So we can see selected empty lines
- ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos +ImVec2(rect_size.x, bg_offy_dn));
- rect.ClipWith(clip_rect);
- if (rect.Overlaps(clip_rect))
- draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);
+ result = &g.NavMoveResultLocalVisibleSet;
+ result->ID = id;
+ result->Window = window;
+ result->RectRel = nav_bb_rel;
}
- rect_pos.x = render_pos.x - render_scroll.x;
- rect_pos.y += g.FontSize;
- }
- }
-
- const int buf_display_len = edit_state.CurLenA;
- if (is_multiline || buf_display_len < buf_display_max_length)
- draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, GetColorU32(ImGuiCol_Text), buf_display, buf_display + buf_display_len, 0.0f, is_multiline ? NULL : &clip_rect);
-
- // Draw blinking cursor
- bool cursor_is_visible = (!g.IO.ConfigCursorBlink) || (g.InputTextState.CursorAnim <= 0.0f) || ImFmod(g.InputTextState.CursorAnim, 1.20f) <= 0.80f;
- ImVec2 cursor_screen_pos = render_pos + cursor_offset - render_scroll;
- ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y-g.FontSize+0.5f, cursor_screen_pos.x+1.0f, cursor_screen_pos.y-1.5f);
- if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect))
- draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text));
-
- // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.)
- if (is_editable)
- g.PlatformImePos = ImVec2(cursor_screen_pos.x - 1, cursor_screen_pos.y - g.FontSize);
- }
- else
- {
- // Render text only
- const char* buf_end = NULL;
- if (is_multiline)
- text_size = ImVec2(size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_end) * g.FontSize); // We don't need width
- else
- buf_end = buf_display + strlen(buf_display);
- if (is_multiline || (buf_end - buf_display) < buf_display_max_length)
- draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos, GetColorU32(ImGuiCol_Text), buf_display, buf_end, 0.0f, is_multiline ? NULL : &clip_rect);
}
- if (is_multiline)
+ // Update window-relative bounding box of navigated item
+ if (g.NavId == id)
{
- Dummy(text_size + ImVec2(0.0f, g.FontSize)); // Always add room to scroll an extra line
- EndChildFrame();
- EndGroup();
+ g.NavWindow = window; // Always refresh g.NavWindow, because some operations such as FocusItem() don't have a window.
+ g.NavLayer = window->DC.NavLayerCurrent;
+ g.NavIdIsAlive = true;
+ g.NavIdTabCounter = window->FocusIdxTabCounter;
+ window->NavRectRel[window->DC.NavLayerCurrent] = nav_bb_rel; // Store item bounding box (relative to window position)
}
-
- if (is_password)
- PopFont();
-
- // Log as text
- if (g.LogEnabled && !is_password)
- LogRenderedText(&render_pos, buf_display, NULL);
-
- if (label_size.x > 0)
- RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
-
- if (value_changed)
- MarkItemEdited(id);
-
- if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0)
- return enter_pressed;
- else
- return value_changed;
-}
-
-bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
-{
- IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
- return InputTextEx(label, buf, (int)buf_size, ImVec2(0,0), flags, callback, user_data);
-}
-
-bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
-{
- return InputTextEx(label, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data);
}
-// NB: format here must be a simple "%xx" format string with no prefix/suffix (unlike the Drag/Slider functions "format" argument)
-bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* data_ptr, const void* step, const void* step_fast, const char* format, ImGuiInputTextFlags extra_flags)
+bool ImGui::NavMoveRequestButNoResultYet()
{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
-
ImGuiContext& g = *GImGui;
- const ImGuiStyle& style = g.Style;
-
- IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
- if (format == NULL)
- format = GDataTypeInfo[data_type].PrintFmt;
-
- char buf[64];
- DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, data_ptr, format);
-
- bool value_changed = false;
- if ((extra_flags & (ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsScientific)) == 0)
- extra_flags |= ImGuiInputTextFlags_CharsDecimal;
- extra_flags |= ImGuiInputTextFlags_AutoSelectAll;
-
- if (step != NULL)
- {
- const float button_size = GetFrameHeight();
-
- BeginGroup(); // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive()
- PushID(label);
- PushItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
- if (InputText("", buf, IM_ARRAYSIZE(buf), extra_flags)) // PushId(label) + "" gives us the expected ID from outside point of view
- value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialText.Data, data_type, data_ptr, format);
- PopItemWidth();
-
- // Step buttons
- SameLine(0, style.ItemInnerSpacing.x);
- if (ButtonEx("-", ImVec2(button_size, button_size), ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups))
- {
- DataTypeApplyOp(data_type, '-', data_ptr, data_ptr, g.IO.KeyCtrl && step_fast ? step_fast : step);
- value_changed = true;
- }
- SameLine(0, style.ItemInnerSpacing.x);
- if (ButtonEx("+", ImVec2(button_size, button_size), ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups))
- {
- DataTypeApplyOp(data_type, '+', data_ptr, data_ptr, g.IO.KeyCtrl && step_fast ? step_fast : step);
- value_changed = true;
- }
- SameLine(0, style.ItemInnerSpacing.x);
- TextUnformatted(label, FindRenderedTextEnd(label));
-
- PopID();
- EndGroup();
- }
- else
- {
- if (InputText(label, buf, IM_ARRAYSIZE(buf), extra_flags))
- value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialText.Data, data_type, data_ptr, format);
- }
-
- return value_changed;
-}
-
-bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, const char* format, ImGuiInputTextFlags extra_flags)
-{
- extra_flags |= ImGuiInputTextFlags_CharsScientific;
- return InputScalar(label, ImGuiDataType_Float, (void*)v, (void*)(step>0.0f ? &step : NULL), (void*)(step_fast>0.0f ? &step_fast : NULL), format, extra_flags);
-}
-
-bool ImGui::InputDouble(const char* label, double* v, double step, double step_fast, const char* format, ImGuiInputTextFlags extra_flags)
-{
- extra_flags |= ImGuiInputTextFlags_CharsScientific;
- return InputScalar(label, ImGuiDataType_Double, (void*)v, (void*)(step>0.0 ? &step : NULL), (void*)(step_fast>0.0 ? &step_fast : NULL), format, extra_flags);
-}
-
-bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags extra_flags)
-{
- // Hexadecimal input provided as a convenience but the flag name is awkward. Typically you'd use InputText() to parse your own data, if you want to handle prefixes.
- const char* format = (extra_flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d";
- return InputScalar(label, ImGuiDataType_S32, (void*)v, (void*)(step>0 ? &step : NULL), (void*)(step_fast>0 ? &step_fast : NULL), format, extra_flags);
+ return g.NavMoveRequest && g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0;
}
-bool ImGui::InputScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* step, const void* step_fast, const char* format, ImGuiInputTextFlags extra_flags)
+void ImGui::NavMoveRequestCancel()
{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
-
ImGuiContext& g = *GImGui;
- bool value_changed = false;
- BeginGroup();
- PushID(label);
- PushMultiItemsWidths(components);
- size_t type_size = GDataTypeInfo[data_type].Size;
- for (int i = 0; i < components; i++)
- {
- PushID(i);
- value_changed |= InputScalar("##v", data_type, v, step, step_fast, format, extra_flags);
- SameLine(0, g.Style.ItemInnerSpacing.x);
- PopID();
- PopItemWidth();
- v = (void*)((char*)v + type_size);
- }
- PopID();
-
- TextUnformatted(label, FindRenderedTextEnd(label));
- EndGroup();
- return value_changed;
-}
-
-bool ImGui::InputFloat2(const char* label, float v[2], const char* format, ImGuiInputTextFlags extra_flags)
-{
- return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, extra_flags);
-}
-
-bool ImGui::InputFloat3(const char* label, float v[3], const char* format, ImGuiInputTextFlags extra_flags)
-{
- return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, extra_flags);
-}
-
-bool ImGui::InputFloat4(const char* label, float v[4], const char* format, ImGuiInputTextFlags extra_flags)
-{
- return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, extra_flags);
-}
-
-// Prefer using "const char* format" directly, which is more flexible and consistent with other API.
-#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
-bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, int decimal_precision, ImGuiInputTextFlags extra_flags)
-{
- char format[16] = "%f";
- if (decimal_precision >= 0)
- ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);
- return InputFloat(label, v, step, step_fast, format, extra_flags);
-}
-
-bool ImGui::InputFloat2(const char* label, float v[2], int decimal_precision, ImGuiInputTextFlags extra_flags)
-{
- char format[16] = "%f";
- if (decimal_precision >= 0)
- ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);
- return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, extra_flags);
-}
-
-bool ImGui::InputFloat3(const char* label, float v[3], int decimal_precision, ImGuiInputTextFlags extra_flags)
-{
- char format[16] = "%f";
- if (decimal_precision >= 0)
- ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);
- return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, extra_flags);
-}
-
-bool ImGui::InputFloat4(const char* label, float v[4], int decimal_precision, ImGuiInputTextFlags extra_flags)
-{
- char format[16] = "%f";
- if (decimal_precision >= 0)
- ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);
- return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, extra_flags);
-}
-#endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS
-
-bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags extra_flags)
-{
- return InputScalarN(label, ImGuiDataType_S32, v, 2, NULL, NULL, "%d", extra_flags);
-}
-
-bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags extra_flags)
-{
- return InputScalarN(label, ImGuiDataType_S32, v, 3, NULL, NULL, "%d", extra_flags);
-}
-
-bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags extra_flags)
-{
- return InputScalarN(label, ImGuiDataType_S32, v, 4, NULL, NULL, "%d", extra_flags);
+ g.NavMoveRequest = false;
+ NavUpdateAnyRequestFlag();
}
-static float CalcMaxPopupHeightFromItemCount(int items_count)
+void ImGui::NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, const ImRect& bb_rel, ImGuiNavMoveFlags move_flags)
{
ImGuiContext& g = *GImGui;
- if (items_count <= 0)
- return FLT_MAX;
- return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2);
+ IM_ASSERT(g.NavMoveRequestForward == ImGuiNavForward_None);
+ ImGui::NavMoveRequestCancel();
+ g.NavMoveDir = move_dir;
+ g.NavMoveClipDir = clip_dir;
+ g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued;
+ g.NavMoveRequestFlags = move_flags;
+ g.NavWindow->NavRectRel[g.NavLayer] = bb_rel;
}
-bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags)
+void ImGui::NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags move_flags)
{
- // Always consume the SetNextWindowSizeConstraint() call in our early return paths
ImGuiContext& g = *GImGui;
- ImGuiCond backup_next_window_size_constraint = g.NextWindowData.SizeConstraintCond;
- g.NextWindowData.SizeConstraintCond = 0;
-
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
-
- IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together
-
- const ImGuiStyle& style = g.Style;
- const ImGuiID id = window->GetID(label);
-
- const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight();
- const ImVec2 label_size = CalcTextSize(label, NULL, true);
- const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : CalcItemWidth();
- const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
- const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
- ItemSize(total_bb, style.FramePadding.y);
- if (!ItemAdd(total_bb, id, &frame_bb))
- return false;
-
- bool hovered, held;
- bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held);
- bool popup_open = IsPopupOpen(id);
-
- const ImRect value_bb(frame_bb.Min, frame_bb.Max - ImVec2(arrow_size, 0.0f));
- const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
- RenderNavHighlight(frame_bb, id);
- if (!(flags & ImGuiComboFlags_NoPreview))
- window->DrawList->AddRectFilled(frame_bb.Min, ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Max.y), frame_col, style.FrameRounding, ImDrawCornerFlags_Left);
- if (!(flags & ImGuiComboFlags_NoArrowButton))
- {
- window->DrawList->AddRectFilled(ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Min.y), frame_bb.Max, GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button), style.FrameRounding, (w <= arrow_size) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Right);
- RenderArrow(ImVec2(frame_bb.Max.x - arrow_size + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), ImGuiDir_Down);
- }
- RenderFrameBorder(frame_bb.Min, frame_bb.Max, style.FrameRounding);
- if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview))
- RenderTextClipped(frame_bb.Min + style.FramePadding, value_bb.Max, preview_value, NULL, NULL, ImVec2(0.0f,0.0f));
- if (label_size.x > 0)
- RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
+ if (g.NavWindow != window || !NavMoveRequestButNoResultYet() || g.NavMoveRequestForward != ImGuiNavForward_None || g.NavLayer != 0)
+ return;
+ IM_ASSERT(move_flags != 0); // No points calling this with no wrapping
+ ImRect bb_rel = window->NavRectRel[0];
- if ((pressed || g.NavActivateId == id) && !popup_open)
+ ImGuiDir clip_dir = g.NavMoveDir;
+ if (g.NavMoveDir == ImGuiDir_Left && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX)))
{
- if (window->DC.NavLayerCurrent == 0)
- window->NavLastIds[0] = id;
- OpenPopupEx(id);
- popup_open = true;
+ bb_rel.Min.x = bb_rel.Max.x = ImMax(window->SizeFull.x, window->SizeContents.x) - window->Scroll.x;
+ if (move_flags & ImGuiNavMoveFlags_WrapX) { bb_rel.TranslateY(-bb_rel.GetHeight()); clip_dir = ImGuiDir_Up; }
+ NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags);
}
-
- if (!popup_open)
- return false;
-
- if (backup_next_window_size_constraint)
+ if (g.NavMoveDir == ImGuiDir_Right && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX)))
{
- g.NextWindowData.SizeConstraintCond = backup_next_window_size_constraint;
- g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w);
+ bb_rel.Min.x = bb_rel.Max.x = -window->Scroll.x;
+ if (move_flags & ImGuiNavMoveFlags_WrapX) { bb_rel.TranslateY(+bb_rel.GetHeight()); clip_dir = ImGuiDir_Down; }
+ NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags);
}
- else
+ if (g.NavMoveDir == ImGuiDir_Up && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY)))
{
- if ((flags & ImGuiComboFlags_HeightMask_) == 0)
- flags |= ImGuiComboFlags_HeightRegular;
- IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one
- int popup_max_height_in_items = -1;
- if (flags & ImGuiComboFlags_HeightRegular) popup_max_height_in_items = 8;
- else if (flags & ImGuiComboFlags_HeightSmall) popup_max_height_in_items = 4;
- else if (flags & ImGuiComboFlags_HeightLarge) popup_max_height_in_items = 20;
- SetNextWindowSizeConstraints(ImVec2(w, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
+ bb_rel.Min.y = bb_rel.Max.y = ImMax(window->SizeFull.y, window->SizeContents.y) - window->Scroll.y;
+ if (move_flags & ImGuiNavMoveFlags_WrapY) { bb_rel.TranslateX(-bb_rel.GetWidth()); clip_dir = ImGuiDir_Left; }
+ NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags);
}
-
- char name[16];
- ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.CurrentPopupStack.Size); // Recycle windows based on depth
-
- // Peak into expected window size so we can position it
- if (ImGuiWindow* popup_window = FindWindowByName(name))
- if (popup_window->WasActive)
- {
- ImVec2 size_expected = CalcWindowExpectedSize(popup_window);
- if (flags & ImGuiComboFlags_PopupAlignLeft)
- popup_window->AutoPosLastDirection = ImGuiDir_Left;
- ImRect r_outer = GetWindowAllowedExtentRect(popup_window);
- ImVec2 pos = FindBestWindowPosForPopupEx(frame_bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, frame_bb, ImGuiPopupPositionPolicy_ComboBox);
- SetNextWindowPos(pos);
- }
-
- // Horizontally align ourselves with the framed text
- ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings;
- PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(style.FramePadding.x, style.WindowPadding.y));
- bool ret = Begin(name, NULL, window_flags);
- PopStyleVar();
- if (!ret)
+ if (g.NavMoveDir == ImGuiDir_Down && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY)))
{
- EndPopup();
- IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above
- return false;
+ bb_rel.Min.y = bb_rel.Max.y = -window->Scroll.y;
+ if (move_flags & ImGuiNavMoveFlags_WrapY) { bb_rel.TranslateX(+bb_rel.GetWidth()); clip_dir = ImGuiDir_Right; }
+ NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags);
}
- return true;
-}
-
-void ImGui::EndCombo()
-{
- EndPopup();
}
-// Getter for the old Combo() API: const char*[]
-static bool Items_ArrayGetter(void* data, int idx, const char** out_text)
+static void ImGui::NavSaveLastChildNavWindow(ImGuiWindow* nav_window)
{
- const char* const* items = (const char* const*)data;
- if (out_text)
- *out_text = items[idx];
- return true;
+ ImGuiWindow* parent_window = nav_window;
+ while (parent_window && (parent_window->Flags & ImGuiWindowFlags_ChildWindow) != 0 && (parent_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0)
+ parent_window = parent_window->ParentWindow;
+ if (parent_window && parent_window != nav_window)
+ parent_window->NavLastChildNavWindow = nav_window;
}
-// Getter for the old Combo() API: "item1\0item2\0item3\0"
-static bool Items_SingleStringGetter(void* data, int idx, const char** out_text)
+// Call when we are expected to land on Layer 0 after FocusWindow()
+static ImGuiWindow* ImGui::NavRestoreLastChildNavWindow(ImGuiWindow* window)
{
- // FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited.
- const char* items_separated_by_zeros = (const char*)data;
- int items_count = 0;
- const char* p = items_separated_by_zeros;
- while (*p)
- {
- if (idx == items_count)
- break;
- p += strlen(p) + 1;
- items_count++;
- }
- if (!*p)
- return false;
- if (out_text)
- *out_text = p;
- return true;
+ return window->NavLastChildNavWindow ? window->NavLastChildNavWindow : window;
}
-// Old API, prefer using BeginCombo() nowadays if you can.
-bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int popup_max_height_in_items)
+static void NavRestoreLayer(int layer)
{
ImGuiContext& g = *GImGui;
-
- // Call the getter to obtain the preview string which is a parameter to BeginCombo()
- const char* preview_value = NULL;
- if (*current_item >= 0 && *current_item < items_count)
- items_getter(data, *current_item, &preview_value);
-
- // The old Combo() API exposed "popup_max_height_in_items". The new more general BeginCombo() API doesn't have/need it, but we emulate it here.
- if (popup_max_height_in_items != -1 && !g.NextWindowData.SizeConstraintCond)
- SetNextWindowSizeConstraints(ImVec2(0,0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
-
- if (!BeginCombo(label, preview_value, ImGuiComboFlags_None))
- return false;
-
- // Display items
- // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed)
- bool value_changed = false;
- for (int i = 0; i < items_count; i++)
- {
- PushID((void*)(intptr_t)i);
- const bool item_selected = (i == *current_item);
- const char* item_text;
- if (!items_getter(data, i, &item_text))
- item_text = "*Unknown item*";
- if (Selectable(item_text, item_selected))
- {
- value_changed = true;
- *current_item = i;
- }
- if (item_selected)
- SetItemDefaultFocus();
- PopID();
- }
-
- EndCombo();
- return value_changed;
-}
-
-// Combo box helper allowing to pass an array of strings.
-bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items)
-{
- const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items);
- return value_changed;
+ g.NavLayer = layer;
+ if (layer == 0)
+ g.NavWindow = ImGui::NavRestoreLastChildNavWindow(g.NavWindow);
+ if (layer == 0 && g.NavWindow->NavLastIds[0] != 0)
+ ImGui::SetNavIDWithRectRel(g.NavWindow->NavLastIds[0], layer, g.NavWindow->NavRectRel[0]);
+ else
+ ImGui::NavInitWindow(g.NavWindow, true);
}
-// Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0"
-bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items)
+static inline void ImGui::NavUpdateAnyRequestFlag()
{
- int items_count = 0;
- const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open
- while (*p)
- {
- p += strlen(p) + 1;
- items_count++;
- }
- bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items);
- return value_changed;
+ ImGuiContext& g = *GImGui;
+ g.NavAnyRequest = g.NavMoveRequest || g.NavInitRequest || (IMGUI_DEBUG_NAV_SCORING && g.NavWindow != NULL);
+ if (g.NavAnyRequest)
+ IM_ASSERT(g.NavWindow != NULL);
}
-// Tip: pass an empty label (e.g. "##dummy") then you can use the space to draw other text or image.
-// But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID or use ##unique_id.
-bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
+// This needs to be called before we submit any widget (aka in or before Begin)
+void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit)
{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
-
ImGuiContext& g = *GImGui;
- const ImGuiStyle& style = g.Style;
-
- if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet) // FIXME-OPT: Avoid if vertically clipped.
- PopClipRect();
-
- ImGuiID id = window->GetID(label);
- ImVec2 label_size = CalcTextSize(label, NULL, true);
- ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y);
- ImVec2 pos = window->DC.CursorPos;
- pos.y += window->DC.CurrentLineTextBaseOffset;
- ImRect bb_inner(pos, pos + size);
- ItemSize(bb_inner);
-
- // Fill horizontal space.
- ImVec2 window_padding = window->WindowPadding;
- float max_x = (flags & ImGuiSelectableFlags_SpanAllColumns) ? GetWindowContentRegionMax().x : GetContentRegionMax().x;
- float w_draw = ImMax(label_size.x, window->Pos.x + max_x - window_padding.x - window->DC.CursorPos.x);
- ImVec2 size_draw((size_arg.x != 0 && !(flags & ImGuiSelectableFlags_DrawFillAvailWidth)) ? size_arg.x : w_draw, size_arg.y != 0.0f ? size_arg.y : size.y);
- ImRect bb(pos, pos + size_draw);
- if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_DrawFillAvailWidth))
- bb.Max.x += window_padding.x;
-
- // Selectables are tightly packed together, we extend the box to cover spacing between selectable.
- float spacing_L = (float)(int)(style.ItemSpacing.x * 0.5f);
- float spacing_U = (float)(int)(style.ItemSpacing.y * 0.5f);
- float spacing_R = style.ItemSpacing.x - spacing_L;
- float spacing_D = style.ItemSpacing.y - spacing_U;
- bb.Min.x -= spacing_L;
- bb.Min.y -= spacing_U;
- bb.Max.x += spacing_R;
- bb.Max.y += spacing_D;
- if (!ItemAdd(bb, (flags & ImGuiSelectableFlags_Disabled) ? 0 : id))
- {
- if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet)
- PushColumnClipRect();
- return false;
- }
-
- // 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;
- if (flags & ImGuiSelectableFlags_PressedOnClick) button_flags |= ImGuiButtonFlags_PressedOnClick;
- if (flags & ImGuiSelectableFlags_PressedOnRelease) button_flags |= ImGuiButtonFlags_PressedOnRelease;
- if (flags & ImGuiSelectableFlags_Disabled) button_flags |= ImGuiButtonFlags_Disabled;
- if (flags & ImGuiSelectableFlags_AllowDoubleClick) button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick;
- bool hovered, held;
- bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
- if (flags & ImGuiSelectableFlags_Disabled)
- selected = false;
-
- // Hovering selectable with mouse updates NavId accordingly so navigation can be resumed with gamepad/keyboard (this doesn't happen on most widgets)
- if (pressed || hovered)
- if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent)
- {
- g.NavDisableHighlight = true;
- SetNavID(id, window->DC.NavLayerCurrent);
- }
- if (pressed)
- MarkItemEdited(id);
-
- // Render
- if (hovered || selected)
- {
- const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
- RenderFrame(bb.Min, bb.Max, col, false, 0.0f);
- RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding);
- }
-
- if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet)
+ IM_ASSERT(window == g.NavWindow);
+ bool init_for_nav = false;
+ if (!(window->Flags & ImGuiWindowFlags_NoNavInputs))
+ if (!(window->Flags & ImGuiWindowFlags_ChildWindow) || (window->Flags & ImGuiWindowFlags_Popup) || (window->NavLastIds[0] == 0) || force_reinit)
+ init_for_nav = true;
+ if (init_for_nav)
{
- PushColumnClipRect();
- bb.Max.x -= (GetContentRegionMax().x - max_x);
+ SetNavID(0, g.NavLayer);
+ g.NavInitRequest = true;
+ g.NavInitRequestFromMove = false;
+ g.NavInitResultId = 0;
+ g.NavInitResultRectRel = ImRect();
+ NavUpdateAnyRequestFlag();
}
-
- if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
- RenderTextClipped(bb_inner.Min, bb.Max, label, NULL, &label_size, ImVec2(0.0f,0.0f));
- if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor();
-
- // Automatically close popups
- if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(window->DC.ItemFlags & ImGuiItemFlags_SelectableDontClosePopup))
- CloseCurrentPopup();
- return pressed;
-}
-
-bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
-{
- if (Selectable(label, *p_selected, flags, size_arg))
+ else
{
- *p_selected = !*p_selected;
- return true;
+ g.NavId = window->NavLastIds[0];
}
- return false;
-}
-
-// FIXME: Rename to BeginListBox()
-// Helper to calculate the size of a listbox and display a label on the right.
-// Tip: To have a list filling the entire window width, PushItemWidth(-1) and pass an empty label "##empty"
-bool ImGui::ListBoxHeader(const char* label, const ImVec2& size_arg)
-{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
-
- const ImGuiStyle& style = GetStyle();
- const ImGuiID id = GetID(label);
- const ImVec2 label_size = CalcTextSize(label, NULL, true);
-
- // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
- ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.4f + style.ItemSpacing.y);
- ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y));
- ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
- ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
- window->DC.LastItemRect = bb; // Forward storage for ListBoxFooter.. dodgy.
-
- BeginGroup();
- if (label_size.x > 0)
- RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
-
- BeginChildFrame(id, frame_bb.GetSize());
- return true;
}
-// FIXME: Rename to BeginListBox()
-bool ImGui::ListBoxHeader(const char* label, int items_count, int height_in_items)
+static ImVec2 ImGui::NavCalcPreferredRefPos()
{
- // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
- // We don't add +0.40f if items_count <= height_in_items. It is slightly dodgy, because it means a dynamic list of items will make the widget resize occasionally when it crosses that size.
- // I am expecting that someone will come and complain about this behavior in a remote future, then we can advise on a better solution.
- if (height_in_items < 0)
- height_in_items = ImMin(items_count, 7);
- float height_in_items_f = height_in_items < items_count ? (height_in_items + 0.40f) : (height_in_items + 0.00f);
+ ImGuiContext& g = *GImGui;
+ if (g.NavDisableHighlight || !g.NavDisableMouseHover || !g.NavWindow)
+ return ImFloor(g.IO.MousePos);
- // We include ItemSpacing.y so that a list sized for the exact number of items doesn't make a scrollbar appears. We could also enforce that by passing a flag to BeginChild().
- ImVec2 size;
- size.x = 0.0f;
- size.y = GetTextLineHeightWithSpacing() * height_in_items_f + GetStyle().ItemSpacing.y;
- return ListBoxHeader(label, size);
+ // When navigation is active and mouse is disabled, decide on an arbitrary position around the bottom left of the currently navigated item
+ const ImRect& rect_rel = g.NavWindow->NavRectRel[g.NavLayer];
+ ImVec2 pos = g.NavWindow->Pos + ImVec2(rect_rel.Min.x + ImMin(g.Style.FramePadding.x*4, rect_rel.GetWidth()), rect_rel.Max.y - ImMin(g.Style.FramePadding.y, rect_rel.GetHeight()));
+ ImRect visible_rect = GetViewportRect();
+ return ImFloor(ImClamp(pos, visible_rect.Min, visible_rect.Max)); // ImFloor() is important because non-integer mouse position application in back-end might be lossy and result in undesirable non-zero delta.
}
-// FIXME: Rename to EndListBox()
-void ImGui::ListBoxFooter()
+float ImGui::GetNavInputAmount(ImGuiNavInput n, ImGuiInputReadMode mode)
{
- ImGuiWindow* parent_window = GetCurrentWindow()->ParentWindow;
- const ImRect bb = parent_window->DC.LastItemRect;
- const ImGuiStyle& style = GetStyle();
-
- EndChildFrame();
-
- // Redeclare item size so that it includes the label (we have stored the full size in LastItemRect)
- // We call SameLine() to restore DC.CurrentLine* data
- SameLine();
- parent_window->DC.CursorPos = bb.Min;
- ItemSize(bb, style.FramePadding.y);
- EndGroup();
-}
+ ImGuiContext& g = *GImGui;
+ if (mode == ImGuiInputReadMode_Down)
+ return g.IO.NavInputs[n]; // Instant, read analog input (0.0f..1.0f, as provided by user)
-bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items)
-{
- const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items);
- return value_changed;
+ const float t = g.IO.NavInputsDownDuration[n];
+ if (t < 0.0f && mode == ImGuiInputReadMode_Released) // Return 1.0f when just released, no repeat, ignore analog input.
+ return (g.IO.NavInputsDownDurationPrev[n] >= 0.0f ? 1.0f : 0.0f);
+ if (t < 0.0f)
+ return 0.0f;
+ if (mode == ImGuiInputReadMode_Pressed) // Return 1.0f when just pressed, no repeat, ignore analog input.
+ return (t == 0.0f) ? 1.0f : 0.0f;
+ if (mode == ImGuiInputReadMode_Repeat)
+ return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 0.80f, g.IO.KeyRepeatRate * 0.80f);
+ if (mode == ImGuiInputReadMode_RepeatSlow)
+ return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 1.00f, g.IO.KeyRepeatRate * 2.00f);
+ if (mode == ImGuiInputReadMode_RepeatFast)
+ return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 0.80f, g.IO.KeyRepeatRate * 0.30f);
+ return 0.0f;
}
-bool ImGui::ListBox(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int height_in_items)
+ImVec2 ImGui::GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources, ImGuiInputReadMode mode, float slow_factor, float fast_factor)
{
- if (!ListBoxHeader(label, items_count, height_in_items))
- return false;
-
- // Assume all items have even height (= 1 line of text). If you need items of different or variable sizes you can create a custom version of ListBox() in your code without using the clipper.
- ImGuiContext& g = *GImGui;
- bool value_changed = false;
- ImGuiListClipper clipper(items_count, GetTextLineHeightWithSpacing()); // We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to.
- while (clipper.Step())
- for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
- {
- const bool item_selected = (i == *current_item);
- const char* item_text;
- if (!items_getter(data, i, &item_text))
- item_text = "*Unknown item*";
-
- PushID(i);
- if (Selectable(item_text, item_selected))
- {
- *current_item = i;
- value_changed = true;
- }
- if (item_selected)
- SetItemDefaultFocus();
- PopID();
- }
- ListBoxFooter();
- if (value_changed)
- MarkItemEdited(g.CurrentWindow->DC.LastItemId);
-
- return value_changed;
+ ImVec2 delta(0.0f, 0.0f);
+ if (dir_sources & ImGuiNavDirSourceFlags_Keyboard)
+ delta += ImVec2(GetNavInputAmount(ImGuiNavInput_KeyRight_, mode) - GetNavInputAmount(ImGuiNavInput_KeyLeft_, mode), GetNavInputAmount(ImGuiNavInput_KeyDown_, mode) - GetNavInputAmount(ImGuiNavInput_KeyUp_, mode));
+ if (dir_sources & ImGuiNavDirSourceFlags_PadDPad)
+ delta += ImVec2(GetNavInputAmount(ImGuiNavInput_DpadRight, mode) - GetNavInputAmount(ImGuiNavInput_DpadLeft, mode), GetNavInputAmount(ImGuiNavInput_DpadDown, mode) - GetNavInputAmount(ImGuiNavInput_DpadUp, mode));
+ if (dir_sources & ImGuiNavDirSourceFlags_PadLStick)
+ delta += ImVec2(GetNavInputAmount(ImGuiNavInput_LStickRight, mode) - GetNavInputAmount(ImGuiNavInput_LStickLeft, mode), GetNavInputAmount(ImGuiNavInput_LStickDown, mode) - GetNavInputAmount(ImGuiNavInput_LStickUp, mode));
+ if (slow_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakSlow))
+ delta *= slow_factor;
+ if (fast_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakFast))
+ delta *= fast_factor;
+ return delta;
}
-bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled)
+// Scroll to keep newly navigated item fully into view
+// NB: We modify rect_rel by the amount we scrolled for, so it is immediately updated.
+static void NavScrollToBringItemIntoView(ImGuiWindow* window, const ImRect& item_rect)
{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
+ ImRect window_rect(window->InnerMainRect.Min - ImVec2(1, 1), window->InnerMainRect.Max + ImVec2(1, 1));
+ //g.OverlayDrawList.AddRect(window_rect.Min, window_rect.Max, IM_COL32_WHITE); // [DEBUG]
+ if (window_rect.Contains(item_rect))
+ return;
ImGuiContext& g = *GImGui;
- ImGuiStyle& style = g.Style;
- ImVec2 pos = window->DC.CursorPos;
- ImVec2 label_size = CalcTextSize(label, NULL, true);
-
- ImGuiSelectableFlags flags = ImGuiSelectableFlags_PressedOnRelease | (enabled ? 0 : ImGuiSelectableFlags_Disabled);
- bool pressed;
- if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
+ if (window->ScrollbarX && item_rect.Min.x < window_rect.Min.x)
{
- // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful
- // Note that in this situation we render neither the shortcut neither the selected tick mark
- float w = label_size.x;
- window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f);
- PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f);
- pressed = Selectable(label, false, flags, ImVec2(w, 0.0f));
- PopStyleVar();
- window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
+ window->ScrollTarget.x = item_rect.Min.x - window->Pos.x + window->Scroll.x - g.Style.ItemSpacing.x;
+ window->ScrollTargetCenterRatio.x = 0.0f;
}
- else
+ else if (window->ScrollbarX && item_rect.Max.x >= window_rect.Max.x)
{
- ImVec2 shortcut_size = shortcut ? CalcTextSize(shortcut, NULL) : ImVec2(0.0f, 0.0f);
- float w = window->MenuColumns.DeclColumns(label_size.x, shortcut_size.x, (float)(int)(g.FontSize * 1.20f)); // Feedback for next frame
- float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w);
- pressed = Selectable(label, false, flags | ImGuiSelectableFlags_DrawFillAvailWidth, ImVec2(w, 0.0f));
- if (shortcut_size.x > 0.0f)
- {
- PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
- RenderText(pos + ImVec2(window->MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false);
- PopStyleColor();
- }
- if (selected)
- RenderCheckMark(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), g.FontSize * 0.866f);
+ window->ScrollTarget.x = item_rect.Max.x - window->Pos.x + window->Scroll.x + g.Style.ItemSpacing.x;
+ window->ScrollTargetCenterRatio.x = 1.0f;
}
- return pressed;
-}
-
-bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled)
-{
- if (MenuItem(label, shortcut, p_selected ? *p_selected : false, enabled))
+ if (item_rect.Min.y < window_rect.Min.y)
{
- if (p_selected)
- *p_selected = !*p_selected;
- return true;
+ window->ScrollTarget.y = item_rect.Min.y - window->Pos.y + window->Scroll.y - g.Style.ItemSpacing.y;
+ window->ScrollTargetCenterRatio.y = 0.0f;
}
- return false;
-}
-
-// For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.
-bool ImGui::BeginMainMenuBar()
-{
- ImGuiContext& g = *GImGui;
- g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f));
- SetNextWindowPos(ImVec2(0.0f, 0.0f));
- SetNextWindowSize(ImVec2(g.IO.DisplaySize.x, g.NextWindowData.MenuBarOffsetMinVal.y + g.FontBaseSize + g.Style.FramePadding.y));
- PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
- PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0,0));
- ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;
- bool is_open = Begin("##MainMenuBar", NULL, window_flags) && BeginMenuBar();
- PopStyleVar(2);
- g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f);
- if (!is_open)
+ else if (item_rect.Max.y >= window_rect.Max.y)
{
- End();
- return false;
+ window->ScrollTarget.y = item_rect.Max.y - window->Pos.y + window->Scroll.y + g.Style.ItemSpacing.y;
+ window->ScrollTargetCenterRatio.y = 1.0f;
}
- return true;
}
-void ImGui::EndMainMenuBar()
+static void ImGui::NavUpdate()
{
- EndMenuBar();
-
- // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window
ImGuiContext& g = *GImGui;
- if (g.CurrentWindow == g.NavWindow && g.NavLayer == 0)
- FocusFrontMostActiveWindowIgnoringOne(g.NavWindow);
-
- End();
-}
-
-bool ImGui::BeginMenuBar()
-{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
- if (!(window->Flags & ImGuiWindowFlags_MenuBar))
- return false;
-
- IM_ASSERT(!window->DC.MenuBarAppending);
- BeginGroup(); // Backup position on layer 0
- PushID("##menubar");
-
- // We don't clip with current window clipping rectangle as it is already set to the area below. However we clip with window full rect.
- // We remove 1 worth of rounding to Max.x to that text in long menus and small windows don't tend to display over the lower-right rounded area, which looks particularly glitchy.
- ImRect bar_rect = window->MenuBarRect();
- ImRect clip_rect(ImFloor(bar_rect.Min.x + 0.5f), ImFloor(bar_rect.Min.y + window->WindowBorderSize + 0.5f), ImFloor(ImMax(bar_rect.Min.x, bar_rect.Max.x - window->WindowRounding) + 0.5f), ImFloor(bar_rect.Max.y + 0.5f));
- clip_rect.ClipWith(window->OuterRectClipped);
- PushClipRect(clip_rect.Min, clip_rect.Max, false);
-
- window->DC.CursorPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y);
- window->DC.LayoutType = ImGuiLayoutType_Horizontal;
- window->DC.NavLayerCurrent++;
- window->DC.NavLayerCurrentMask <<= 1;
- window->DC.MenuBarAppending = true;
- AlignTextToFramePadding();
- return true;
-}
+ g.IO.WantSetMousePos = false;
+#if 0
+ if (g.NavScoringCount > 0) printf("[%05d] NavScoringCount %d for '%s' layer %d (Init:%d, Move:%d)\n", g.FrameCount, g.NavScoringCount, g.NavWindow ? g.NavWindow->Name : "NULL", g.NavLayer, g.NavInitRequest || g.NavInitResultId != 0, g.NavMoveRequest);
+#endif
-void ImGui::EndMenuBar()
-{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return;
- ImGuiContext& g = *GImGui;
+ // Set input source as Gamepad when buttons are pressed before we map Keyboard (some features differs when used with Gamepad vs Keyboard)
+ bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0;
+ bool nav_gamepad_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (g.IO.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
+ if (nav_gamepad_active)
+ if (g.IO.NavInputs[ImGuiNavInput_Activate] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Input] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Cancel] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Menu] > 0.0f)
+ g.NavInputSource = ImGuiInputSource_NavGamepad;
- // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings.
- if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
+ // Update Keyboard->Nav inputs mapping
+ if (nav_keyboard_active)
{
- ImGuiWindow* nav_earliest_child = g.NavWindow;
- while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu))
- nav_earliest_child = nav_earliest_child->ParentWindow;
- if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && g.NavMoveRequestForward == ImGuiNavForward_None)
- {
- // To do so we claim focus back, restore NavId and then process the movement request for yet another frame.
- // This involve a one-frame delay which isn't very problematic in this situation. We could remove it by scoring in advance for multiple window (probably not worth the hassle/cost)
- IM_ASSERT(window->DC.NavLayerActiveMaskNext & 0x02); // Sanity check
- FocusWindow(window);
- SetNavIDWithRectRel(window->NavLastIds[1], 1, window->NavRectRel[1]);
- g.NavLayer = 1;
- g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection.
- g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued;
- NavMoveRequestCancel();
- }
+ #define NAV_MAP_KEY(_KEY, _NAV_INPUT) if (IsKeyDown(g.IO.KeyMap[_KEY])) { g.IO.NavInputs[_NAV_INPUT] = 1.0f; g.NavInputSource = ImGuiInputSource_NavKeyboard; }
+ NAV_MAP_KEY(ImGuiKey_Space, ImGuiNavInput_Activate );
+ NAV_MAP_KEY(ImGuiKey_Enter, ImGuiNavInput_Input );
+ NAV_MAP_KEY(ImGuiKey_Escape, ImGuiNavInput_Cancel );
+ NAV_MAP_KEY(ImGuiKey_LeftArrow, ImGuiNavInput_KeyLeft_ );
+ NAV_MAP_KEY(ImGuiKey_RightArrow,ImGuiNavInput_KeyRight_);
+ NAV_MAP_KEY(ImGuiKey_UpArrow, ImGuiNavInput_KeyUp_ );
+ NAV_MAP_KEY(ImGuiKey_DownArrow, ImGuiNavInput_KeyDown_ );
+ if (g.IO.KeyCtrl) g.IO.NavInputs[ImGuiNavInput_TweakSlow] = 1.0f;
+ if (g.IO.KeyShift) g.IO.NavInputs[ImGuiNavInput_TweakFast] = 1.0f;
+ if (g.IO.KeyAlt) g.IO.NavInputs[ImGuiNavInput_KeyMenu_] = 1.0f;
+ #undef NAV_MAP_KEY
}
+ memcpy(g.IO.NavInputsDownDurationPrev, g.IO.NavInputsDownDuration, sizeof(g.IO.NavInputsDownDuration));
+ for (int i = 0; i < IM_ARRAYSIZE(g.IO.NavInputs); i++)
+ g.IO.NavInputsDownDuration[i] = (g.IO.NavInputs[i] > 0.0f) ? (g.IO.NavInputsDownDuration[i] < 0.0f ? 0.0f : g.IO.NavInputsDownDuration[i] + g.IO.DeltaTime) : -1.0f;
- IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar);
- IM_ASSERT(window->DC.MenuBarAppending);
- 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().AdvanceCursor = false;
- EndGroup(); // Restore position on layer 0
- window->DC.LayoutType = ImGuiLayoutType_Vertical;
- window->DC.NavLayerCurrent--;
- window->DC.NavLayerCurrentMask >>= 1;
- window->DC.MenuBarAppending = false;
-}
-
-bool ImGui::BeginMenu(const char* label, bool enabled)
-{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
-
- ImGuiContext& g = *GImGui;
- const ImGuiStyle& style = g.Style;
- const ImGuiID id = window->GetID(label);
-
- ImVec2 label_size = CalcTextSize(label, NULL, true);
-
- bool pressed;
- bool menu_is_open = IsPopupOpen(id);
- bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].OpenParentId == window->IDStack.back());
- ImGuiWindow* backed_nav_window = g.NavWindow;
- if (menuset_is_open)
- g.NavWindow = window; // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent)
-
- // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu (using FindBestWindowPosForPopup).
- ImVec2 popup_pos, pos = window->DC.CursorPos;
- if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
+ // Process navigation init request (select first/default focus)
+ if (g.NavInitResultId != 0 && (!g.NavDisableHighlight || g.NavInitRequestFromMove))
{
- // Menu inside an horizontal menu bar
- // Selectable extend their highlight by half ItemSpacing in each direction.
- // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin()
- popup_pos = ImVec2(pos.x - window->WindowPadding.x, pos.y - style.FramePadding.y + window->MenuBarHeight());
- window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f);
- PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f);
- float w = label_size.x;
- pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));
- PopStyleVar();
- window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
+ // Apply result from previous navigation init request (will typically select the first item, unless SetItemDefaultFocus() has been called)
+ IM_ASSERT(g.NavWindow);
+ if (g.NavInitRequestFromMove)
+ SetNavIDWithRectRel(g.NavInitResultId, g.NavLayer, g.NavInitResultRectRel);
+ else
+ SetNavID(g.NavInitResultId, g.NavLayer);
+ g.NavWindow->NavRectRel[g.NavLayer] = g.NavInitResultRectRel;
}
- else
+ g.NavInitRequest = false;
+ g.NavInitRequestFromMove = false;
+ g.NavInitResultId = 0;
+ g.NavJustMovedToId = 0;
+
+ // Process navigation move request
+ if (g.NavMoveRequest && (g.NavMoveResultLocal.ID != 0 || g.NavMoveResultOther.ID != 0))
+ NavUpdateMoveResult();
+
+ // When a forwarded move request failed, we restore the highlight that we disabled during the forward frame
+ if (g.NavMoveRequestForward == ImGuiNavForward_ForwardActive)
{
- // Menu inside a menu
- popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y);
- float w = window->MenuColumns.DeclColumns(label_size.x, 0.0f, (float)(int)(g.FontSize * 1.20f)); // Feedback to next frame
- float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w);
- pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_DrawFillAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));
- if (!enabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
- RenderArrow(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.30f, 0.0f), ImGuiDir_Right);
- if (!enabled) PopStyleColor();
+ IM_ASSERT(g.NavMoveRequest);
+ if (g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0)
+ g.NavDisableHighlight = false;
+ g.NavMoveRequestForward = ImGuiNavForward_None;
}
- const bool hovered = enabled && ItemHoverable(window->DC.LastItemRect, id);
- if (menuset_is_open)
- g.NavWindow = backed_nav_window;
-
- bool want_open = false, want_close = false;
- if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
+ // Apply application mouse position movement, after we had a chance to process move request result.
+ if (g.NavMousePosDirty && g.NavIdIsAlive)
{
- // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive.
- bool moving_within_opened_triangle = false;
- if (g.HoveredWindow == window && g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].ParentWindow == window && !(window->Flags & ImGuiWindowFlags_MenuBar))
+ // Set mouse position given our knowledge of the navigated item position from last frame
+ if ((g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) && (g.IO.BackendFlags & ImGuiBackendFlags_HasSetMousePos))
{
- if (ImGuiWindow* next_window = g.OpenPopupStack[g.CurrentPopupStack.Size].Window)
+ if (!g.NavDisableHighlight && g.NavDisableMouseHover && g.NavWindow)
{
- ImRect next_window_rect = next_window->Rect();
- ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta;
- ImVec2 tb = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR();
- ImVec2 tc = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR();
- float extra = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack.
- ta.x += (window->Pos.x < next_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues
- tb.y = ta.y + ImMax((tb.y - extra) - ta.y, -100.0f); // triangle is maximum 200 high to limit the slope and the bias toward large sub-menus // FIXME: Multiply by fb_scale?
- tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f);
- moving_within_opened_triangle = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos);
- //window->DrawList->PushClipRectFullScreen(); window->DrawList->AddTriangleFilled(ta, tb, tc, moving_within_opened_triangle ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); window->DrawList->PopClipRect(); // Debug
+ g.IO.MousePos = g.IO.MousePosPrev = NavCalcPreferredRefPos();
+ g.IO.WantSetMousePos = true;
}
}
+ g.NavMousePosDirty = false;
+ }
+ g.NavIdIsAlive = false;
+ g.NavJustTabbedId = 0;
+ IM_ASSERT(g.NavLayer == 0 || g.NavLayer == 1);
+
+ // Store our return window (for returning from Layer 1 to Layer 0) and clear it as soon as we step back in our own Layer 0
+ if (g.NavWindow)
+ NavSaveLastChildNavWindow(g.NavWindow);
+ if (g.NavWindow && g.NavWindow->NavLastChildNavWindow != NULL && g.NavLayer == 0)
+ g.NavWindow->NavLastChildNavWindow = NULL;
- want_close = (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_within_opened_triangle);
- want_open = (!menu_is_open && hovered && !moving_within_opened_triangle) || (!menu_is_open && hovered && pressed);
+ // Update CTRL+TAB and Windowing features (hold Square to move/resize/etc.)
+ NavUpdateWindowing();
+
+ // Set output flags for user application
+ g.IO.NavActive = (nav_keyboard_active || nav_gamepad_active) && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs);
+ g.IO.NavVisible = (g.IO.NavActive && g.NavId != 0 && !g.NavDisableHighlight) || (g.NavWindowingTarget != NULL) || g.NavInitRequest;
- if (g.NavActivateId == id)
+ // Process NavCancel input (to close a popup, get back to parent, clear focus)
+ if (IsNavInputPressed(ImGuiNavInput_Cancel, ImGuiInputReadMode_Pressed))
+ {
+ if (g.ActiveId != 0)
{
- want_close = menu_is_open;
- want_open = !menu_is_open;
+ ClearActiveID();
}
- if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open
+ else if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow) && !(g.NavWindow->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->ParentWindow)
{
- want_open = true;
- NavMoveRequestCancel();
+ // Exit child window
+ ImGuiWindow* child_window = g.NavWindow;
+ ImGuiWindow* parent_window = g.NavWindow->ParentWindow;
+ IM_ASSERT(child_window->ChildId != 0);
+ FocusWindow(parent_window);
+ SetNavID(child_window->ChildId, 0);
+ g.NavIdIsAlive = false;
+ if (g.NavDisableMouseHover)
+ g.NavMousePosDirty = true;
}
- }
- else
- {
- // Menu bar
- if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it
+ else if (g.OpenPopupStack.Size > 0)
{
- want_close = true;
- want_open = menu_is_open = false;
+ // Close open popup/menu
+ if (!(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal))
+ ClosePopupToLevel(g.OpenPopupStack.Size - 1);
}
- else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others
+ else if (g.NavLayer != 0)
{
- want_open = true;
+ // Leave the "menu" layer
+ NavRestoreLayer(0);
}
- else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open
+ else
{
- want_open = true;
- NavMoveRequestCancel();
+ // Clear NavLastId for popups but keep it for regular child window so we can leave one and come back where we were
+ if (g.NavWindow && ((g.NavWindow->Flags & ImGuiWindowFlags_Popup) || !(g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow)))
+ g.NavWindow->NavLastIds[0] = 0;
+ g.NavId = 0;
}
}
- if (!enabled) // explicitly close if an open menu becomes disabled, facilitate users code a lot in pattern such as 'if (BeginMenu("options", has_object)) { ..use object.. }'
- want_close = true;
- if (want_close && IsPopupOpen(id))
- ClosePopupToLevel(g.CurrentPopupStack.Size);
-
- if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.CurrentPopupStack.Size)
+ // Process manual activation request
+ g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = 0;
+ if (g.NavId != 0 && !g.NavDisableHighlight && !g.NavWindowingTarget && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
{
- // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame.
- OpenPopup(label);
- return false;
+ bool activate_down = IsNavInputDown(ImGuiNavInput_Activate);
+ bool activate_pressed = activate_down && IsNavInputPressed(ImGuiNavInput_Activate, ImGuiInputReadMode_Pressed);
+ if (g.ActiveId == 0 && activate_pressed)
+ g.NavActivateId = g.NavId;
+ if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_down)
+ g.NavActivateDownId = g.NavId;
+ if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_pressed)
+ g.NavActivatePressedId = g.NavId;
+ if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && IsNavInputPressed(ImGuiNavInput_Input, ImGuiInputReadMode_Pressed))
+ g.NavInputId = g.NavId;
}
+ if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
+ g.NavDisableHighlight = true;
+ if (g.NavActivateId != 0)
+ IM_ASSERT(g.NavActivateDownId == g.NavActivateId);
+ g.NavMoveRequest = false;
- menu_is_open |= want_open;
- if (want_open)
- OpenPopup(label);
+ // Process programmatic activation request
+ if (g.NavNextActivateId != 0)
+ g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = g.NavNextActivateId;
+ g.NavNextActivateId = 0;
- if (menu_is_open)
+ // Initiate directional inputs request
+ const int allowed_dir_flags = (g.ActiveId == 0) ? ~0 : g.ActiveIdAllowNavDirFlags;
+ if (g.NavMoveRequestForward == ImGuiNavForward_None)
{
- // Sub-menus are ChildWindow so that mouse can be hovering across them (otherwise top-most popup menu would steal focus and not allow hovering on parent menu)
- SetNextWindowPos(popup_pos, ImGuiCond_Always);
- ImGuiWindowFlags flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus;
- if (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
- flags |= ImGuiWindowFlags_ChildWindow;
- menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
+ g.NavMoveDir = ImGuiDir_None;
+ g.NavMoveRequestFlags = 0;
+ if (g.NavWindow && !g.NavWindowingTarget && allowed_dir_flags && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
+ {
+ if ((allowed_dir_flags & (1<<ImGuiDir_Left)) && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadLeft, ImGuiNavInput_KeyLeft_, ImGuiInputReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Left;
+ if ((allowed_dir_flags & (1<<ImGuiDir_Right)) && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadRight,ImGuiNavInput_KeyRight_,ImGuiInputReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Right;
+ if ((allowed_dir_flags & (1<<ImGuiDir_Up)) && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadUp, ImGuiNavInput_KeyUp_, ImGuiInputReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Up;
+ if ((allowed_dir_flags & (1<<ImGuiDir_Down)) && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadDown, ImGuiNavInput_KeyDown_, ImGuiInputReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Down;
+ }
+ g.NavMoveClipDir = g.NavMoveDir;
}
-
- return menu_is_open;
-}
-
-void ImGui::EndMenu()
-{
- // Nav: When a left move request _within our child menu_ failed, close the menu.
- // A menu doesn't close itself because EndMenuBar() wants the catch the last Left<>Right inputs.
- // However, it means that with the current code, a BeginMenu() from outside another menu or a menu-bar won't be closable with the Left direction.
- ImGuiContext& g = *GImGui;
- ImGuiWindow* window = g.CurrentWindow;
- if (g.NavWindow && g.NavWindow->ParentWindow == window && g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet() && window->DC.LayoutType == ImGuiLayoutType_Vertical)
+ else
{
- ClosePopupToLevel(g.OpenPopupStack.Size - 1);
- NavMoveRequestCancel();
+ // Forwarding previous request (which has been modified, e.g. wrap around menus rewrite the requests with a starting rectangle at the other side of the window)
+ // (Preserve g.NavMoveRequestFlags, g.NavMoveClipDir which were set by the NavMoveRequestForward() function)
+ IM_ASSERT(g.NavMoveDir != ImGuiDir_None && g.NavMoveClipDir != ImGuiDir_None);
+ IM_ASSERT(g.NavMoveRequestForward == ImGuiNavForward_ForwardQueued);
+ g.NavMoveRequestForward = ImGuiNavForward_ForwardActive;
}
- EndPopup();
-}
-
-// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
-void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags)
-{
- ImGuiContext& g = *GImGui;
-
- int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]);
- BeginTooltipEx(0, true);
+ // Update PageUp/PageDown scroll
+ float nav_scoring_rect_offset_y = 0.0f;
+ if (nav_keyboard_active)
+ nav_scoring_rect_offset_y = NavUpdatePageUpPageDown(allowed_dir_flags);
- const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text;
- if (text_end > text)
+ // If we initiate a movement request and have no current NavId, we initiate a InitDefautRequest that will be used as a fallback if the direction fails to find a match
+ if (g.NavMoveDir != ImGuiDir_None)
{
- TextUnformatted(text, text_end);
- Separator();
+ g.NavMoveRequest = true;
+ g.NavMoveDirLast = g.NavMoveDir;
}
+ if (g.NavMoveRequest && g.NavId == 0)
+ {
+ g.NavInitRequest = g.NavInitRequestFromMove = true;
+ g.NavInitResultId = 0;
+ g.NavDisableHighlight = false;
+ }
+ NavUpdateAnyRequestFlag();
- ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2);
- ColorButton("##preview", ImVec4(col[0], col[1], col[2], col[3]), (flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz);
- SameLine();
- if (flags & ImGuiColorEditFlags_NoAlpha)
- Text("#%02X%02X%02X\nR: %d, G: %d, B: %d\n(%.3f, %.3f, %.3f)", cr, cg, cb, cr, cg, cb, col[0], col[1], col[2]);
- else
- Text("#%02X%02X%02X%02X\nR:%d, G:%d, B:%d, A:%d\n(%.3f, %.3f, %.3f, %.3f)", cr, cg, cb, ca, cr, cg, cb, ca, col[0], col[1], col[2], col[3]);
- EndTooltip();
-}
-
-static inline ImU32 ImAlphaBlendColor(ImU32 col_a, ImU32 col_b)
-{
- float t = ((col_b >> IM_COL32_A_SHIFT) & 0xFF) / 255.f;
- int r = ImLerp((int)(col_a >> IM_COL32_R_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_R_SHIFT) & 0xFF, t);
- int g = ImLerp((int)(col_a >> IM_COL32_G_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_G_SHIFT) & 0xFF, t);
- int b = ImLerp((int)(col_a >> IM_COL32_B_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_B_SHIFT) & 0xFF, t);
- return IM_COL32(r, g, b, 0xFF);
-}
-
-// NB: This is rather brittle and will show artifact when rounding this enabled if rounded corners overlap multiple cells. Caller currently responsible for avoiding that.
-// I spent a non reasonable amount of time trying to getting this right for ColorButton with rounding+anti-aliasing+ImGuiColorEditFlags_HalfAlphaPreview flag + various grid sizes and offsets, and eventually gave up... probably more reasonable to disable rounding alltogether.
-void ImGui::RenderColorRectWithAlphaCheckerboard(ImVec2 p_min, ImVec2 p_max, ImU32 col, float grid_step, ImVec2 grid_off, float rounding, int rounding_corners_flags)
-{
- ImGuiWindow* window = GetCurrentWindow();
- if (((col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) < 0xFF)
+ // Scrolling
+ if (g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget)
{
- ImU32 col_bg1 = GetColorU32(ImAlphaBlendColor(IM_COL32(204,204,204,255), col));
- ImU32 col_bg2 = GetColorU32(ImAlphaBlendColor(IM_COL32(128,128,128,255), col));
- window->DrawList->AddRectFilled(p_min, p_max, col_bg1, rounding, rounding_corners_flags);
+ // *Fallback* manual-scroll with Nav directional keys when window has no navigable item
+ ImGuiWindow* window = g.NavWindow;
+ const float scroll_speed = ImFloor(window->CalcFontSize() * 100 * g.IO.DeltaTime + 0.5f); // We need round the scrolling speed because sub-pixel scroll isn't reliably supported.
+ if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll && g.NavMoveRequest)
+ {
+ if (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right)
+ SetWindowScrollX(window, ImFloor(window->Scroll.x + ((g.NavMoveDir == ImGuiDir_Left) ? -1.0f : +1.0f) * scroll_speed));
+ if (g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down)
+ SetWindowScrollY(window, ImFloor(window->Scroll.y + ((g.NavMoveDir == ImGuiDir_Up) ? -1.0f : +1.0f) * scroll_speed));
+ }
- int yi = 0;
- for (float y = p_min.y + grid_off.y; y < p_max.y; y += grid_step, yi++)
+ // *Normal* Manual scroll with NavScrollXXX keys
+ // Next movement request will clamp the NavId reference rectangle to the visible area, so navigation will resume within those bounds.
+ ImVec2 scroll_dir = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down, 1.0f/10.0f, 10.0f);
+ if (scroll_dir.x != 0.0f && window->ScrollbarX)
{
- float y1 = ImClamp(y, p_min.y, p_max.y), y2 = ImMin(y + grid_step, p_max.y);
- if (y2 <= y1)
- continue;
- for (float x = p_min.x + grid_off.x + (yi & 1) * grid_step; x < p_max.x; x += grid_step * 2.0f)
- {
- float x1 = ImClamp(x, p_min.x, p_max.x), x2 = ImMin(x + grid_step, p_max.x);
- if (x2 <= x1)
- continue;
- int rounding_corners_flags_cell = 0;
- if (y1 <= p_min.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopRight; }
- if (y2 >= p_max.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotRight; }
- rounding_corners_flags_cell &= rounding_corners_flags;
- window->DrawList->AddRectFilled(ImVec2(x1,y1), ImVec2(x2,y2), col_bg2, rounding_corners_flags_cell ? rounding : 0.0f, rounding_corners_flags_cell);
- }
+ SetWindowScrollX(window, ImFloor(window->Scroll.x + scroll_dir.x * scroll_speed));
+ g.NavMoveFromClampedRefRect = true;
+ }
+ if (scroll_dir.y != 0.0f)
+ {
+ SetWindowScrollY(window, ImFloor(window->Scroll.y + scroll_dir.y * scroll_speed));
+ g.NavMoveFromClampedRefRect = true;
}
}
- else
+
+ // Reset search results
+ g.NavMoveResultLocal.Clear();
+ g.NavMoveResultLocalVisibleSet.Clear();
+ g.NavMoveResultOther.Clear();
+
+ // When we have manually scrolled (without using navigation) and NavId becomes out of bounds, we project its bounding box to the visible area to restart navigation within visible items
+ if (g.NavMoveRequest && g.NavMoveFromClampedRefRect && g.NavLayer == 0)
{
- window->DrawList->AddRectFilled(p_min, p_max, col, rounding, rounding_corners_flags);
+ ImGuiWindow* window = g.NavWindow;
+ ImRect window_rect_rel(window->InnerMainRect.Min - window->Pos - ImVec2(1,1), window->InnerMainRect.Max - window->Pos + ImVec2(1,1));
+ if (!window_rect_rel.Contains(window->NavRectRel[g.NavLayer]))
+ {
+ float pad = window->CalcFontSize() * 0.5f;
+ window_rect_rel.Expand(ImVec2(-ImMin(window_rect_rel.GetWidth(), pad), -ImMin(window_rect_rel.GetHeight(), pad))); // Terrible approximation for the intent of starting navigation from first fully visible item
+ window->NavRectRel[g.NavLayer].ClipWith(window_rect_rel);
+ g.NavId = 0;
+ }
+ g.NavMoveFromClampedRefRect = false;
}
-}
-void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags)
-{
- ImGuiContext& g = *GImGui;
- if ((flags & ImGuiColorEditFlags__InputsMask) == 0)
- flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__InputsMask;
- if ((flags & ImGuiColorEditFlags__DataTypeMask) == 0)
- flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__DataTypeMask;
- if ((flags & ImGuiColorEditFlags__PickerMask) == 0)
- flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__PickerMask;
- IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__InputsMask))); // Check only 1 option is selected
- IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__DataTypeMask))); // Check only 1 option is selected
- IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check only 1 option is selected
- g.ColorEditOptions = flags;
+ // For scoring we use a single segment on the left side our current item bounding box (not touching the edge to avoid box overlap with zero-spaced items)
+ ImRect nav_rect_rel = (g.NavWindow && !g.NavWindow->NavRectRel[g.NavLayer].IsInverted()) ? g.NavWindow->NavRectRel[g.NavLayer] : ImRect(0,0,0,0);
+ g.NavScoringRectScreen = g.NavWindow ? ImRect(g.NavWindow->Pos + nav_rect_rel.Min, g.NavWindow->Pos + nav_rect_rel.Max) : GetViewportRect();
+ g.NavScoringRectScreen.TranslateY(nav_scoring_rect_offset_y);
+ g.NavScoringRectScreen.Min.x = ImMin(g.NavScoringRectScreen.Min.x + 1.0f, g.NavScoringRectScreen.Max.x);
+ g.NavScoringRectScreen.Max.x = g.NavScoringRectScreen.Min.x;
+ IM_ASSERT(!g.NavScoringRectScreen.IsInverted()); // Ensure if we have a finite, non-inverted bounding box here will allows us to remove extraneous ImFabs() calls in NavScoreItem().
+ //g.OverlayDrawList.AddRect(g.NavScoringRectScreen.Min, g.NavScoringRectScreen.Max, IM_COL32(255,200,0,255)); // [DEBUG]
+ g.NavScoringCount = 0;
+#if IMGUI_DEBUG_NAV_RECTS
+ if (g.NavWindow) { for (int layer = 0; layer < 2; layer++) GetOverlayDrawList()->AddRect(g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Min, g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Max, IM_COL32(255,200,0,255)); } // [DEBUG]
+ if (g.NavWindow) { ImU32 col = (g.NavWindow->HiddenFrames == 0) ? IM_COL32(255,0,255,255) : IM_COL32(255,0,0,255); ImVec2 p = NavCalcPreferredRefPos(); char buf[32]; ImFormatString(buf, 32, "%d", g.NavLayer); g.OverlayDrawList.AddCircleFilled(p, 3.0f, col); g.OverlayDrawList.AddText(NULL, 13.0f, p + ImVec2(8,-4), col, buf); }
+#endif
}
-// A little colored square. Return true when clicked.
-// FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip.
-// 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip.
-bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, ImVec2 size)
+static void ImGui::NavUpdateMoveResult()
{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
-
+ // Select which result to use
ImGuiContext& g = *GImGui;
- const ImGuiID id = window->GetID(desc_id);
- float default_size = GetFrameHeight();
- if (size.x == 0.0f)
- size.x = default_size;
- if (size.y == 0.0f)
- size.y = default_size;
- const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
- ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
- if (!ItemAdd(bb, id))
- return false;
+ ImGuiNavMoveResult* result = (g.NavMoveResultLocal.ID != 0) ? &g.NavMoveResultLocal : &g.NavMoveResultOther;
- bool hovered, held;
- bool pressed = ButtonBehavior(bb, id, &hovered, &held);
+ // PageUp/PageDown behavior first jumps to the bottom/top mostly visible item, _otherwise_ use the result from the previous/next page.
+ if (g.NavMoveRequestFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet)
+ if (g.NavMoveResultLocalVisibleSet.ID != 0 && g.NavMoveResultLocalVisibleSet.ID != g.NavId)
+ result = &g.NavMoveResultLocalVisibleSet;
- if (flags & ImGuiColorEditFlags_NoAlpha)
- flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf);
+ // Maybe entering a flattened child from the outside? In this case solve the tie using the regular scoring rules.
+ if (result != &g.NavMoveResultOther && g.NavMoveResultOther.ID != 0 && g.NavMoveResultOther.Window->ParentWindow == g.NavWindow)
+ if ((g.NavMoveResultOther.DistBox < result->DistBox) || (g.NavMoveResultOther.DistBox == result->DistBox && g.NavMoveResultOther.DistCenter < result->DistCenter))
+ result = &g.NavMoveResultOther;
+ IM_ASSERT(g.NavWindow && result->Window);
- ImVec4 col_without_alpha(col.x, col.y, col.z, 1.0f);
- float grid_step = ImMin(size.x, size.y) / 2.99f;
- float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f);
- ImRect bb_inner = bb;
- float off = -0.75f; // The border (using Col_FrameBg) tends to look off when color is near-opaque and rounding is enabled. This offset seemed like a good middle ground to reduce those artifacts.
- bb_inner.Expand(off);
- if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col.w < 1.0f)
- {
- float mid_x = (float)(int)((bb_inner.Min.x + bb_inner.Max.x) * 0.5f + 0.5f);
- RenderColorRectWithAlphaCheckerboard(ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawCornerFlags_TopRight| ImDrawCornerFlags_BotRight);
- window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_without_alpha), rounding, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotLeft);
- }
- else
- {
- // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha
- ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col : col_without_alpha;
- if (col_source.w < 1.0f)
- RenderColorRectWithAlphaCheckerboard(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding);
- else
- window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding, ImDrawCornerFlags_All);
- }
- RenderNavHighlight(bb, id);
- if (g.Style.FrameBorderSize > 0.0f)
- RenderFrameBorder(bb.Min, bb.Max, rounding);
- else
- window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border
-
- // Drag and Drop Source
- // NB: The ActiveId test is merely an optional micro-optimization, BeginDragDropSource() does the same test.
- if (g.ActiveId == id && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropSource())
+ // Scroll to keep newly navigated item fully into view.
+ if (g.NavLayer == 0)
{
- if (flags & ImGuiColorEditFlags_NoAlpha)
- SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col, sizeof(float) * 3, ImGuiCond_Once);
- else
- SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col, sizeof(float) * 4, ImGuiCond_Once);
- ColorButton(desc_id, col, flags);
- SameLine();
- TextUnformatted("Color");
- EndDragDropSource();
- }
-
- // Tooltip
- if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered)
- ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf));
-
- if (pressed)
- MarkItemEdited(id);
-
- return pressed;
-}
+ ImRect rect_abs = ImRect(result->RectRel.Min + result->Window->Pos, result->RectRel.Max + result->Window->Pos);
+ NavScrollToBringItemIntoView(result->Window, rect_abs);
-bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags)
-{
- return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha);
-}
+ // Estimate upcoming scroll so we can offset our result position so mouse position can be applied immediately after in NavUpdate()
+ ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(result->Window, false);
+ ImVec2 delta_scroll = result->Window->Scroll - next_scroll;
+ result->RectRel.Translate(delta_scroll);
-void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags)
-{
- bool allow_opt_inputs = !(flags & ImGuiColorEditFlags__InputsMask);
- bool allow_opt_datatype = !(flags & ImGuiColorEditFlags__DataTypeMask);
- if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context"))
- return;
- ImGuiContext& g = *GImGui;
- ImGuiColorEditFlags opts = g.ColorEditOptions;
- if (allow_opt_inputs)
- {
- if (RadioButton("RGB", (opts & ImGuiColorEditFlags_RGB) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_RGB;
- if (RadioButton("HSV", (opts & ImGuiColorEditFlags_HSV) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HSV;
- if (RadioButton("HEX", (opts & ImGuiColorEditFlags_HEX) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HEX;
- }
- if (allow_opt_datatype)
- {
- if (allow_opt_inputs) Separator();
- if (RadioButton("0..255", (opts & ImGuiColorEditFlags_Uint8) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Uint8;
- if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Float;
+ // Also scroll parent window to keep us into view if necessary (we could/should technically recurse back the whole the parent hierarchy).
+ if (result->Window->Flags & ImGuiWindowFlags_ChildWindow)
+ NavScrollToBringItemIntoView(result->Window->ParentWindow, ImRect(rect_abs.Min + delta_scroll, rect_abs.Max + delta_scroll));
}
- if (allow_opt_inputs || allow_opt_datatype)
- Separator();
- if (Button("Copy as..", ImVec2(-1,0)))
- OpenPopup("Copy");
- if (BeginPopup("Copy"))
- {
- int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]);
- char buf[64];
- ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
- if (Selectable(buf))
- SetClipboardText(buf);
- ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca);
- if (Selectable(buf))
- SetClipboardText(buf);
- if (flags & ImGuiColorEditFlags_NoAlpha)
- ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X", cr, cg, cb);
- else
- ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X%02X", cr, cg, cb, ca);
- if (Selectable(buf))
- SetClipboardText(buf);
- EndPopup();
- }
-
- g.ColorEditOptions = opts;
- EndPopup();
+ // Apply result from previous frame navigation directional move request
+ ClearActiveID();
+ g.NavWindow = result->Window;
+ SetNavIDWithRectRel(result->ID, g.NavLayer, result->RectRel);
+ g.NavJustMovedToId = result->ID;
+ g.NavMoveFromClampedRefRect = false;
}
-void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags)
+static float ImGui::NavUpdatePageUpPageDown(int allowed_dir_flags)
{
- bool allow_opt_picker = !(flags & ImGuiColorEditFlags__PickerMask);
- bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar);
- if ((!allow_opt_picker && !allow_opt_alpha_bar) || !ImGui::BeginPopup("context"))
- return;
ImGuiContext& g = *GImGui;
- if (allow_opt_picker)
- {
- ImVec2 picker_size(g.FontSize * 8, ImMax(g.FontSize * 8 - (ImGui::GetFrameHeight() + g.Style.ItemInnerSpacing.x), 1.0f)); // FIXME: Picker size copied from main picker function
- ImGui::PushItemWidth(picker_size.x);
- for (int picker_type = 0; picker_type < 2; picker_type++)
- {
- // Draw small/thumbnail version of each picker type (over an invisible button for selection)
- if (picker_type > 0) ImGui::Separator();
- ImGui::PushID(picker_type);
- ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs|ImGuiColorEditFlags_NoOptions|ImGuiColorEditFlags_NoLabel|ImGuiColorEditFlags_NoSidePreview|(flags & ImGuiColorEditFlags_NoAlpha);
- if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar;
- if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel;
- ImVec2 backup_pos = ImGui::GetCursorScreenPos();
- if (ImGui::Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup
- g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags__PickerMask) | (picker_flags & ImGuiColorEditFlags__PickerMask);
- ImGui::SetCursorScreenPos(backup_pos);
- ImVec4 dummy_ref_col;
- memcpy(&dummy_ref_col.x, ref_col, sizeof(float) * (picker_flags & ImGuiColorEditFlags_NoAlpha ? 3 : 4));
- ImGui::ColorPicker4("##dummypicker", &dummy_ref_col.x, picker_flags);
- ImGui::PopID();
- }
- ImGui::PopItemWidth();
- }
- if (allow_opt_alpha_bar)
+ if (g.NavMoveDir == ImGuiDir_None && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget && g.NavLayer == 0)
{
- if (allow_opt_picker) ImGui::Separator();
- ImGui::CheckboxFlags("Alpha Bar", (unsigned int*)&g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar);
- }
- ImGui::EndPopup();
-}
-
-// Edit colors components (each component in 0.0f..1.0f range).
-// See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
-// With typical options: Left-click on colored square to open color picker. Right-click to open option menu. CTRL-Click over input fields to edit them and TAB to go to next item.
-bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags)
-{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return false;
-
- ImGuiContext& g = *GImGui;
- const ImGuiStyle& style = g.Style;
- const float square_sz = GetFrameHeight();
- const float w_extra = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x);
- const float w_items_all = CalcItemWidth() - w_extra;
- const char* label_display_end = FindRenderedTextEnd(label);
-
- BeginGroup();
- PushID(label);
-
- // If we're not showing any slider there's no point in doing any HSV conversions
- const ImGuiColorEditFlags flags_untouched = flags;
- if (flags & ImGuiColorEditFlags_NoInputs)
- flags = (flags & (~ImGuiColorEditFlags__InputsMask)) | ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_NoOptions;
-
- // Context menu: display and modify options (before defaults are applied)
- if (!(flags & ImGuiColorEditFlags_NoOptions))
- ColorEditOptionsPopup(col, flags);
-
- // Read stored options
- if (!(flags & ImGuiColorEditFlags__InputsMask))
- flags |= (g.ColorEditOptions & ImGuiColorEditFlags__InputsMask);
- if (!(flags & ImGuiColorEditFlags__DataTypeMask))
- flags |= (g.ColorEditOptions & ImGuiColorEditFlags__DataTypeMask);
- if (!(flags & ImGuiColorEditFlags__PickerMask))
- flags |= (g.ColorEditOptions & ImGuiColorEditFlags__PickerMask);
- flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask));
-
- const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0;
- const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0;
- const int components = alpha ? 4 : 3;
-
- // Convert to the formats we need
- float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f };
- if (flags & ImGuiColorEditFlags_HSV)
- ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
- int i[4] = { IM_F32_TO_INT8_UNBOUND(f[0]), IM_F32_TO_INT8_UNBOUND(f[1]), IM_F32_TO_INT8_UNBOUND(f[2]), IM_F32_TO_INT8_UNBOUND(f[3]) };
-
- bool value_changed = false;
- bool value_changed_as_float = false;
-
- if ((flags & (ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_HSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
- {
- // RGB/HSV 0..255 Sliders
- const float w_item_one = ImMax(1.0f, (float)(int)((w_items_all - (style.ItemInnerSpacing.x) * (components-1)) / (float)components));
- const float w_item_last = ImMax(1.0f, (float)(int)(w_items_all - (w_item_one + style.ItemInnerSpacing.x) * (components-1)));
-
- const bool hide_prefix = (w_item_one <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x);
- const char* ids[4] = { "##X", "##Y", "##Z", "##W" };
- const char* fmt_table_int[3][4] =
- {
- { "%3d", "%3d", "%3d", "%3d" }, // Short display
- { "R:%3d", "G:%3d", "B:%3d", "A:%3d" }, // Long display for RGBA
- { "H:%3d", "S:%3d", "V:%3d", "A:%3d" } // Long display for HSVA
- };
- const char* fmt_table_float[3][4] =
- {
- { "%0.3f", "%0.3f", "%0.3f", "%0.3f" }, // Short display
- { "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA
- { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" } // Long display for HSVA
- };
- const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_HSV) ? 2 : 1;
-
- PushItemWidth(w_item_one);
- for (int n = 0; n < components; n++)
- {
- if (n > 0)
- SameLine(0, style.ItemInnerSpacing.x);
- if (n + 1 == components)
- PushItemWidth(w_item_last);
- if (flags & ImGuiColorEditFlags_Float)
- value_changed = value_changed_as_float = value_changed | DragFloat(ids[n], &f[n], 1.0f/255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]);
- else
- value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]);
- if (!(flags & ImGuiColorEditFlags_NoOptions))
- OpenPopupOnItemClick("context");
- }
- PopItemWidth();
- PopItemWidth();
- }
- else if ((flags & ImGuiColorEditFlags_HEX) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
- {
- // RGB Hexadecimal Input
- char buf[64];
- if (alpha)
- ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255), ImClamp(i[3],0,255));
- else
- ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255));
- PushItemWidth(w_items_all);
- if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase))
- {
- value_changed = true;
- char* p = buf;
- while (*p == '#' || ImCharIsBlankA(*p))
- p++;
- i[0] = i[1] = i[2] = i[3] = 0;
- if (alpha)
- sscanf(p, "%02X%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2], (unsigned int*)&i[3]); // Treat at unsigned (%X is unsigned)
- else
- sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]);
- }
- if (!(flags & ImGuiColorEditFlags_NoOptions))
- OpenPopupOnItemClick("context");
- PopItemWidth();
- }
-
- ImGuiWindow* picker_active_window = NULL;
- if (!(flags & ImGuiColorEditFlags_NoSmallPreview))
- {
- if (!(flags & ImGuiColorEditFlags_NoInputs))
- SameLine(0, style.ItemInnerSpacing.x);
-
- const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f);
- if (ColorButton("##ColorButton", col_v4, flags))
+ ImGuiWindow* window = g.NavWindow;
+ bool page_up_held = IsKeyDown(g.IO.KeyMap[ImGuiKey_PageUp]) && (allowed_dir_flags & (1 << ImGuiDir_Up));
+ bool page_down_held = IsKeyDown(g.IO.KeyMap[ImGuiKey_PageDown]) && (allowed_dir_flags & (1 << ImGuiDir_Down));
+ if ((page_up_held && !page_down_held) || (page_down_held && !page_up_held))
{
- if (!(flags & ImGuiColorEditFlags_NoPicker))
+ if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll)
{
- // Store current color and open a picker
- g.ColorPickerRef = col_v4;
- OpenPopup("picker");
- SetNextWindowPos(window->DC.LastItemRect.GetBL() + ImVec2(-1,style.ItemSpacing.y));
+ // Fallback manual-scroll when window has no navigable item
+ if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageUp], true))
+ SetWindowScrollY(window, window->Scroll.y - window->InnerClipRect.GetHeight());
+ else if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageDown], true))
+ SetWindowScrollY(window, window->Scroll.y + window->InnerClipRect.GetHeight());
}
- }
- if (!(flags & ImGuiColorEditFlags_NoOptions))
- OpenPopupOnItemClick("context");
-
- if (BeginPopup("picker"))
- {
- picker_active_window = g.CurrentWindow;
- if (label != label_display_end)
+ else
{
- TextUnformatted(label, label_display_end);
- Separator();
+ const ImRect& nav_rect_rel = window->NavRectRel[g.NavLayer];
+ const float page_offset_y = ImMax(0.0f, window->InnerClipRect.GetHeight() - window->CalcFontSize() * 1.0f + nav_rect_rel.GetHeight());
+ float nav_scoring_rect_offset_y = 0.0f;
+ if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageUp], true))
+ {
+ nav_scoring_rect_offset_y = -page_offset_y;
+ g.NavMoveDir = ImGuiDir_Down; // Because our scoring rect is offset, we intentionally request the opposite direction (so we can always land on the last item)
+ g.NavMoveClipDir = ImGuiDir_Up;
+ g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet;
+ }
+ else if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageDown], true))
+ {
+ nav_scoring_rect_offset_y = +page_offset_y;
+ g.NavMoveDir = ImGuiDir_Up; // Because our scoring rect is offset, we intentionally request the opposite direction (so we can always land on the last item)
+ g.NavMoveClipDir = ImGuiDir_Down;
+ g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet;
+ }
+ return nav_scoring_rect_offset_y;
}
- ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar;
- ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf;
- PushItemWidth(square_sz * 12.0f); // Use 256 + bar sizes?
- value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x);
- PopItemWidth();
- EndPopup();
- }
- }
-
- if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel))
- {
- SameLine(0, style.ItemInnerSpacing.x);
- TextUnformatted(label, label_display_end);
- }
-
- // Convert back
- if (picker_active_window == NULL)
- {
- if (!value_changed_as_float)
- for (int n = 0; n < 4; n++)
- f[n] = i[n] / 255.0f;
- if (flags & ImGuiColorEditFlags_HSV)
- ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
- if (value_changed)
- {
- col[0] = f[0];
- col[1] = f[1];
- col[2] = f[2];
- if (alpha)
- col[3] = f[3];
}
}
-
- PopID();
- EndGroup();
-
- // Drag and Drop Target
- // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test.
- if ((window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget())
- {
- if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
- {
- memcpy((float*)col, payload->Data, sizeof(float) * 3);
- value_changed = true;
- }
- if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
- {
- memcpy((float*)col, payload->Data, sizeof(float) * components);
- value_changed = true;
- }
- EndDragDropTarget();
- }
-
- // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4().
- if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window)
- window->DC.LastItemId = g.ActiveId;
-
- if (value_changed)
- MarkItemEdited(window->DC.LastItemId);
-
- return value_changed;
+ return 0.0f;
}
-bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags)
+static int FindWindowIndex(ImGuiWindow* window) // FIXME-OPT O(N)
{
- float col4[4] = { col[0], col[1], col[2], 1.0f };
- if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha))
- return false;
- col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2];
- return true;
+ ImGuiContext& g = *GImGui;
+ for (int i = g.Windows.Size-1; i >= 0; i--)
+ if (g.Windows[i] == window)
+ return i;
+ return -1;
}
-static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w)
+static ImGuiWindow* FindWindowNavFocusable(int i_start, int i_stop, int dir) // FIXME-OPT O(N)
{
- ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x + 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Right, IM_COL32_BLACK);
- ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x, pos.y), half_sz, ImGuiDir_Right, IM_COL32_WHITE);
- ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x - 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Left, IM_COL32_BLACK);
- ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x, pos.y), half_sz, ImGuiDir_Left, IM_COL32_WHITE);
+ ImGuiContext& g = *GImGui;
+ for (int i = i_start; i >= 0 && i < g.Windows.Size && i != i_stop; i += dir)
+ if (ImGui::IsWindowNavFocusable(g.Windows[i]))
+ return g.Windows[i];
+ return NULL;
}
-// ColorPicker
-// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
-// FIXME: we adjust the big color square height based on item width, which may cause a flickering feedback loop (if automatic height makes a vertical scrollbar appears, affecting automatic width..)
-bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col)
+static void NavUpdateWindowingHighlightWindow(int focus_change_dir)
{
ImGuiContext& g = *GImGui;
- ImGuiWindow* window = GetCurrentWindow();
- ImDrawList* draw_list = window->DrawList;
+ IM_ASSERT(g.NavWindowingTarget);
+ if (g.NavWindowingTarget->Flags & ImGuiWindowFlags_Modal)
+ return;
- ImGuiStyle& style = g.Style;
- ImGuiIO& io = g.IO;
-
- PushID(label);
- BeginGroup();
-
- if (!(flags & ImGuiColorEditFlags_NoSidePreview))
- flags |= ImGuiColorEditFlags_NoSmallPreview;
-
- // Context menu: display and store options.
- if (!(flags & ImGuiColorEditFlags_NoOptions))
-
- // Read stored options
- if (!(flags & ImGuiColorEditFlags__PickerMask))
- flags |= ((g.ColorEditOptions & ImGuiColorEditFlags__PickerMask) ? g.ColorEditOptions : ImGuiColorEditFlags__OptionsDefault) & ImGuiColorEditFlags__PickerMask;
- IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check that only 1 is selected
- if (!(flags & ImGuiColorEditFlags_NoOptions))
- flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar);
-
- // Setup
- int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4;
- bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha);
- ImVec2 picker_pos = window->DC.CursorPos;
- float square_sz = GetFrameHeight();
- float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars
- float sv_picker_size = ImMax(bars_width * 1, CalcItemWidth() - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box
- float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x;
- float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x;
- float bars_triangles_half_sz = (float)(int)(bars_width * 0.20f);
-
- float backup_initial_col[4];
- memcpy(backup_initial_col, col, components * sizeof(float));
-
- float wheel_thickness = sv_picker_size * 0.08f;
- float wheel_r_outer = sv_picker_size * 0.50f;
- float wheel_r_inner = wheel_r_outer - wheel_thickness;
- ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size*0.5f);
-
- // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic.
- float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f);
- ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point.
- ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point.
- ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point.
-
- float H,S,V;
- ColorConvertRGBtoHSV(col[0], col[1], col[2], H, S, V);
-
- bool value_changed = false, value_changed_h = false, value_changed_sv = false;
-
- PushItemFlag(ImGuiItemFlags_NoNav, true);
- if (flags & ImGuiColorEditFlags_PickerHueWheel)
- {
- // Hue wheel + SV triangle logic
- InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size));
- if (IsItemActive())
- {
- ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center;
- ImVec2 current_off = g.IO.MousePos - wheel_center;
- float initial_dist2 = ImLengthSqr(initial_off);
- if (initial_dist2 >= (wheel_r_inner-1)*(wheel_r_inner-1) && initial_dist2 <= (wheel_r_outer+1)*(wheel_r_outer+1))
- {
- // Interactive with Hue wheel
- H = ImAtan2(current_off.y, current_off.x) / IM_PI*0.5f;
- if (H < 0.0f)
- H += 1.0f;
- value_changed = value_changed_h = true;
- }
- float cos_hue_angle = ImCos(-H * 2.0f * IM_PI);
- float sin_hue_angle = ImSin(-H * 2.0f * IM_PI);
- if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle)))
- {
- // Interacting with SV triangle
- ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle);
- if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated))
- current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated);
- float uu, vv, ww;
- ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww);
- V = ImClamp(1.0f - vv, 0.0001f, 1.0f);
- S = ImClamp(uu / V, 0.0001f, 1.0f);
- value_changed = value_changed_sv = true;
- }
- }
- if (!(flags & ImGuiColorEditFlags_NoOptions))
- OpenPopupOnItemClick("context");
- }
- else if (flags & ImGuiColorEditFlags_PickerHueBar)
- {
- // SV rectangle logic
- InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size));
- if (IsItemActive())
- {
- S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size-1));
- V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
- value_changed = value_changed_sv = true;
- }
- if (!(flags & ImGuiColorEditFlags_NoOptions))
- OpenPopupOnItemClick("context");
+ const int i_current = FindWindowIndex(g.NavWindowingTarget);
+ ImGuiWindow* window_target = FindWindowNavFocusable(i_current + focus_change_dir, -INT_MAX, focus_change_dir);
+ if (!window_target)
+ window_target = FindWindowNavFocusable((focus_change_dir < 0) ? (g.Windows.Size - 1) : 0, i_current, focus_change_dir);
+ if (window_target) // Don't reset windowing target if there's a single window in the list
+ g.NavWindowingTarget = g.NavWindowingTargetAnim = window_target;
+ g.NavWindowingToggleLayer = false;
+}
- // Hue bar logic
- SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y));
- InvisibleButton("hue", ImVec2(bars_width, sv_picker_size));
- if (IsItemActive())
- {
- H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
- value_changed = value_changed_h = true;
- }
- }
+// Window management mode (hold to: change focus/move/resize, tap to: toggle menu layer)
+static void ImGui::NavUpdateWindowing()
+{
+ ImGuiContext& g = *GImGui;
+ ImGuiWindow* apply_focus_window = NULL;
+ bool apply_toggle_layer = false;
- // Alpha bar logic
- if (alpha_bar)
+ ImGuiWindow* modal_window = GetFrontMostPopupModal();
+ if (modal_window != NULL)
{
- SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y));
- InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size));
- if (IsItemActive())
- {
- col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
- value_changed = true;
- }
+ g.NavWindowingTarget = NULL;
+ return;
}
- PopItemFlag(); // ImGuiItemFlags_NoNav
- if (!(flags & ImGuiColorEditFlags_NoSidePreview))
+ // Fade out
+ if (g.NavWindowingTargetAnim && g.NavWindowingTarget == NULL)
{
- SameLine(0, style.ItemInnerSpacing.x);
- BeginGroup();
+ g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha - g.IO.DeltaTime * 10.0f, 0.0f);
+ if (g.DimBgRatio <= 0.0f && g.NavWindowingHighlightAlpha <= 0.0f)
+ g.NavWindowingTargetAnim = NULL;
}
- if (!(flags & ImGuiColorEditFlags_NoLabel))
- {
- const char* label_display_end = FindRenderedTextEnd(label);
- if (label != label_display_end)
+ // Start CTRL-TAB or Square+L/R window selection
+ bool start_windowing_with_gamepad = !g.NavWindowingTarget && IsNavInputPressed(ImGuiNavInput_Menu, ImGuiInputReadMode_Pressed);
+ bool start_windowing_with_keyboard = !g.NavWindowingTarget && g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab) && (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard);
+ if (start_windowing_with_gamepad || start_windowing_with_keyboard)
+ if (ImGuiWindow* window = g.NavWindow ? g.NavWindow : FindWindowNavFocusable(g.Windows.Size - 1, -INT_MAX, -1))
{
- if ((flags & ImGuiColorEditFlags_NoSidePreview))
- SameLine(0, style.ItemInnerSpacing.x);
- TextUnformatted(label, label_display_end);
+ g.NavWindowingTarget = g.NavWindowingTargetAnim = window;
+ g.NavWindowingTimer = g.NavWindowingHighlightAlpha = 0.0f;
+ g.NavWindowingToggleLayer = start_windowing_with_keyboard ? false : true;
+ g.NavInputSource = start_windowing_with_keyboard ? ImGuiInputSource_NavKeyboard : ImGuiInputSource_NavGamepad;
}
- }
- if (!(flags & ImGuiColorEditFlags_NoSidePreview))
+ // Gamepad update
+ g.NavWindowingTimer += g.IO.DeltaTime;
+ if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_NavGamepad)
{
- PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);
- ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
- if ((flags & ImGuiColorEditFlags_NoLabel))
- Text("Current");
- ColorButton("##current", col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2));
- if (ref_col != NULL)
+ // Highlight only appears after a brief time holding the button, so that a fast tap on PadMenu (to toggle NavLayer) doesn't add visual noise
+ g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f));
+
+ // Select window to focus
+ const int focus_change_dir = (int)IsNavInputPressed(ImGuiNavInput_FocusPrev, ImGuiInputReadMode_RepeatSlow) - (int)IsNavInputPressed(ImGuiNavInput_FocusNext, ImGuiInputReadMode_RepeatSlow);
+ if (focus_change_dir != 0)
{
- Text("Original");
- ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]);
- if (ColorButton("##original", ref_col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2)))
- {
- memcpy(col, ref_col, components * sizeof(float));
- value_changed = true;
- }
+ NavUpdateWindowingHighlightWindow(focus_change_dir);
+ g.NavWindowingHighlightAlpha = 1.0f;
}
- PopItemFlag();
- EndGroup();
- }
- // Convert back color to RGB
- if (value_changed_h || value_changed_sv)
- ColorConvertHSVtoRGB(H >= 1.0f ? H - 10 * 1e-6f : H, S > 0.0f ? S : 10*1e-6f, V > 0.0f ? V : 1e-6f, col[0], col[1], col[2]);
-
- // R,G,B and H,S,V slider color editor
- bool value_changed_fix_hue_wrap = false;
- if ((flags & ImGuiColorEditFlags_NoInputs) == 0)
- {
- PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x);
- ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf;
- ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker;
- if (flags & ImGuiColorEditFlags_RGB || (flags & ImGuiColorEditFlags__InputsMask) == 0)
- if (ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_RGB))
- {
- // FIXME: Hackily differenciating using the DragInt (ActiveId != 0 && !ActiveIdAllowOverlap) vs. using the InputText or DropTarget.
- // For the later we don't want to run the hue-wrap canceling code. If you are well versed in HSV picker please provide your input! (See #2050)
- value_changed_fix_hue_wrap = (g.ActiveId != 0 && !g.ActiveIdAllowOverlap);
- value_changed = true;
- }
- if (flags & ImGuiColorEditFlags_HSV || (flags & ImGuiColorEditFlags__InputsMask) == 0)
- value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_HSV);
- if (flags & ImGuiColorEditFlags_HEX || (flags & ImGuiColorEditFlags__InputsMask) == 0)
- value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_HEX);
- PopItemWidth();
- }
-
- // Try to cancel hue wrap (after ColorEdit4 call), if any
- if (value_changed_fix_hue_wrap)
- {
- float new_H, new_S, new_V;
- ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V);
- if (new_H <= 0 && H > 0)
+ // Single press toggles NavLayer, long press with L/R apply actual focus on release (until then the window was merely rendered front-most)
+ if (!IsNavInputDown(ImGuiNavInput_Menu))
{
- if (new_V <= 0 && V != new_V)
- ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]);
- else if (new_S <= 0)
- ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]);
+ g.NavWindowingToggleLayer &= (g.NavWindowingHighlightAlpha < 1.0f); // Once button was held long enough we don't consider it a tap-to-toggle-layer press anymore.
+ if (g.NavWindowingToggleLayer && g.NavWindow)
+ apply_toggle_layer = true;
+ else if (!g.NavWindowingToggleLayer)
+ apply_focus_window = g.NavWindowingTarget;
+ g.NavWindowingTarget = NULL;
}
}
- ImVec4 hue_color_f(1, 1, 1, 1); ColorConvertHSVtoRGB(H, 1, 1, hue_color_f.x, hue_color_f.y, hue_color_f.z);
- ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f);
- ImU32 col32_no_alpha = ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 1.0f));
+ // Keyboard: Focus
+ if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_NavKeyboard)
+ {
+ // Visuals only appears after a brief time after pressing TAB the first time, so that a fast CTRL+TAB doesn't add visual noise
+ g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); // 1.0f
+ if (IsKeyPressedMap(ImGuiKey_Tab, true))
+ NavUpdateWindowingHighlightWindow(g.IO.KeyShift ? +1 : -1);
+ if (!g.IO.KeyCtrl)
+ apply_focus_window = g.NavWindowingTarget;
+ }
- const ImU32 hue_colors[6+1] = { IM_COL32(255,0,0,255), IM_COL32(255,255,0,255), IM_COL32(0,255,0,255), IM_COL32(0,255,255,255), IM_COL32(0,0,255,255), IM_COL32(255,0,255,255), IM_COL32(255,0,0,255) };
- ImVec2 sv_cursor_pos;
+ // Keyboard: Press and Release ALT to toggle menu layer
+ // FIXME: We lack an explicit IO variable for "is the imgui window focused", so compare mouse validity to detect the common case of back-end clearing releases all keys on ALT-TAB
+ if ((g.ActiveId == 0 || g.ActiveIdAllowOverlap) && IsNavInputPressed(ImGuiNavInput_KeyMenu_, ImGuiInputReadMode_Released))
+ if (IsMousePosValid(&g.IO.MousePos) == IsMousePosValid(&g.IO.MousePosPrev))
+ apply_toggle_layer = true;
- if (flags & ImGuiColorEditFlags_PickerHueWheel)
+ // Move window
+ if (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoMove))
{
- // Render Hue Wheel
- const float aeps = 1.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out).
- const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12);
- for (int n = 0; n < 6; n++)
+ ImVec2 move_delta;
+ if (g.NavInputSource == ImGuiInputSource_NavKeyboard && !g.IO.KeyShift)
+ move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down);
+ if (g.NavInputSource == ImGuiInputSource_NavGamepad)
+ move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down);
+ if (move_delta.x != 0.0f || move_delta.y != 0.0f)
{
- const float a0 = (n) /6.0f * 2.0f * IM_PI - aeps;
- const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps;
- const int vert_start_idx = draw_list->VtxBuffer.Size;
- draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc);
- draw_list->PathStroke(IM_COL32_WHITE, false, wheel_thickness);
- const int vert_end_idx = draw_list->VtxBuffer.Size;
-
- // Paint colors over existing vertices
- ImVec2 gradient_p0(wheel_center.x + ImCos(a0) * wheel_r_inner, wheel_center.y + ImSin(a0) * wheel_r_inner);
- ImVec2 gradient_p1(wheel_center.x + ImCos(a1) * wheel_r_inner, wheel_center.y + ImSin(a1) * wheel_r_inner);
- ShadeVertsLinearColorGradientKeepAlpha(draw_list, vert_start_idx, vert_end_idx, gradient_p0, gradient_p1, hue_colors[n], hue_colors[n+1]);
+ const float NAV_MOVE_SPEED = 800.0f;
+ const float move_speed = ImFloor(NAV_MOVE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y)); // FIXME: Doesn't code variable framerate very well
+ g.NavWindowingTarget->RootWindow->Pos += move_delta * move_speed;
+ g.NavDisableMouseHover = true;
+ MarkIniSettingsDirty(g.NavWindowingTarget);
}
-
- // Render Cursor + preview on Hue Wheel
- float cos_hue_angle = ImCos(H * 2.0f * IM_PI);
- float sin_hue_angle = ImSin(H * 2.0f * IM_PI);
- ImVec2 hue_cursor_pos(wheel_center.x + cos_hue_angle * (wheel_r_inner+wheel_r_outer)*0.5f, wheel_center.y + sin_hue_angle * (wheel_r_inner+wheel_r_outer)*0.5f);
- float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f;
- int hue_cursor_segments = ImClamp((int)(hue_cursor_rad / 1.4f), 9, 32);
- draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments);
- draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad+1, IM_COL32(128,128,128,255), hue_cursor_segments);
- draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, IM_COL32_WHITE, hue_cursor_segments);
-
- // Render SV triangle (rotated according to hue)
- ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle);
- ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle);
- ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle);
- ImVec2 uv_white = GetFontTexUvWhitePixel();
- draw_list->PrimReserve(6, 6);
- draw_list->PrimVtx(tra, uv_white, hue_color32);
- draw_list->PrimVtx(trb, uv_white, hue_color32);
- draw_list->PrimVtx(trc, uv_white, IM_COL32_WHITE);
- draw_list->PrimVtx(tra, uv_white, IM_COL32_BLACK_TRANS);
- draw_list->PrimVtx(trb, uv_white, IM_COL32_BLACK);
- draw_list->PrimVtx(trc, uv_white, IM_COL32_BLACK_TRANS);
- draw_list->AddTriangle(tra, trb, trc, IM_COL32(128,128,128,255), 1.5f);
- sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V));
- }
- else if (flags & ImGuiColorEditFlags_PickerHueBar)
- {
- // Render SV Square
- draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_WHITE, hue_color32, hue_color32, IM_COL32_WHITE);
- draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_BLACK_TRANS, IM_COL32_BLACK_TRANS, IM_COL32_BLACK, IM_COL32_BLACK);
- RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), 0.0f);
- sv_cursor_pos.x = ImClamp((float)(int)(picker_pos.x + ImSaturate(S) * sv_picker_size + 0.5f), picker_pos.x + 2, picker_pos.x + sv_picker_size - 2); // Sneakily prevent the circle to stick out too much
- sv_cursor_pos.y = ImClamp((float)(int)(picker_pos.y + ImSaturate(1 - V) * sv_picker_size + 0.5f), picker_pos.y + 2, picker_pos.y + sv_picker_size - 2);
-
- // Render Hue Bar
- for (int i = 0; i < 6; ++i)
- draw_list->AddRectFilledMultiColor(ImVec2(bar0_pos_x, picker_pos.y + i * (sv_picker_size / 6)), ImVec2(bar0_pos_x + bars_width, picker_pos.y + (i + 1) * (sv_picker_size / 6)), hue_colors[i], hue_colors[i], hue_colors[i + 1], hue_colors[i + 1]);
- float bar0_line_y = (float)(int)(picker_pos.y + H * sv_picker_size + 0.5f);
- RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f);
- RenderArrowsForVerticalBar(draw_list, ImVec2(bar0_pos_x - 1, bar0_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f);
- }
-
- // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range)
- float sv_cursor_rad = value_changed_sv ? 10.0f : 6.0f;
- draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, col32_no_alpha, 12);
- draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad+1, IM_COL32(128,128,128,255), 12);
- draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, IM_COL32_WHITE, 12);
-
- // Render alpha bar
- if (alpha_bar)
- {
- float alpha = ImSaturate(col[3]);
- ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size);
- RenderColorRectWithAlphaCheckerboard(bar1_bb.Min, bar1_bb.Max, IM_COL32(0,0,0,0), bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f));
- draw_list->AddRectFilledMultiColor(bar1_bb.Min, bar1_bb.Max, col32_no_alpha, col32_no_alpha, col32_no_alpha & ~IM_COL32_A_MASK, col32_no_alpha & ~IM_COL32_A_MASK);
- float bar1_line_y = (float)(int)(picker_pos.y + (1.0f - alpha) * sv_picker_size + 0.5f);
- RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f);
- RenderArrowsForVerticalBar(draw_list, ImVec2(bar1_pos_x - 1, bar1_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f);
- }
-
- EndGroup();
-
- if (value_changed && memcmp(backup_initial_col, col, components * sizeof(float)) == 0)
- value_changed = false;
- if (value_changed)
- MarkItemEdited(window->DC.LastItemId);
-
- PopID();
-
- return value_changed;
-}
-
-// Horizontal/vertical separating line
-void ImGui::Separator()
-{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return;
- ImGuiContext& g = *GImGui;
-
- // Those flags should eventually be overridable by the user
- ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal;
- IM_ASSERT(ImIsPowerOfTwo((int)(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)))); // Check that only 1 option is selected
- if (flags & ImGuiSeparatorFlags_Vertical)
- {
- VerticalSeparator();
- return;
}
- // Horizontal Separator
- if (window->DC.ColumnsSet)
- PopClipRect();
-
- float x1 = window->Pos.x;
- float x2 = window->Pos.x + window->Size.x;
- if (!window->DC.GroupStack.empty())
- x1 += window->DC.Indent.x;
-
- const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y+1.0f));
- ItemSize(ImVec2(0.0f, 0.0f)); // NB: we don't provide our width so that it doesn't get feed back into AutoFit, we don't provide height to not alter layout.
- if (!ItemAdd(bb, 0))
+ // Apply final focus
+ if (apply_focus_window && (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindow))
{
- if (window->DC.ColumnsSet)
- PushColumnClipRect();
- return;
- }
-
- window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x,bb.Min.y), GetColorU32(ImGuiCol_Separator));
-
- if (g.LogEnabled)
- LogRenderedText(NULL, IM_NEWLINE "--------------------------------");
+ g.NavDisableHighlight = false;
+ g.NavDisableMouseHover = true;
+ apply_focus_window = NavRestoreLastChildNavWindow(apply_focus_window);
+ ClosePopupsOverWindow(apply_focus_window);
+ FocusWindow(apply_focus_window);
+ if (apply_focus_window->NavLastIds[0] == 0)
+ NavInitWindow(apply_focus_window, false);
- if (window->DC.ColumnsSet)
- {
- PushColumnClipRect();
- window->DC.ColumnsSet->LineMinY = window->DC.CursorPos.y;
+ // If the window only has a menu layer, select it directly
+ if (apply_focus_window->DC.NavLayerActiveMask == (1 << 1))
+ g.NavLayer = 1;
}
-}
-
-void ImGui::VerticalSeparator()
-{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return;
- ImGuiContext& g = *GImGui;
-
- float y1 = window->DC.CursorPos.y;
- float y2 = window->DC.CursorPos.y + window->DC.CurrentLineSize.y;
- const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + 1.0f, y2));
- ItemSize(ImVec2(bb.GetWidth(), 0.0f));
- if (!ItemAdd(bb, 0))
- return;
-
- window->DrawList->AddLine(ImVec2(bb.Min.x, bb.Min.y), ImVec2(bb.Min.x, bb.Max.y), GetColorU32(ImGuiCol_Separator));
- if (g.LogEnabled)
- LogText(" |");
-}
-
-// Using 'hover_visibility_delay' allows us to hide the highlight and mouse cursor for a short time, which can be convenient to reduce visual noise.
-bool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend, float hover_visibility_delay)
-{
- ImGuiContext& g = *GImGui;
- ImGuiWindow* window = g.CurrentWindow;
-
- const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags;
- window->DC.ItemFlags |= ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus;
- bool item_add = ItemAdd(bb, id);
- window->DC.ItemFlags = item_flags_backup;
- if (!item_add)
- return false;
-
- bool hovered, held;
- ImRect bb_interact = bb;
- bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f));
- ButtonBehavior(bb_interact, id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap);
- if (g.ActiveId != id)
- SetItemAllowOverlap();
-
- if (held || (g.HoveredId == id && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay))
- SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW);
+ if (apply_focus_window)
+ g.NavWindowingTarget = NULL;
- ImRect bb_render = bb;
- if (held)
+ // Apply menu/layer toggle
+ if (apply_toggle_layer && g.NavWindow)
{
- ImVec2 mouse_delta_2d = g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min;
- float mouse_delta = (axis == ImGuiAxis_Y) ? mouse_delta_2d.y : mouse_delta_2d.x;
-
- // Minimum pane size
- float size_1_maximum_delta = ImMax(0.0f, *size1 - min_size1);
- float size_2_maximum_delta = ImMax(0.0f, *size2 - min_size2);
- if (mouse_delta < -size_1_maximum_delta)
- mouse_delta = -size_1_maximum_delta;
- if (mouse_delta > size_2_maximum_delta)
- mouse_delta = size_2_maximum_delta;
-
- // Apply resize
- if (mouse_delta != 0.0f)
+ // Move to parent menu if necessary
+ ImGuiWindow* new_nav_window = g.NavWindow;
+ while ((new_nav_window->DC.NavLayerActiveMask & (1 << 1)) == 0 && (new_nav_window->Flags & ImGuiWindowFlags_ChildWindow) != 0 && (new_nav_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0)
+ new_nav_window = new_nav_window->ParentWindow;
+ if (new_nav_window != g.NavWindow)
{
- if (mouse_delta < 0.0f)
- IM_ASSERT(*size1 + mouse_delta >= min_size1);
- if (mouse_delta > 0.0f)
- IM_ASSERT(*size2 - mouse_delta >= min_size2);
- *size1 += mouse_delta;
- *size2 -= mouse_delta;
- bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta));
- MarkItemEdited(id);
+ ImGuiWindow* old_nav_window = g.NavWindow;
+ FocusWindow(new_nav_window);
+ new_nav_window->NavLastChildNavWindow = old_nav_window;
}
+ g.NavDisableHighlight = false;
+ g.NavDisableMouseHover = true;
+ NavRestoreLayer((g.NavWindow->DC.NavLayerActiveMask & (1 << 1)) ? (g.NavLayer ^ 1) : 0);
}
-
- // Render
- const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
- window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, g.Style.FrameRounding);
-
- return held;
-}
-
-void ImGui::Spacing()
-{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return;
- ItemSize(ImVec2(0,0));
-}
-
-void ImGui::Dummy(const ImVec2& size)
-{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return;
-
- const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
- ItemSize(bb);
- ItemAdd(bb, 0);
}
-bool ImGui::IsRectVisible(const ImVec2& size)
-{
- ImGuiWindow* window = GetCurrentWindowRead();
- return window->ClipRect.Overlaps(ImRect(window->DC.CursorPos, window->DC.CursorPos + size));
-}
-
-bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max)
-{
- ImGuiWindow* window = GetCurrentWindowRead();
- return window->ClipRect.Overlaps(ImRect(rect_min, rect_max));
-}
-
-// Lock horizontal starting position + capture group bounding box into one "item" (so you can use IsItemHovered() or layout primitives such as SameLine() on whole group, etc.)
-void ImGui::BeginGroup()
+// Window has already passed the IsWindowNavFocusable()
+static const char* GetFallbackWindowNameForWindowingList(ImGuiWindow* window)
{
- ImGuiContext& g = *GImGui;
- ImGuiWindow* window = GetCurrentWindow();
-
- window->DC.GroupStack.resize(window->DC.GroupStack.Size + 1);
- ImGuiGroupData& group_data = window->DC.GroupStack.back();
- group_data.BackupCursorPos = window->DC.CursorPos;
- group_data.BackupCursorMaxPos = window->DC.CursorMaxPos;
- group_data.BackupIndent = window->DC.Indent;
- group_data.BackupGroupOffset = window->DC.GroupOffset;
- group_data.BackupCurrentLineSize = window->DC.CurrentLineSize;
- group_data.BackupCurrentLineTextBaseOffset = window->DC.CurrentLineTextBaseOffset;
- group_data.BackupLogLinePosY = window->DC.LogLinePosY;
- group_data.BackupActiveIdIsAlive = g.ActiveIdIsAlive;
- group_data.BackupActiveIdPreviousFrameIsAlive = g.ActiveIdPreviousFrameIsAlive;
- group_data.AdvanceCursor = true;
-
- window->DC.GroupOffset.x = window->DC.CursorPos.x - window->Pos.x - window->DC.ColumnsOffset.x;
- window->DC.Indent = window->DC.GroupOffset;
- window->DC.CursorMaxPos = window->DC.CursorPos;
- window->DC.CurrentLineSize = ImVec2(0.0f, 0.0f);
- window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; // To enforce Log carriage return
+ if (window->Flags & ImGuiWindowFlags_Popup)
+ return "(Popup)";
+ if ((window->Flags & ImGuiWindowFlags_MenuBar) && strcmp(window->Name, "##MainMenuBar") == 0)
+ return "(Main menu bar)";
+ return "(Untitled)";
}
-void ImGui::EndGroup()
+// Overlay displayed when using CTRL+TAB. Called by EndFrame().
+void ImGui::NavUpdateWindowingList()
{
ImGuiContext& g = *GImGui;
- ImGuiWindow* window = GetCurrentWindow();
- IM_ASSERT(!window->DC.GroupStack.empty()); // Mismatched BeginGroup()/EndGroup() calls
-
- ImGuiGroupData& group_data = window->DC.GroupStack.back();
-
- ImRect group_bb(group_data.BackupCursorPos, window->DC.CursorMaxPos);
- group_bb.Max = ImMax(group_bb.Min, group_bb.Max);
-
- window->DC.CursorPos = group_data.BackupCursorPos;
- window->DC.CursorMaxPos = ImMax(group_data.BackupCursorMaxPos, window->DC.CursorMaxPos);
- window->DC.Indent = group_data.BackupIndent;
- window->DC.GroupOffset = group_data.BackupGroupOffset;
- window->DC.CurrentLineSize = group_data.BackupCurrentLineSize;
- window->DC.CurrentLineTextBaseOffset = group_data.BackupCurrentLineTextBaseOffset;
- window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; // To enforce Log carriage return
-
- if (group_data.AdvanceCursor)
- {
- window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.PrevLineTextBaseOffset, group_data.BackupCurrentLineTextBaseOffset); // FIXME: Incorrect, we should grab the base offset from the *first line* of the group but it is hard to obtain now.
- ItemSize(group_bb.GetSize(), group_data.BackupCurrentLineTextBaseOffset);
- ItemAdd(group_bb, 0);
- }
-
- // If the current ActiveId was declared within the boundary of our group, we copy it to LastItemId so IsItemActive(), IsItemDeactivated() etc. will be functional on the entire group.
- // It would be be neater if we replaced window.DC.LastItemId by e.g. 'bool LastItemIsActive', but would put a little more burden on individual widgets.
- // (and if you grep for LastItemId you'll notice it is only used in that context.
- if ((group_data.BackupActiveIdIsAlive != g.ActiveId) && (g.ActiveIdIsAlive == g.ActiveId) && g.ActiveId) // && g.ActiveIdWindow->RootWindow == window->RootWindow)
- window->DC.LastItemId = g.ActiveId;
- else if (!group_data.BackupActiveIdPreviousFrameIsAlive && g.ActiveIdPreviousFrameIsAlive) // && g.ActiveIdPreviousFrameWindow->RootWindow == window->RootWindow)
- window->DC.LastItemId = g.ActiveIdPreviousFrame;
- window->DC.LastItemRect = group_bb;
-
- window->DC.GroupStack.pop_back();
-
- //window->DrawList->AddRect(group_bb.Min, group_bb.Max, IM_COL32(255,0,255,255)); // [Debug]
-}
+ IM_ASSERT(g.NavWindowingTarget != NULL);
-// Gets back to previous line and continue with horizontal layout
-// pos_x == 0 : follow right after previous item
-// pos_x != 0 : align to specified x position (relative to window/group left)
-// spacing_w < 0 : use default spacing if pos_x == 0, no spacing if pos_x != 0
-// spacing_w >= 0 : enforce spacing amount
-void ImGui::SameLine(float pos_x, float spacing_w)
-{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
+ if (g.NavWindowingTimer < NAV_WINDOWING_LIST_APPEAR_DELAY)
return;
- ImGuiContext& g = *GImGui;
- if (pos_x != 0.0f)
- {
- if (spacing_w < 0.0f) spacing_w = 0.0f;
- window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + pos_x + spacing_w + window->DC.GroupOffset.x + window->DC.ColumnsOffset.x;
- window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y;
- }
- else
+ if (g.NavWindowingList == NULL)
+ g.NavWindowingList = FindWindowByName("###NavWindowingList");
+ SetNextWindowSizeConstraints(ImVec2(g.IO.DisplaySize.x * 0.20f, g.IO.DisplaySize.y * 0.20f), ImVec2(FLT_MAX, FLT_MAX));
+ SetNextWindowPos(g.IO.DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
+ PushStyleVar(ImGuiStyleVar_WindowPadding, g.Style.WindowPadding * 2.0f);
+ Begin("###NavWindowingList", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings);
+ for (int n = g.Windows.Size - 1; n >= 0; n--)
{
- if (spacing_w < 0.0f) spacing_w = g.Style.ItemSpacing.x;
- window->DC.CursorPos.x = window->DC.CursorPosPrevLine.x + spacing_w;
- window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y;
+ ImGuiWindow* window = g.Windows[n];
+ if (!IsWindowNavFocusable(window))
+ continue;
+ const char* label = window->Name;
+ if (label == FindRenderedTextEnd(label))
+ label = GetFallbackWindowNameForWindowingList(window);
+ Selectable(label, g.NavWindowingTarget == window);
}
- window->DC.CurrentLineSize = window->DC.PrevLineSize;
- window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset;
+ End();
+ PopStyleVar();
}
-void ImGui::NewLine()
-{
- ImGuiWindow* window = GetCurrentWindow();
- if (window->SkipItems)
- return;
-
- ImGuiContext& g = *GImGui;
- const ImGuiLayoutType backup_layout_type = window->DC.LayoutType;
- window->DC.LayoutType = ImGuiLayoutType_Vertical;
- if (window->DC.CurrentLineSize.y > 0.0f) // In the event that we are on a line with items that is smaller that FontSize high, we will preserve its height.
- ItemSize(ImVec2(0,0));
- else
- ItemSize(ImVec2(0.0f, g.FontSize));
- window->DC.LayoutType = backup_layout_type;
-}
+//-----------------------------------------------------------------------------
+// COLUMNS
+//-----------------------------------------------------------------------------
void ImGui::NextColumn()
{
@@ -13627,94 +8106,6 @@ void ImGui::Columns(int columns_count, const char* id, bool border)
BeginColumns(id, columns_count, flags);
}
-void ImGui::Indent(float indent_w)
-{
- ImGuiContext& g = *GImGui;
- ImGuiWindow* window = GetCurrentWindow();
- window->DC.Indent.x += (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing;
- window->DC.CursorPos.x = window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x;
-}
-
-void ImGui::Unindent(float indent_w)
-{
- ImGuiContext& g = *GImGui;
- ImGuiWindow* window = GetCurrentWindow();
- window->DC.Indent.x -= (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing;
- window->DC.CursorPos.x = window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x;
-}
-
-void ImGui::TreePush(const char* str_id)
-{
- ImGuiWindow* window = GetCurrentWindow();
- Indent();
- window->DC.TreeDepth++;
- PushID(str_id ? str_id : "#TreePush");
-}
-
-void ImGui::TreePush(const void* ptr_id)
-{
- ImGuiWindow* window = GetCurrentWindow();
- Indent();
- window->DC.TreeDepth++;
- PushID(ptr_id ? ptr_id : (const void*)"#TreePush");
-}
-
-void ImGui::TreePushRawID(ImGuiID id)
-{
- ImGuiWindow* window = GetCurrentWindow();
- Indent();
- window->DC.TreeDepth++;
- window->IDStack.push_back(id);
-}
-
-void ImGui::TreePop()
-{
- ImGuiContext& g = *GImGui;
- ImGuiWindow* window = g.CurrentWindow;
- Unindent();
-
- window->DC.TreeDepth--;
- if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
- if (g.NavIdIsAlive && (window->DC.TreeDepthMayJumpToParentOnPop & (1 << window->DC.TreeDepth)))
- {
- SetNavID(window->IDStack.back(), g.NavLayer);
- NavMoveRequestCancel();
- }
- window->DC.TreeDepthMayJumpToParentOnPop &= (1 << window->DC.TreeDepth) - 1;
-
- IM_ASSERT(window->IDStack.Size > 1); // There should always be 1 element in the IDStack (pushed during window creation). If this triggers you called TreePop/PopID too much.
- PopID();
-}
-
-void ImGui::Value(const char* prefix, bool b)
-{
- Text("%s: %s", prefix, (b ? "true" : "false"));
-}
-
-void ImGui::Value(const char* prefix, int v)
-{
- Text("%s: %d", prefix, v);
-}
-
-void ImGui::Value(const char* prefix, unsigned int v)
-{
- Text("%s: %d", prefix, v);
-}
-
-void ImGui::Value(const char* prefix, float v, const char* float_format)
-{
- if (float_format)
- {
- char fmt[64];
- ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format);
- Text(fmt, prefix, v);
- }
- else
- {
- Text("%s: %.3f", prefix, v);
- }
-}
-
//-----------------------------------------------------------------------------
// DRAG AND DROP
//-----------------------------------------------------------------------------
@@ -14003,6 +8394,397 @@ void ImGui::EndDragDropTarget()
}
//-----------------------------------------------------------------------------
+// LOGGING
+//-----------------------------------------------------------------------------
+
+// Pass text data straight to log (without being displayed)
+void ImGui::LogText(const char* fmt, ...)
+{
+ ImGuiContext& g = *GImGui;
+ if (!g.LogEnabled)
+ return;
+
+ va_list args;
+ va_start(args, fmt);
+ if (g.LogFile)
+ vfprintf(g.LogFile, fmt, args);
+ else
+ g.LogClipboard.appendfv(fmt, args);
+ va_end(args);
+}
+
+// Internal version that takes a position to decide on newline placement and pad items according to their depth.
+// We split text into individual lines to add current tree level padding
+void ImGui::LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end)
+{
+ ImGuiContext& g = *GImGui;
+ ImGuiWindow* window = g.CurrentWindow;
+
+ if (!text_end)
+ text_end = FindRenderedTextEnd(text, text_end);
+
+ const bool log_new_line = ref_pos && (ref_pos->y > window->DC.LogLinePosY + 1);
+ if (ref_pos)
+ window->DC.LogLinePosY = ref_pos->y;
+
+ const char* text_remaining = text;
+ if (g.LogStartDepth > window->DC.TreeDepth) // Re-adjust padding if we have popped out of our starting depth
+ g.LogStartDepth = window->DC.TreeDepth;
+ const int tree_depth = (window->DC.TreeDepth - g.LogStartDepth);
+ for (;;)
+ {
+ // Split the string. Each new line (after a '\n') is followed by spacing corresponding to the current depth of our log entry.
+ const char* line_end = text_remaining;
+ while (line_end < text_end)
+ if (*line_end == '\n')
+ break;
+ else
+ line_end++;
+ if (line_end >= text_end)
+ line_end = NULL;
+
+ const bool is_first_line = (text == text_remaining);
+ bool is_last_line = false;
+ if (line_end == NULL)
+ {
+ is_last_line = true;
+ line_end = text_end;
+ }
+ if (line_end != NULL && !(is_last_line && (line_end - text_remaining)==0))
+ {
+ const int char_count = (int)(line_end - text_remaining);
+ if (log_new_line || !is_first_line)
+ LogText(IM_NEWLINE "%*s%.*s", tree_depth*4, "", char_count, text_remaining);
+ else
+ LogText(" %.*s", char_count, text_remaining);
+ }
+
+ if (is_last_line)
+ break;
+ text_remaining = line_end + 1;
+ }
+}
+
+// Start logging ImGui output to TTY
+void ImGui::LogToTTY(int max_depth)
+{
+ ImGuiContext& g = *GImGui;
+ if (g.LogEnabled)
+ return;
+ ImGuiWindow* window = g.CurrentWindow;
+
+ IM_ASSERT(g.LogFile == NULL);
+ g.LogFile = stdout;
+ g.LogEnabled = true;
+ g.LogStartDepth = window->DC.TreeDepth;
+ if (max_depth >= 0)
+ g.LogAutoExpandMaxDepth = max_depth;
+}
+
+// Start logging ImGui output to given file
+void ImGui::LogToFile(int max_depth, const char* filename)
+{
+ ImGuiContext& g = *GImGui;
+ if (g.LogEnabled)
+ return;
+ ImGuiWindow* window = g.CurrentWindow;
+
+ if (!filename)
+ {
+ filename = g.IO.LogFilename;
+ if (!filename)
+ return;
+ }
+
+ IM_ASSERT(g.LogFile == NULL);
+ g.LogFile = ImFileOpen(filename, "ab");
+ if (!g.LogFile)
+ {
+ IM_ASSERT(g.LogFile != NULL); // Consider this an error
+ return;
+ }
+ g.LogEnabled = true;
+ g.LogStartDepth = window->DC.TreeDepth;
+ if (max_depth >= 0)
+ g.LogAutoExpandMaxDepth = max_depth;
+}
+
+// Start logging ImGui output to clipboard
+void ImGui::LogToClipboard(int max_depth)
+{
+ ImGuiContext& g = *GImGui;
+ if (g.LogEnabled)
+ return;
+ ImGuiWindow* window = g.CurrentWindow;
+
+ IM_ASSERT(g.LogFile == NULL);
+ g.LogFile = NULL;
+ g.LogEnabled = true;
+ g.LogStartDepth = window->DC.TreeDepth;
+ if (max_depth >= 0)
+ g.LogAutoExpandMaxDepth = max_depth;
+}
+
+void ImGui::LogFinish()
+{
+ ImGuiContext& g = *GImGui;
+ if (!g.LogEnabled)
+ return;
+
+ LogText(IM_NEWLINE);
+ if (g.LogFile != NULL)
+ {
+ if (g.LogFile == stdout)
+ fflush(g.LogFile);
+ else
+ fclose(g.LogFile);
+ g.LogFile = NULL;
+ }
+ if (g.LogClipboard.size() > 1)
+ {
+ SetClipboardText(g.LogClipboard.begin());
+ g.LogClipboard.clear();
+ }
+ g.LogEnabled = false;
+}
+
+// Helper to display logging buttons
+void ImGui::LogButtons()
+{
+ ImGuiContext& g = *GImGui;
+
+ PushID("LogButtons");
+ const bool log_to_tty = Button("Log To TTY"); SameLine();
+ const bool log_to_file = Button("Log To File"); SameLine();
+ const bool log_to_clipboard = Button("Log To Clipboard"); SameLine();
+ PushItemWidth(80.0f);
+ PushAllowKeyboardFocus(false);
+ SliderInt("Depth", &g.LogAutoExpandMaxDepth, 0, 9, NULL);
+ PopAllowKeyboardFocus();
+ PopItemWidth();
+ PopID();
+
+ // Start logging at the end of the function so that the buttons don't appear in the log
+ if (log_to_tty)
+ LogToTTY(g.LogAutoExpandMaxDepth);
+ if (log_to_file)
+ LogToFile(g.LogAutoExpandMaxDepth, g.IO.LogFilename);
+ if (log_to_clipboard)
+ LogToClipboard(g.LogAutoExpandMaxDepth);
+}
+
+//-----------------------------------------------------------------------------
+// SETTINGS
+//-----------------------------------------------------------------------------
+
+void ImGui::MarkIniSettingsDirty()
+{
+ ImGuiContext& g = *GImGui;
+ if (g.SettingsDirtyTimer <= 0.0f)
+ g.SettingsDirtyTimer = g.IO.IniSavingRate;
+}
+
+void ImGui::MarkIniSettingsDirty(ImGuiWindow* window)
+{
+ ImGuiContext& g = *GImGui;
+ if (!(window->Flags & ImGuiWindowFlags_NoSavedSettings))
+ if (g.SettingsDirtyTimer <= 0.0f)
+ g.SettingsDirtyTimer = g.IO.IniSavingRate;
+}
+
+static ImGuiWindowSettings* CreateNewWindowSettings(const char* name)
+{
+ ImGuiContext& g = *GImGui;
+ g.SettingsWindows.push_back(ImGuiWindowSettings());
+ ImGuiWindowSettings* settings = &g.SettingsWindows.back();
+ settings->Name = ImStrdup(name);
+ settings->ID = ImHash(name, 0);
+ return settings;
+}
+
+ImGuiWindowSettings* ImGui::FindWindowSettings(ImGuiID id)
+{
+ ImGuiContext& g = *GImGui;
+ for (int i = 0; i != g.SettingsWindows.Size; i++)
+ if (g.SettingsWindows[i].ID == id)
+ return &g.SettingsWindows[i];
+ return NULL;
+}
+
+void ImGui::LoadIniSettingsFromDisk(const char* ini_filename)
+{
+ size_t file_data_size = 0;
+ char* file_data = (char*)ImFileLoadToMemory(ini_filename, "rb", &file_data_size);
+ if (!file_data)
+ return;
+ LoadIniSettingsFromMemory(file_data, (size_t)file_data_size);
+ ImGui::MemFree(file_data);
+}
+
+ImGuiSettingsHandler* ImGui::FindSettingsHandler(const char* type_name)
+{
+ ImGuiContext& g = *GImGui;
+ const ImGuiID type_hash = ImHash(type_name, 0, 0);
+ for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++)
+ if (g.SettingsHandlers[handler_n].TypeHash == type_hash)
+ return &g.SettingsHandlers[handler_n];
+ return NULL;
+}
+
+// Zero-tolerance, no error reporting, cheap .ini parsing
+void ImGui::LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size)
+{
+ ImGuiContext& g = *GImGui;
+ IM_ASSERT(g.Initialized);
+ IM_ASSERT(g.SettingsLoaded == false && g.FrameCount == 0);
+
+ // For user convenience, we allow passing a non zero-terminated string (hence the ini_size parameter).
+ // For our convenience and to make the code simpler, we'll also write zero-terminators within the buffer. So let's create a writable copy..
+ if (ini_size == 0)
+ ini_size = strlen(ini_data);
+ char* buf = (char*)ImGui::MemAlloc(ini_size + 1);
+ char* buf_end = buf + ini_size;
+ memcpy(buf, ini_data, ini_size);
+ buf[ini_size] = 0;
+
+ void* entry_data = NULL;
+ ImGuiSettingsHandler* entry_handler = NULL;
+
+ char* line_end = NULL;
+ for (char* line = buf; line < buf_end; line = line_end + 1)
+ {
+ // Skip new lines markers, then find end of the line
+ while (*line == '\n' || *line == '\r')
+ line++;
+ line_end = line;
+ while (line_end < buf_end && *line_end != '\n' && *line_end != '\r')
+ line_end++;
+ line_end[0] = 0;
+
+ if (line[0] == '[' && line_end > line && line_end[-1] == ']')
+ {
+ // Parse "[Type][Name]". Note that 'Name' can itself contains [] characters, which is acceptable with the current format and parsing code.
+ line_end[-1] = 0;
+ const char* name_end = line_end - 1;
+ const char* type_start = line + 1;
+ char* type_end = (char*)(intptr_t)ImStrchrRange(type_start, name_end, ']');
+ const char* name_start = type_end ? ImStrchrRange(type_end + 1, name_end, '[') : NULL;
+ if (!type_end || !name_start)
+ {
+ name_start = type_start; // Import legacy entries that have no type
+ type_start = "Window";
+ }
+ else
+ {
+ *type_end = 0; // Overwrite first ']'
+ name_start++; // Skip second '['
+ }
+ entry_handler = FindSettingsHandler(type_start);
+ entry_data = entry_handler ? entry_handler->ReadOpenFn(&g, entry_handler, name_start) : NULL;
+ }
+ else if (entry_handler != NULL && entry_data != NULL)
+ {
+ // Let type handler parse the line
+ entry_handler->ReadLineFn(&g, entry_handler, entry_data, line);
+ }
+ }
+ ImGui::MemFree(buf);
+ g.SettingsLoaded = true;
+}
+
+void ImGui::SaveIniSettingsToDisk(const char* ini_filename)
+{
+ ImGuiContext& g = *GImGui;
+ g.SettingsDirtyTimer = 0.0f;
+ if (!ini_filename)
+ return;
+
+ size_t ini_data_size = 0;
+ const char* ini_data = SaveIniSettingsToMemory(&ini_data_size);
+ FILE* f = ImFileOpen(ini_filename, "wt");
+ if (!f)
+ return;
+ fwrite(ini_data, sizeof(char), ini_data_size, f);
+ fclose(f);
+}
+
+// Call registered handlers (e.g. SettingsHandlerWindow_WriteAll() + custom handlers) to write their stuff into a text buffer
+const char* ImGui::SaveIniSettingsToMemory(size_t* out_size)
+{
+ ImGuiContext& g = *GImGui;
+ g.SettingsDirtyTimer = 0.0f;
+ g.SettingsIniData.Buf.resize(0);
+ g.SettingsIniData.Buf.push_back(0);
+ for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++)
+ {
+ ImGuiSettingsHandler* handler = &g.SettingsHandlers[handler_n];
+ handler->WriteAllFn(&g, handler, &g.SettingsIniData);
+ }
+ if (out_size)
+ *out_size = (size_t)g.SettingsIniData.size();
+ return g.SettingsIniData.c_str();
+}
+
+static void* SettingsHandlerWindow_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name)
+{
+ ImGuiWindowSettings* settings = ImGui::FindWindowSettings(ImHash(name, 0));
+ if (!settings)
+ settings = CreateNewWindowSettings(name);
+ return (void*)settings;
+}
+
+static void SettingsHandlerWindow_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line)
+{
+ ImGuiWindowSettings* settings = (ImGuiWindowSettings*)entry;
+ float x, y;
+ int i;
+ if (sscanf(line, "Pos=%f,%f", &x, &y) == 2) settings->Pos = ImVec2(x, y);
+ else if (sscanf(line, "Size=%f,%f", &x, &y) == 2) settings->Size = ImMax(ImVec2(x, y), GImGui->Style.WindowMinSize);
+ else if (sscanf(line, "Collapsed=%d", &i) == 1) settings->Collapsed = (i != 0);
+}
+
+static void SettingsHandlerWindow_WriteAll(ImGuiContext* imgui_ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf)
+{
+ // Gather data from windows that were active during this session
+ ImGuiContext& g = *imgui_ctx;
+ for (int i = 0; i != g.Windows.Size; i++)
+ {
+ ImGuiWindow* window = g.Windows[i];
+ if (window->Flags & ImGuiWindowFlags_NoSavedSettings)
+ continue;
+
+ ImGuiWindowSettings* settings = (window->SettingsIdx != -1) ? &g.SettingsWindows[window->SettingsIdx] : ImGui::FindWindowSettings(window->ID);
+ if (!settings)
+ {
+ settings = CreateNewWindowSettings(window->Name);
+ window->SettingsIdx = g.SettingsWindows.index_from_pointer(settings);
+ }
+ IM_ASSERT(settings->ID == window->ID);
+ settings->Pos = window->Pos;
+ settings->Size = window->SizeFull;
+ settings->Collapsed = window->Collapsed;
+ }
+
+ // Write a buffer
+ // If a window wasn't opened in this session we preserve its settings
+ buf->reserve(buf->size() + g.SettingsWindows.Size * 96); // ballpark reserve
+ for (int i = 0; i != g.SettingsWindows.Size; i++)
+ {
+ const ImGuiWindowSettings* settings = &g.SettingsWindows[i];
+ if (settings->Pos.x == FLT_MAX)
+ continue;
+ const char* name = settings->Name;
+ if (const char* p = strstr(name, "###")) // Skip to the "###" marker if any. We don't skip past to match the behavior of GetID()
+ name = p;
+ buf->appendf("[%s][%s]\n", handler->TypeName, name);
+ buf->appendf("Pos=%d,%d\n", (int)settings->Pos.x, (int)settings->Pos.y);
+ buf->appendf("Size=%d,%d\n", (int)settings->Size.x, (int)settings->Size.y);
+ buf->appendf("Collapsed=%d\n", settings->Collapsed);
+ buf->appendf("\n");
+ }
+}
+
+//-----------------------------------------------------------------------------
// PLATFORM DEPENDENT HELPERS
//-----------------------------------------------------------------------------
@@ -14119,7 +8901,7 @@ static void ImeSetInputScreenPosFn_DefaultImpl(int, int) {}
#endif
//-----------------------------------------------------------------------------
-// HELP, METRICS
+// METRICS/DEBUG WINDOW
//-----------------------------------------------------------------------------
void ImGui::ShowMetricsWindow(bool* p_open)
diff --git a/imgui/imgui.h b/imgui/imgui.h
index 52caf451..97f79a87 100644
--- a/imgui/imgui.h
+++ b/imgui/imgui.h
@@ -1,4 +1,4 @@
-// dear imgui, v1.63
+// dear imgui, v1.64
// (headers)
// See imgui.cpp file for documentation.
@@ -23,8 +23,8 @@
// Version
// (Integer encoded as XYYZZ for use in #if preprocessor conditionals. Work in progress versions typically starts at XYY00 then bounced up to XYY01 when release tagging happens)
-#define IMGUI_VERSION "1.63"
-#define IMGUI_VERSION_NUM 16301
+#define IMGUI_VERSION "1.64"
+#define IMGUI_VERSION_NUM 16401
#define IMGUI_CHECKVERSION() ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert))
// Define attributes of all API symbols declarations (e.g. for DLL under Windows)
diff --git a/imgui/imgui_demo.cpp b/imgui/imgui_demo.cpp
index 49ef8bcc..1702474e 100644
--- a/imgui/imgui_demo.cpp
+++ b/imgui/imgui_demo.cpp
@@ -1,4 +1,4 @@
-// dear imgui, v1.63
+// dear imgui, v1.64
// (demo code)
// Message to the person tempted to delete this file when integrating ImGui into their code base:
diff --git a/imgui/imgui_draw.cpp b/imgui/imgui_draw.cpp
index d7044cac..98fbc45c 100644
--- a/imgui/imgui_draw.cpp
+++ b/imgui/imgui_draw.cpp
@@ -1,14 +1,21 @@
-// dear imgui, v1.63
+// dear imgui, v1.64
// (drawing and font code)
-// Contains implementation for
-// - Default styles
-// - ImDrawList
-// - ImDrawData
-// - ImFontAtlas
-// - Internal Render Helpers
-// - ImFont
-// - Default font data
+/*
+
+Index of this file:
+- Cruft for stb_truetype/stb_rectpack implementation
+- Style functions (default style)
+- ImDrawList
+- ImDrawData
+- ShadeVertsXXX helpers functions
+- ImFontConfig
+- ImFontAtlas
+- ImFont
+- Internal Render Helpers
+- Default font data
+
+*/
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
diff --git a/imgui/imgui_internal.h b/imgui/imgui_internal.h
index f9d55b31..81116ce9 100644
--- a/imgui/imgui_internal.h
+++ b/imgui/imgui_internal.h
@@ -1,4 +1,4 @@
-// dear imgui, v1.63
+// dear imgui, v1.64
// (internal structures/api)
// You may use this file to debug, understand or extend ImGui features but we don't provide any guarantee of forward compatibility!
@@ -1137,8 +1137,9 @@ namespace ImGui
// Settings
IMGUI_API void MarkIniSettingsDirty();
IMGUI_API void MarkIniSettingsDirty(ImGuiWindow* window);
- IMGUI_API ImGuiSettingsHandler* FindSettingsHandler(const char* type_name);
+ IMGUI_API ImGuiWindowSettings* CreateNewWindowSettings(const char* name);
IMGUI_API ImGuiWindowSettings* FindWindowSettings(ImGuiID id);
+ IMGUI_API ImGuiSettingsHandler* FindSettingsHandler(const char* type_name);
// Basic Accessors
inline ImGuiID GetItemID() { ImGuiContext& g = *GImGui; return g.CurrentWindow->DC.LastItemId; }
diff --git a/imgui/imgui_widgets.cpp b/imgui/imgui_widgets.cpp
new file mode 100644
index 00000000..c2c67e78
--- /dev/null
+++ b/imgui/imgui_widgets.cpp
@@ -0,0 +1,5536 @@
+// dear imgui, v1.64
+// (widgets code)
+
+/*
+
+Index of this file:
+- Widgets: Text, etc.
+- Widgets: Button, Image, Checkbox, RadioButton, ProgressBar, Bullet, etc.
+- Widgets: ComboBox
+- Data Type and Data Formatting Helpers
+- Widgets: DragScalar, DragFloat, DragInt, etc.
+- Widgets: SliderScalar, SliderFloat, SliderInt, etc.
+- Widgets: InputScalar, InputFloat, InputInt, etc.
+- Widgets: InputText, InputTextMultiline
+- Widgets: ColorEdit, ColorPicker, ColorButton, etc.
+- Widgets: TreeNode, TreePush, TreePop, etc.
+- Widgets: Selectable
+- Widgets: ListBox
+- Widgets: PlotLines, PlotHistogram
+- Widgets: Value
+- Widgets: MenuItem, BeginMenu, EndMenu, etc.
+
+*/
+
+#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#include "imgui.h"
+#ifndef IMGUI_DEFINE_MATH_OPERATORS
+#define IMGUI_DEFINE_MATH_OPERATORS
+#endif
+#include "imgui_internal.h"
+
+#include <ctype.h> // toupper, isprint
+#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
+#include <stddef.h> // intptr_t
+#else
+#include <stdint.h> // intptr_t
+#endif
+
+// Visual Studio warnings
+#ifdef _MSC_VER
+#pragma warning (disable: 4127) // condition expression is constant
+#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
+#endif
+
+// Clang/GCC warnings with -Weverything
+#ifdef __clang__
+#pragma clang diagnostic ignored "-Wformat-nonliteral" // warning : format string is not a string literal // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code.
+#pragma clang diagnostic ignored "-Wsign-conversion" // warning : implicit conversion changes signedness //
+#elif defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked
+#if __GNUC__ >= 8
+#pragma GCC diagnostic ignored "-Wclass-memaccess" // warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead
+#endif
+#endif
+
+//-------------------------------------------------------------------------
+// Data
+//-------------------------------------------------------------------------
+
+// Those MIN/MAX values are not define because we need to point to them
+static const ImS32 IM_S32_MIN = INT_MIN; // (-2147483647 - 1), (0x80000000);
+static const ImS32 IM_S32_MAX = INT_MAX; // (2147483647), (0x7FFFFFFF)
+static const ImU32 IM_U32_MIN = 0;
+static const ImU32 IM_U32_MAX = UINT_MAX; // (0xFFFFFFFF)
+#ifdef LLONG_MIN
+static const ImS64 IM_S64_MIN = LLONG_MIN; // (-9223372036854775807ll - 1ll);
+static const ImS64 IM_S64_MAX = LLONG_MAX; // (9223372036854775807ll);
+#else
+static const ImS64 IM_S64_MIN = -9223372036854775807LL - 1;
+static const ImS64 IM_S64_MAX = 9223372036854775807LL;
+#endif
+static const ImU64 IM_U64_MIN = 0;
+#ifdef ULLONG_MAX
+static const ImU64 IM_U64_MAX = ULLONG_MAX; // (0xFFFFFFFFFFFFFFFFull);
+#else
+static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1);
+#endif
+
+//-------------------------------------------------------------------------
+// Forward Declarations
+//-------------------------------------------------------------------------
+
+// Data Type helpers
+static inline int DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* data_ptr, const char* format);
+static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, void* arg_1, const void* arg_2);
+static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* format);
+
+// For InputTextEx()
+static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data);
+static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end);
+static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false);
+
+namespace ImGui
+{
+
+// Template widget behaviors
+template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
+static bool DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, float power);
+
+template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
+static bool SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, TYPE* v, const TYPE v_min, const TYPE v_max, const char* format, float power, ImGuiSliderFlags flags, ImRect* out_grab_bb);
+
+}
+
+//-------------------------------------------------------------------------
+// WIDGETS: Text
+// - TextUnformatted()
+// - Text()
+// - TextV()
+// - TextColored()
+// - TextColoredV()
+// - TextDisabled()
+// - TextDisabledV()
+// - TextWrapped()
+// - TextWrappedV()
+// - LabelText()
+// - LabelTextV()
+// - BulletText()
+// - BulletTextV()
+//-------------------------------------------------------------------------
+
+void ImGui::TextUnformatted(const char* text, const char* text_end)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return;
+
+ ImGuiContext& g = *GImGui;
+ IM_ASSERT(text != NULL);
+ const char* text_begin = text;
+ if (text_end == NULL)
+ text_end = text + strlen(text); // FIXME-OPT
+
+ const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrentLineTextBaseOffset);
+ const float wrap_pos_x = window->DC.TextWrapPos;
+ const bool wrap_enabled = wrap_pos_x >= 0.0f;
+ if (text_end - text > 2000 && !wrap_enabled)
+ {
+ // Long text!
+ // Perform manual coarse clipping to optimize for long multi-line text
+ // From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled.
+ // We also don't vertically center the text within the line full height, which is unlikely to matter because we are likely the biggest and only item on the line.
+ const char* line = text;
+ const float line_height = GetTextLineHeight();
+ const ImRect clip_rect = window->ClipRect;
+ ImVec2 text_size(0,0);
+
+ if (text_pos.y <= clip_rect.Max.y)
+ {
+ ImVec2 pos = text_pos;
+
+ // Lines to skip (can't skip when logging text)
+ if (!g.LogEnabled)
+ {
+ int lines_skippable = (int)((clip_rect.Min.y - text_pos.y) / line_height);
+ if (lines_skippable > 0)
+ {
+ int lines_skipped = 0;
+ while (line < text_end && lines_skipped < lines_skippable)
+ {
+ const char* line_end = strchr(line, '\n');
+ if (!line_end)
+ line_end = text_end;
+ line = line_end + 1;
+ lines_skipped++;
+ }
+ pos.y += lines_skipped * line_height;
+ }
+ }
+
+ // Lines to render
+ if (line < text_end)
+ {
+ ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height));
+ while (line < text_end)
+ {
+ const char* line_end = strchr(line, '\n');
+ if (IsClippedEx(line_rect, 0, false))
+ break;
+
+ const ImVec2 line_size = CalcTextSize(line, line_end, false);
+ text_size.x = ImMax(text_size.x, line_size.x);
+ RenderText(pos, line, line_end, false);
+ if (!line_end)
+ line_end = text_end;
+ line = line_end + 1;
+ line_rect.Min.y += line_height;
+ line_rect.Max.y += line_height;
+ pos.y += line_height;
+ }
+
+ // Count remaining lines
+ int lines_skipped = 0;
+ while (line < text_end)
+ {
+ const char* line_end = strchr(line, '\n');
+ if (!line_end)
+ line_end = text_end;
+ line = line_end + 1;
+ lines_skipped++;
+ }
+ pos.y += lines_skipped * line_height;
+ }
+
+ text_size.y += (pos - text_pos).y;
+ }
+
+ ImRect bb(text_pos, text_pos + text_size);
+ ItemSize(bb);
+ ItemAdd(bb, 0);
+ }
+ else
+ {
+ const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f;
+ const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width);
+
+ // Account of baseline offset
+ ImRect bb(text_pos, text_pos + text_size);
+ ItemSize(text_size);
+ if (!ItemAdd(bb, 0))
+ return;
+
+ // Render (we don't hide text after ## in this end-user function)
+ RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width);
+ }
+}
+
+void ImGui::Text(const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ TextV(fmt, args);
+ va_end(args);
+}
+
+void ImGui::TextV(const char* fmt, va_list args)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return;
+
+ ImGuiContext& g = *GImGui;
+ const char* text_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
+ TextUnformatted(g.TempBuffer, text_end);
+}
+
+void ImGui::TextColored(const ImVec4& col, const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ TextColoredV(col, fmt, args);
+ va_end(args);
+}
+
+void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args)
+{
+ PushStyleColor(ImGuiCol_Text, col);
+ TextV(fmt, args);
+ PopStyleColor();
+}
+
+void ImGui::TextDisabled(const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ TextDisabledV(fmt, args);
+ va_end(args);
+}
+
+void ImGui::TextDisabledV(const char* fmt, va_list args)
+{
+ PushStyleColor(ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled]);
+ TextV(fmt, args);
+ PopStyleColor();
+}
+
+void ImGui::TextWrapped(const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ TextWrappedV(fmt, args);
+ va_end(args);
+}
+
+void ImGui::TextWrappedV(const char* fmt, va_list args)
+{
+ bool need_wrap = (GImGui->CurrentWindow->DC.TextWrapPos < 0.0f); // Keep existing wrap position is one ia already set
+ if (need_wrap) PushTextWrapPos(0.0f);
+ TextV(fmt, args);
+ if (need_wrap) PopTextWrapPos();
+}
+
+void ImGui::LabelText(const char* label, const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ LabelTextV(label, fmt, args);
+ va_end(args);
+}
+
+// Add a label+text combo aligned to other label+value widgets
+void ImGui::LabelTextV(const char* label, const char* fmt, va_list args)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return;
+
+ ImGuiContext& g = *GImGui;
+ const ImGuiStyle& style = g.Style;
+ const float w = CalcItemWidth();
+
+ const ImVec2 label_size = CalcTextSize(label, NULL, true);
+ const ImRect value_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2));
+ const ImRect total_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w + (label_size.x > 0.0f ? style.ItemInnerSpacing.x : 0.0f), style.FramePadding.y*2) + label_size);
+ ItemSize(total_bb, style.FramePadding.y);
+ if (!ItemAdd(total_bb, 0))
+ return;
+
+ // Render
+ const char* value_text_begin = &g.TempBuffer[0];
+ const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
+ RenderTextClipped(value_bb.Min, value_bb.Max, value_text_begin, value_text_end, NULL, ImVec2(0.0f,0.5f));
+ if (label_size.x > 0.0f)
+ RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label);
+}
+
+void ImGui::BulletText(const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ BulletTextV(fmt, args);
+ va_end(args);
+}
+
+// Text with a little bullet aligned to the typical tree node.
+void ImGui::BulletTextV(const char* fmt, va_list args)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return;
+
+ ImGuiContext& g = *GImGui;
+ const ImGuiStyle& style = g.Style;
+
+ const char* text_begin = g.TempBuffer;
+ const char* text_end = text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
+ const ImVec2 label_size = CalcTextSize(text_begin, text_end, false);
+ const float text_base_offset_y = ImMax(0.0f, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it
+ const float line_height = ImMax(ImMin(window->DC.CurrentLineSize.y, g.FontSize + g.Style.FramePadding.y*2), g.FontSize);
+ const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize + (label_size.x > 0.0f ? (label_size.x + style.FramePadding.x*2) : 0.0f), ImMax(line_height, label_size.y))); // Empty text doesn't add padding
+ ItemSize(bb);
+ if (!ItemAdd(bb, 0))
+ return;
+
+ // Render
+ RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f));
+ RenderText(bb.Min+ImVec2(g.FontSize + style.FramePadding.x*2, text_base_offset_y), text_begin, text_end, false);
+}
+
+//-------------------------------------------------------------------------
+// WIDGETS: Main
+// - ButtonBehavior() [Internal]
+// - Button()
+// - SmallButton()
+// - InvisibleButton()
+// - ArrowButton()
+// - CloseButton() [Internal]
+// - CollapseButton() [Internal]
+// - Scrollbar() [Internal]
+// - Image()
+// - ImageButton()
+// - Checkbox()
+// - CheckboxFlags()
+// - RadioButton()
+// - ProgressBar()
+// - Bullet()
+//-------------------------------------------------------------------------
+
+bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags)
+{
+ ImGuiContext& g = *GImGui;
+ ImGuiWindow* window = GetCurrentWindow();
+
+ if (flags & ImGuiButtonFlags_Disabled)
+ {
+ if (out_hovered) *out_hovered = false;
+ if (out_held) *out_held = false;
+ if (g.ActiveId == id) ClearActiveID();
+ return false;
+ }
+
+ // Default behavior requires click+release on same spot
+ if ((flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick)) == 0)
+ flags |= ImGuiButtonFlags_PressedOnClickRelease;
+
+ ImGuiWindow* backup_hovered_window = g.HoveredWindow;
+ if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window)
+ g.HoveredWindow = window;
+
+ bool pressed = false;
+ bool hovered = ItemHoverable(bb, id);
+
+ // Drag source doesn't report as hovered
+ if (hovered && g.DragDropActive && g.DragDropPayload.SourceId == id && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoDisableHover))
+ hovered = false;
+
+ // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button
+ if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers))
+ if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
+ {
+ hovered = true;
+ SetHoveredID(id);
+ if (CalcTypematicPressedRepeatAmount(g.HoveredIdTimer + 0.0001f, g.HoveredIdTimer + 0.0001f - g.IO.DeltaTime, 0.01f, 0.70f)) // FIXME: Our formula for CalcTypematicPressedRepeatAmount() is fishy
+ {
+ pressed = true;
+ FocusWindow(window);
+ }
+ }
+
+ if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window)
+ g.HoveredWindow = backup_hovered_window;
+
+ // AllowOverlap mode (rarely used) requires previous frame HoveredId to be null or to match. This allows using patterns where a later submitted widget overlaps a previous one.
+ if (hovered && (flags & ImGuiButtonFlags_AllowItemOverlap) && (g.HoveredIdPreviousFrame != id && g.HoveredIdPreviousFrame != 0))
+ hovered = false;
+
+ // Mouse
+ if (hovered)
+ {
+ if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt))
+ {
+ // | CLICKING | HOLDING with ImGuiButtonFlags_Repeat
+ // PressedOnClickRelease | <on release>* | <on repeat> <on repeat> .. (NOT on release) <-- MOST COMMON! (*) only if both click/release were over bounds
+ // PressedOnClick | <on click> | <on click> <on repeat> <on repeat> ..
+ // PressedOnRelease | <on release> | <on repeat> <on repeat> .. (NOT on release)
+ // PressedOnDoubleClick | <on dclick> | <on dclick> <on repeat> <on repeat> ..
+ // FIXME-NAV: We don't honor those different behaviors.
+ if ((flags & ImGuiButtonFlags_PressedOnClickRelease) && g.IO.MouseClicked[0])
+ {
+ SetActiveID(id, window);
+ if (!(flags & ImGuiButtonFlags_NoNavFocus))
+ SetFocusID(id, window);
+ FocusWindow(window);
+ }
+ if (((flags & ImGuiButtonFlags_PressedOnClick) && g.IO.MouseClicked[0]) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDoubleClicked[0]))
+ {
+ pressed = true;
+ if (flags & ImGuiButtonFlags_NoHoldingActiveID)
+ ClearActiveID();
+ else
+ SetActiveID(id, window); // Hold on ID
+ FocusWindow(window);
+ }
+ if ((flags & ImGuiButtonFlags_PressedOnRelease) && g.IO.MouseReleased[0])
+ {
+ if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps <on release>
+ pressed = true;
+ ClearActiveID();
+ }
+
+ // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above).
+ // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings.
+ if ((flags & ImGuiButtonFlags_Repeat) && g.ActiveId == id && g.IO.MouseDownDuration[0] > 0.0f && IsMouseClicked(0, true))
+ pressed = true;
+ }
+
+ if (pressed)
+ g.NavDisableHighlight = true;
+ }
+
+ // Gamepad/Keyboard navigation
+ // We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse.
+ if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover && (g.ActiveId == 0 || g.ActiveId == id || g.ActiveId == window->MoveId))
+ hovered = true;
+
+ if (g.NavActivateDownId == id)
+ {
+ bool nav_activated_by_code = (g.NavActivateId == id);
+ bool nav_activated_by_inputs = IsNavInputPressed(ImGuiNavInput_Activate, (flags & ImGuiButtonFlags_Repeat) ? ImGuiInputReadMode_Repeat : ImGuiInputReadMode_Pressed);
+ if (nav_activated_by_code || nav_activated_by_inputs)
+ pressed = true;
+ if (nav_activated_by_code || nav_activated_by_inputs || g.ActiveId == id)
+ {
+ // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.
+ g.NavActivateId = id; // This is so SetActiveId assign a Nav source
+ SetActiveID(id, window);
+ if (!(flags & ImGuiButtonFlags_NoNavFocus))
+ SetFocusID(id, window);
+ g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right) | (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
+ }
+ }
+
+ bool held = false;
+ if (g.ActiveId == id)
+ {
+ if (g.ActiveIdSource == ImGuiInputSource_Mouse)
+ {
+ if (g.ActiveIdIsJustActivated)
+ g.ActiveIdClickOffset = g.IO.MousePos - bb.Min;
+ if (g.IO.MouseDown[0])
+ {
+ held = true;
+ }
+ else
+ {
+ if (hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease))
+ if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps <on release>
+ if (!g.DragDropActive)
+ pressed = true;
+ ClearActiveID();
+ }
+ if (!(flags & ImGuiButtonFlags_NoNavFocus))
+ g.NavDisableHighlight = true;
+ }
+ else if (g.ActiveIdSource == ImGuiInputSource_Nav)
+ {
+ if (g.NavActivateDownId != id)
+ ClearActiveID();
+ }
+ }
+
+ if (out_hovered) *out_hovered = hovered;
+ if (out_held) *out_held = held;
+
+ return pressed;
+}
+
+bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ ImGuiContext& g = *GImGui;
+ const ImGuiStyle& style = g.Style;
+ const ImGuiID id = window->GetID(label);
+ const ImVec2 label_size = CalcTextSize(label, NULL, true);
+
+ ImVec2 pos = window->DC.CursorPos;
+ if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrentLineTextBaseOffset) // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag)
+ pos.y += window->DC.CurrentLineTextBaseOffset - style.FramePadding.y;
+ ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f);
+
+ const ImRect bb(pos, pos + size);
+ ItemSize(bb, style.FramePadding.y);
+ if (!ItemAdd(bb, id))
+ return false;
+
+ if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat)
+ flags |= ImGuiButtonFlags_Repeat;
+ bool hovered, held;
+ bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
+ if (pressed)
+ MarkItemEdited(id);
+
+ // Render
+ const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
+ RenderNavHighlight(bb, id);
+ RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
+ RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb);
+
+ // Automatically close popups
+ //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
+ // CloseCurrentPopup();
+
+ return pressed;
+}
+
+bool ImGui::Button(const char* label, const ImVec2& size_arg)
+{
+ return ButtonEx(label, size_arg, 0);
+}
+
+// Small buttons fits within text without additional vertical spacing.
+bool ImGui::SmallButton(const char* label)
+{
+ ImGuiContext& g = *GImGui;
+ float backup_padding_y = g.Style.FramePadding.y;
+ g.Style.FramePadding.y = 0.0f;
+ bool pressed = ButtonEx(label, ImVec2(0, 0), ImGuiButtonFlags_AlignTextBaseLine);
+ g.Style.FramePadding.y = backup_padding_y;
+ return pressed;
+}
+
+// Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack.
+// Then you can keep 'str_id' empty or the same for all your buttons (instead of creating a string based on a non-string id)
+bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ // Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size.
+ IM_ASSERT(size_arg.x != 0.0f && size_arg.y != 0.0f);
+
+ const ImGuiID id = window->GetID(str_id);
+ ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f);
+ const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
+ ItemSize(bb);
+ if (!ItemAdd(bb, id))
+ return false;
+
+ bool hovered, held;
+ bool pressed = ButtonBehavior(bb, id, &hovered, &held);
+
+ return pressed;
+}
+
+bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ ImGuiContext& g = *GImGui;
+ const ImGuiID id = window->GetID(str_id);
+ const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
+ const float default_size = GetFrameHeight();
+ ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
+ if (!ItemAdd(bb, id))
+ return false;
+
+ if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat)
+ flags |= ImGuiButtonFlags_Repeat;
+
+ bool hovered, held;
+ bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
+
+ // Render
+ const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
+ RenderNavHighlight(bb, id);
+ RenderFrame(bb.Min, bb.Max, col, true, g.Style.FrameRounding);
+ RenderArrow(bb.Min + ImVec2(ImMax(0.0f, size.x - g.FontSize - g.Style.FramePadding.x), ImMax(0.0f, size.y - g.FontSize - g.Style.FramePadding.y)), dir);
+
+ return pressed;
+}
+
+bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir)
+{
+ float sz = GetFrameHeight();
+ return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), 0);
+}
+
+// Button to close a window
+bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos, float radius)
+{
+ ImGuiContext& g = *GImGui;
+ ImGuiWindow* window = g.CurrentWindow;
+
+ // We intentionally allow interaction when clipped so that a mechanical Alt,Right,Validate sequence close a window.
+ // (this isn't the regular behavior of buttons, but it doesn't affect the user much because navigation tends to keep items visible).
+ const ImRect bb(pos - ImVec2(radius,radius), pos + ImVec2(radius,radius));
+ bool is_clipped = !ItemAdd(bb, id);
+
+ bool hovered, held;
+ bool pressed = ButtonBehavior(bb, id, &hovered, &held);
+ if (is_clipped)
+ return pressed;
+
+ // Render
+ ImVec2 center = bb.GetCenter();
+ if (hovered)
+ window->DrawList->AddCircleFilled(center, ImMax(2.0f, radius), GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered), 9);
+
+ float cross_extent = (radius * 0.7071f) - 1.0f;
+ ImU32 cross_col = GetColorU32(ImGuiCol_Text);
+ center -= ImVec2(0.5f, 0.5f);
+ window->DrawList->AddLine(center + ImVec2(+cross_extent,+cross_extent), center + ImVec2(-cross_extent,-cross_extent), cross_col, 1.0f);
+ window->DrawList->AddLine(center + ImVec2(+cross_extent,-cross_extent), center + ImVec2(-cross_extent,+cross_extent), cross_col, 1.0f);
+
+ return pressed;
+}
+
+bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos)
+{
+ ImGuiContext& g = *GImGui;
+ ImGuiWindow* window = g.CurrentWindow;
+
+ ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f);
+ ItemAdd(bb, id);
+ bool hovered, held;
+ bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None);
+
+ ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
+ if (hovered || held)
+ window->DrawList->AddCircleFilled(bb.GetCenter() + ImVec2(0.0f, -0.5f), g.FontSize * 0.5f + 1.0f, col, 9);
+ RenderArrow(bb.Min + g.Style.FramePadding, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f);
+
+ // Switch to moving the window after mouse is moved beyond the initial drag threshold
+ if (IsItemActive() && IsMouseDragging())
+ StartMouseMovingWindow(window);
+
+ return pressed;
+}
+
+// Vertical/Horizontal scrollbar
+// The entire piece of code below is rather confusing because:
+// - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab)
+// - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar
+// - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal.
+void ImGui::Scrollbar(ImGuiLayoutType direction)
+{
+ ImGuiContext& g = *GImGui;
+ ImGuiWindow* window = g.CurrentWindow;
+
+ const bool horizontal = (direction == ImGuiLayoutType_Horizontal);
+ const ImGuiStyle& style = g.Style;
+ const ImGuiID id = window->GetID(horizontal ? "#SCROLLX" : "#SCROLLY");
+
+ // Render background
+ bool other_scrollbar = (horizontal ? window->ScrollbarY : window->ScrollbarX);
+ float other_scrollbar_size_w = other_scrollbar ? style.ScrollbarSize : 0.0f;
+ const ImRect window_rect = window->Rect();
+ const float border_size = window->WindowBorderSize;
+ ImRect bb = horizontal
+ ? ImRect(window->Pos.x + border_size, window_rect.Max.y - style.ScrollbarSize, window_rect.Max.x - other_scrollbar_size_w - border_size, window_rect.Max.y - border_size)
+ : ImRect(window_rect.Max.x - style.ScrollbarSize, window->Pos.y + border_size, window_rect.Max.x - border_size, window_rect.Max.y - other_scrollbar_size_w - border_size);
+ if (!horizontal)
+ bb.Min.y += window->TitleBarHeight() + ((window->Flags & ImGuiWindowFlags_MenuBar) ? window->MenuBarHeight() : 0.0f);
+ if (bb.GetWidth() <= 0.0f || bb.GetHeight() <= 0.0f)
+ return;
+
+ int window_rounding_corners;
+ if (horizontal)
+ window_rounding_corners = ImDrawCornerFlags_BotLeft | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight);
+ else
+ window_rounding_corners = (((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) ? ImDrawCornerFlags_TopRight : 0) | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight);
+ window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_ScrollbarBg), window->WindowRounding, window_rounding_corners);
+ bb.Expand(ImVec2(-ImClamp((float)(int)((bb.Max.x - bb.Min.x - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp((float)(int)((bb.Max.y - bb.Min.y - 2.0f) * 0.5f), 0.0f, 3.0f)));
+
+ // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar)
+ float scrollbar_size_v = horizontal ? bb.GetWidth() : bb.GetHeight();
+ float scroll_v = horizontal ? window->Scroll.x : window->Scroll.y;
+ float win_size_avail_v = (horizontal ? window->SizeFull.x : window->SizeFull.y) - other_scrollbar_size_w;
+ float win_size_contents_v = horizontal ? window->SizeContents.x : window->SizeContents.y;
+
+ // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount)
+ // But we maintain a minimum size in pixel to allow for the user to still aim inside.
+ IM_ASSERT(ImMax(win_size_contents_v, win_size_avail_v) > 0.0f); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers.
+ const float win_size_v = ImMax(ImMax(win_size_contents_v, win_size_avail_v), 1.0f);
+ const float grab_h_pixels = ImClamp(scrollbar_size_v * (win_size_avail_v / win_size_v), style.GrabMinSize, scrollbar_size_v);
+ const float grab_h_norm = grab_h_pixels / scrollbar_size_v;
+
+ // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar().
+ bool held = false;
+ bool hovered = false;
+ const bool previously_held = (g.ActiveId == id);
+ ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus);
+
+ float scroll_max = ImMax(1.0f, win_size_contents_v - win_size_avail_v);
+ float scroll_ratio = ImSaturate(scroll_v / scroll_max);
+ float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
+ if (held && grab_h_norm < 1.0f)
+ {
+ float scrollbar_pos_v = horizontal ? bb.Min.x : bb.Min.y;
+ float mouse_pos_v = horizontal ? g.IO.MousePos.x : g.IO.MousePos.y;
+ float* click_delta_to_grab_center_v = horizontal ? &g.ScrollbarClickDeltaToGrabCenter.x : &g.ScrollbarClickDeltaToGrabCenter.y;
+
+ // Click position in scrollbar normalized space (0.0f->1.0f)
+ const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v);
+ SetHoveredID(id);
+
+ bool seek_absolute = false;
+ if (!previously_held)
+ {
+ // On initial click calculate the distance between mouse and the center of the grab
+ if (clicked_v_norm >= grab_v_norm && clicked_v_norm <= grab_v_norm + grab_h_norm)
+ {
+ *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f;
+ }
+ else
+ {
+ seek_absolute = true;
+ *click_delta_to_grab_center_v = 0.0f;
+ }
+ }
+
+ // Apply scroll
+ // It is ok to modify Scroll here because we are being called in Begin() after the calculation of SizeContents and before setting up our starting position
+ const float scroll_v_norm = ImSaturate((clicked_v_norm - *click_delta_to_grab_center_v - grab_h_norm*0.5f) / (1.0f - grab_h_norm));
+ scroll_v = (float)(int)(0.5f + scroll_v_norm * scroll_max);//(win_size_contents_v - win_size_v));
+ if (horizontal)
+ window->Scroll.x = scroll_v;
+ else
+ window->Scroll.y = scroll_v;
+
+ // Update values for rendering
+ scroll_ratio = ImSaturate(scroll_v / scroll_max);
+ grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
+
+ // Update distance to grab now that we have seeked and saturated
+ if (seek_absolute)
+ *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f;
+ }
+
+ // Render
+ const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab);
+ ImRect grab_rect;
+ if (horizontal)
+ grab_rect = ImRect(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y, ImMin(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, window_rect.Max.x), bb.Max.y);
+ else
+ grab_rect = ImRect(bb.Min.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm), bb.Max.x, ImMin(ImLerp(bb.Min.y, bb.Max.y, grab_v_norm) + grab_h_pixels, window_rect.Max.y));
+ window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding);
+}
+
+void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return;
+
+ ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
+ if (border_col.w > 0.0f)
+ bb.Max += ImVec2(2, 2);
+ ItemSize(bb);
+ if (!ItemAdd(bb, 0))
+ return;
+
+ if (border_col.w > 0.0f)
+ {
+ window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f);
+ window->DrawList->AddImage(user_texture_id, bb.Min + ImVec2(1, 1), bb.Max - ImVec2(1, 1), uv0, uv1, GetColorU32(tint_col));
+ }
+ else
+ {
+ window->DrawList->AddImage(user_texture_id, bb.Min, bb.Max, uv0, uv1, GetColorU32(tint_col));
+ }
+}
+
+// frame_padding < 0: uses FramePadding from style (default)
+// frame_padding = 0: no framing
+// frame_padding > 0: set framing size
+// The color used are the button colors.
+bool ImGui::ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ ImGuiContext& g = *GImGui;
+ const ImGuiStyle& style = g.Style;
+
+ // Default to using texture ID as ID. User can still push string/integer prefixes.
+ // We could hash the size/uv to create a unique ID but that would prevent the user from animating UV.
+ PushID((void*)user_texture_id);
+ const ImGuiID id = window->GetID("#image");
+ PopID();
+
+ const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : style.FramePadding;
+ const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2);
+ const ImRect image_bb(window->DC.CursorPos + padding, window->DC.CursorPos + padding + size);
+ ItemSize(bb);
+ if (!ItemAdd(bb, id))
+ return false;
+
+ bool hovered, held;
+ bool pressed = ButtonBehavior(bb, id, &hovered, &held);
+
+ // Render
+ const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
+ RenderNavHighlight(bb, id);
+ RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, style.FrameRounding));
+ if (bg_col.w > 0.0f)
+ window->DrawList->AddRectFilled(image_bb.Min, image_bb.Max, GetColorU32(bg_col));
+ window->DrawList->AddImage(user_texture_id, image_bb.Min, image_bb.Max, uv0, uv1, GetColorU32(tint_col));
+
+ return pressed;
+}
+
+bool ImGui::Checkbox(const char* label, bool* v)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ ImGuiContext& g = *GImGui;
+ const ImGuiStyle& style = g.Style;
+ const ImGuiID id = window->GetID(label);
+ const ImVec2 label_size = CalcTextSize(label, NULL, true);
+
+ const ImRect check_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(label_size.y + style.FramePadding.y*2, label_size.y + style.FramePadding.y*2)); // We want a square shape to we use Y twice
+ ItemSize(check_bb, style.FramePadding.y);
+
+ ImRect total_bb = check_bb;
+ if (label_size.x > 0)
+ SameLine(0, style.ItemInnerSpacing.x);
+ const ImRect text_bb(window->DC.CursorPos + ImVec2(0,style.FramePadding.y), window->DC.CursorPos + ImVec2(0,style.FramePadding.y) + label_size);
+ if (label_size.x > 0)
+ {
+ ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y);
+ total_bb = ImRect(ImMin(check_bb.Min, text_bb.Min), ImMax(check_bb.Max, text_bb.Max));
+ }
+
+ if (!ItemAdd(total_bb, id))
+ return false;
+
+ bool hovered, held;
+ bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
+ if (pressed)
+ {
+ *v = !(*v);
+ MarkItemEdited(id);
+ }
+
+ RenderNavHighlight(total_bb, id);
+ RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding);
+ if (*v)
+ {
+ const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight());
+ const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f));
+ RenderCheckMark(check_bb.Min + ImVec2(pad,pad), GetColorU32(ImGuiCol_CheckMark), check_bb.GetWidth() - pad*2.0f);
+ }
+
+ if (g.LogEnabled)
+ LogRenderedText(&text_bb.Min, *v ? "[x]" : "[ ]");
+ if (label_size.x > 0.0f)
+ RenderText(text_bb.Min, label);
+
+ return pressed;
+}
+
+bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value)
+{
+ bool v = ((*flags & flags_value) == flags_value);
+ bool pressed = Checkbox(label, &v);
+ if (pressed)
+ {
+ if (v)
+ *flags |= flags_value;
+ else
+ *flags &= ~flags_value;
+ }
+
+ return pressed;
+}
+
+bool ImGui::RadioButton(const char* label, bool active)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ ImGuiContext& g = *GImGui;
+ const ImGuiStyle& style = g.Style;
+ const ImGuiID id = window->GetID(label);
+ const ImVec2 label_size = CalcTextSize(label, NULL, true);
+
+ const ImRect check_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(label_size.y + style.FramePadding.y*2-1, label_size.y + style.FramePadding.y*2-1));
+ ItemSize(check_bb, style.FramePadding.y);
+
+ ImRect total_bb = check_bb;
+ if (label_size.x > 0)
+ SameLine(0, style.ItemInnerSpacing.x);
+ const ImRect text_bb(window->DC.CursorPos + ImVec2(0, style.FramePadding.y), window->DC.CursorPos + ImVec2(0, style.FramePadding.y) + label_size);
+ if (label_size.x > 0)
+ {
+ ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y);
+ total_bb.Add(text_bb);
+ }
+
+ if (!ItemAdd(total_bb, id))
+ return false;
+
+ ImVec2 center = check_bb.GetCenter();
+ center.x = (float)(int)center.x + 0.5f;
+ center.y = (float)(int)center.y + 0.5f;
+ const float radius = check_bb.GetHeight() * 0.5f;
+
+ bool hovered, held;
+ bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
+ if (pressed)
+ MarkItemEdited(id);
+
+ RenderNavHighlight(total_bb, id);
+ window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16);
+ if (active)
+ {
+ const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight());
+ const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f));
+ window->DrawList->AddCircleFilled(center, radius-pad, GetColorU32(ImGuiCol_CheckMark), 16);
+ }
+
+ if (style.FrameBorderSize > 0.0f)
+ {
+ window->DrawList->AddCircle(center+ImVec2(1,1), radius, GetColorU32(ImGuiCol_BorderShadow), 16, style.FrameBorderSize);
+ window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), 16, style.FrameBorderSize);
+ }
+
+ if (g.LogEnabled)
+ LogRenderedText(&text_bb.Min, active ? "(x)" : "( )");
+ if (label_size.x > 0.0f)
+ RenderText(text_bb.Min, label);
+
+ return pressed;
+}
+
+bool ImGui::RadioButton(const char* label, int* v, int v_button)
+{
+ const bool pressed = RadioButton(label, *v == v_button);
+ if (pressed)
+ *v = v_button;
+ return pressed;
+}
+
+// size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size
+void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return;
+
+ ImGuiContext& g = *GImGui;
+ const ImGuiStyle& style = g.Style;
+
+ ImVec2 pos = window->DC.CursorPos;
+ ImRect bb(pos, pos + CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y*2.0f));
+ ItemSize(bb, style.FramePadding.y);
+ if (!ItemAdd(bb, 0))
+ return;
+
+ // Render
+ fraction = ImSaturate(fraction);
+ RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
+ bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize));
+ const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y);
+ RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0f, fraction, style.FrameRounding);
+
+ // Default displaying the fraction as percentage string, but user can override it
+ char overlay_buf[32];
+ if (!overlay)
+ {
+ ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction*100+0.01f);
+ overlay = overlay_buf;
+ }
+
+ ImVec2 overlay_size = CalcTextSize(overlay, NULL);
+ if (overlay_size.x > 0.0f)
+ RenderTextClipped(ImVec2(ImClamp(fill_br.x + style.ItemSpacing.x, bb.Min.x, bb.Max.x - overlay_size.x - style.ItemInnerSpacing.x), bb.Min.y), bb.Max, overlay, NULL, &overlay_size, ImVec2(0.0f,0.5f), &bb);
+}
+
+void ImGui::Bullet()
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return;
+
+ ImGuiContext& g = *GImGui;
+ const ImGuiStyle& style = g.Style;
+ const float line_height = ImMax(ImMin(window->DC.CurrentLineSize.y, g.FontSize + g.Style.FramePadding.y*2), g.FontSize);
+ const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height));
+ ItemSize(bb);
+ if (!ItemAdd(bb, 0))
+ {
+ SameLine(0, style.FramePadding.x*2);
+ return;
+ }
+
+ // Render and stay on same line
+ RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f));
+ SameLine(0, style.FramePadding.x*2);
+}
+
+//-------------------------------------------------------------------------
+// WIDGETS: Combo Box
+// - BeginCombo()
+// - EndCombo()
+// - Combo()
+//-------------------------------------------------------------------------
+
+static float CalcMaxPopupHeightFromItemCount(int items_count)
+{
+ ImGuiContext& g = *GImGui;
+ if (items_count <= 0)
+ return FLT_MAX;
+ return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2);
+}
+
+bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags)
+{
+ // Always consume the SetNextWindowSizeConstraint() call in our early return paths
+ ImGuiContext& g = *GImGui;
+ ImGuiCond backup_next_window_size_constraint = g.NextWindowData.SizeConstraintCond;
+ g.NextWindowData.SizeConstraintCond = 0;
+
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together
+
+ const ImGuiStyle& style = g.Style;
+ const ImGuiID id = window->GetID(label);
+
+ const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight();
+ const ImVec2 label_size = CalcTextSize(label, NULL, true);
+ const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : CalcItemWidth();
+ const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
+ const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
+ ItemSize(total_bb, style.FramePadding.y);
+ if (!ItemAdd(total_bb, id, &frame_bb))
+ return false;
+
+ bool hovered, held;
+ bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held);
+ bool popup_open = IsPopupOpen(id);
+
+ const ImRect value_bb(frame_bb.Min, frame_bb.Max - ImVec2(arrow_size, 0.0f));
+ const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
+ RenderNavHighlight(frame_bb, id);
+ if (!(flags & ImGuiComboFlags_NoPreview))
+ window->DrawList->AddRectFilled(frame_bb.Min, ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Max.y), frame_col, style.FrameRounding, ImDrawCornerFlags_Left);
+ if (!(flags & ImGuiComboFlags_NoArrowButton))
+ {
+ window->DrawList->AddRectFilled(ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Min.y), frame_bb.Max, GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button), style.FrameRounding, (w <= arrow_size) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Right);
+ RenderArrow(ImVec2(frame_bb.Max.x - arrow_size + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), ImGuiDir_Down);
+ }
+ RenderFrameBorder(frame_bb.Min, frame_bb.Max, style.FrameRounding);
+ if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview))
+ RenderTextClipped(frame_bb.Min + style.FramePadding, value_bb.Max, preview_value, NULL, NULL, ImVec2(0.0f,0.0f));
+ if (label_size.x > 0)
+ RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
+
+ if ((pressed || g.NavActivateId == id) && !popup_open)
+ {
+ if (window->DC.NavLayerCurrent == 0)
+ window->NavLastIds[0] = id;
+ OpenPopupEx(id);
+ popup_open = true;
+ }
+
+ if (!popup_open)
+ return false;
+
+ if (backup_next_window_size_constraint)
+ {
+ g.NextWindowData.SizeConstraintCond = backup_next_window_size_constraint;
+ g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w);
+ }
+ else
+ {
+ if ((flags & ImGuiComboFlags_HeightMask_) == 0)
+ flags |= ImGuiComboFlags_HeightRegular;
+ IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one
+ int popup_max_height_in_items = -1;
+ if (flags & ImGuiComboFlags_HeightRegular) popup_max_height_in_items = 8;
+ else if (flags & ImGuiComboFlags_HeightSmall) popup_max_height_in_items = 4;
+ else if (flags & ImGuiComboFlags_HeightLarge) popup_max_height_in_items = 20;
+ SetNextWindowSizeConstraints(ImVec2(w, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
+ }
+
+ char name[16];
+ ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.CurrentPopupStack.Size); // Recycle windows based on depth
+
+ // Peak into expected window size so we can position it
+ if (ImGuiWindow* popup_window = FindWindowByName(name))
+ if (popup_window->WasActive)
+ {
+ ImVec2 size_expected = CalcWindowExpectedSize(popup_window);
+ if (flags & ImGuiComboFlags_PopupAlignLeft)
+ popup_window->AutoPosLastDirection = ImGuiDir_Left;
+ ImRect r_outer = GetWindowAllowedExtentRect(popup_window);
+ ImVec2 pos = FindBestWindowPosForPopupEx(frame_bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, frame_bb, ImGuiPopupPositionPolicy_ComboBox);
+ SetNextWindowPos(pos);
+ }
+
+ // Horizontally align ourselves with the framed text
+ ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings;
+ PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(style.FramePadding.x, style.WindowPadding.y));
+ bool ret = Begin(name, NULL, window_flags);
+ PopStyleVar();
+ if (!ret)
+ {
+ EndPopup();
+ IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above
+ return false;
+ }
+ return true;
+}
+
+void ImGui::EndCombo()
+{
+ EndPopup();
+}
+
+// Getter for the old Combo() API: const char*[]
+static bool Items_ArrayGetter(void* data, int idx, const char** out_text)
+{
+ const char* const* items = (const char* const*)data;
+ if (out_text)
+ *out_text = items[idx];
+ return true;
+}
+
+// Getter for the old Combo() API: "item1\0item2\0item3\0"
+static bool Items_SingleStringGetter(void* data, int idx, const char** out_text)
+{
+ // FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited.
+ const char* items_separated_by_zeros = (const char*)data;
+ int items_count = 0;
+ const char* p = items_separated_by_zeros;
+ while (*p)
+ {
+ if (idx == items_count)
+ break;
+ p += strlen(p) + 1;
+ items_count++;
+ }
+ if (!*p)
+ return false;
+ if (out_text)
+ *out_text = p;
+ return true;
+}
+
+// Old API, prefer using BeginCombo() nowadays if you can.
+bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int popup_max_height_in_items)
+{
+ ImGuiContext& g = *GImGui;
+
+ // Call the getter to obtain the preview string which is a parameter to BeginCombo()
+ const char* preview_value = NULL;
+ if (*current_item >= 0 && *current_item < items_count)
+ items_getter(data, *current_item, &preview_value);
+
+ // The old Combo() API exposed "popup_max_height_in_items". The new more general BeginCombo() API doesn't have/need it, but we emulate it here.
+ if (popup_max_height_in_items != -1 && !g.NextWindowData.SizeConstraintCond)
+ SetNextWindowSizeConstraints(ImVec2(0,0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
+
+ if (!BeginCombo(label, preview_value, ImGuiComboFlags_None))
+ return false;
+
+ // Display items
+ // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed)
+ bool value_changed = false;
+ for (int i = 0; i < items_count; i++)
+ {
+ PushID((void*)(intptr_t)i);
+ const bool item_selected = (i == *current_item);
+ const char* item_text;
+ if (!items_getter(data, i, &item_text))
+ item_text = "*Unknown item*";
+ if (Selectable(item_text, item_selected))
+ {
+ value_changed = true;
+ *current_item = i;
+ }
+ if (item_selected)
+ SetItemDefaultFocus();
+ PopID();
+ }
+
+ EndCombo();
+ return value_changed;
+}
+
+// Combo box helper allowing to pass an array of strings.
+bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items)
+{
+ const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items);
+ return value_changed;
+}
+
+// Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0"
+bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items)
+{
+ int items_count = 0;
+ const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open
+ while (*p)
+ {
+ p += strlen(p) + 1;
+ items_count++;
+ }
+ bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items);
+ return value_changed;
+}
+
+//-------------------------------------------------------------------------
+// WIDGETS: Data Type and Data Formatting Helpers [Internal]
+// - PatchFormatStringFloatToInt()
+// - DataTypeFormatString()
+// - DataTypeApplyOp()
+// - DataTypeApplyOpFromText()
+// - GetMinimumStepAtDecimalPrecision
+// - RoundScalarWithFormat<>()
+//-------------------------------------------------------------------------
+
+struct ImGuiDataTypeInfo
+{
+ size_t Size;
+ const char* PrintFmt; // Unused
+ const char* ScanFmt;
+};
+
+static const ImGuiDataTypeInfo GDataTypeInfo[] =
+{
+ { sizeof(int), "%d", "%d" },
+ { sizeof(unsigned int), "%u", "%u" },
+#ifdef _MSC_VER
+ { sizeof(ImS64), "%I64d","%I64d" },
+ { sizeof(ImU64), "%I64u","%I64u" },
+#else
+ { sizeof(ImS64), "%lld", "%lld" },
+ { sizeof(ImU64), "%llu", "%llu" },
+#endif
+ { sizeof(float), "%f", "%f" }, // float are promoted to double in va_arg
+ { sizeof(double), "%f", "%lf" },
+};
+IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT);
+
+// FIXME-LEGACY: Prior to 1.61 our DragInt() function internally used floats and because of this the compile-time default value for format was "%.0f".
+// Even though we changed the compile-time default, we expect users to have carried %f around, which would break the display of DragInt() calls.
+// To honor backward compatibility we are rewriting the format string, unless IMGUI_DISABLE_OBSOLETE_FUNCTIONS is enabled. What could possibly go wrong?!
+static const char* PatchFormatStringFloatToInt(const char* fmt)
+{
+ if (fmt[0] == '%' && fmt[1] == '.' && fmt[2] == '0' && fmt[3] == 'f' && fmt[4] == 0) // Fast legacy path for "%.0f" which is expected to be the most common case.
+ return "%d";
+ const char* fmt_start = ImParseFormatFindStart(fmt); // Find % (if any, and ignore %%)
+ const char* fmt_end = ImParseFormatFindEnd(fmt_start); // Find end of format specifier, which itself is an exercise of confidence/recklessness (because snprintf is dependent on libc or user).
+ if (fmt_end > fmt_start && fmt_end[-1] == 'f')
+ {
+#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
+ if (fmt_start == fmt && fmt_end[0] == 0)
+ return "%d";
+ ImGuiContext& g = *GImGui;
+ ImFormatString(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), "%.*s%%d%s", (int)(fmt_start - fmt), fmt, fmt_end); // Honor leading and trailing decorations, but lose alignment/precision.
+ return g.TempBuffer;
+#else
+ IM_ASSERT(0 && "DragInt(): Invalid format string!"); // Old versions used a default parameter of "%.0f", please replace with e.g. "%d"
+#endif
+ }
+ return fmt;
+}
+
+static inline int DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* data_ptr, const char* format)
+{
+ if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32) // Signedness doesn't matter when pushing the argument
+ return ImFormatString(buf, buf_size, format, *(const ImU32*)data_ptr);
+ if (data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64) // Signedness doesn't matter when pushing the argument
+ return ImFormatString(buf, buf_size, format, *(const ImU64*)data_ptr);
+ if (data_type == ImGuiDataType_Float)
+ return ImFormatString(buf, buf_size, format, *(const float*)data_ptr);
+ if (data_type == ImGuiDataType_Double)
+ return ImFormatString(buf, buf_size, format, *(const double*)data_ptr);
+ IM_ASSERT(0);
+ return 0;
+}
+
+// FIXME: Adding support for clamping on boundaries of the data type would be nice.
+static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, void* arg1, const void* arg2)
+{
+ IM_ASSERT(op == '+' || op == '-');
+ switch (data_type)
+ {
+ case ImGuiDataType_S32:
+ if (op == '+') *(int*)output = *(const int*)arg1 + *(const int*)arg2;
+ else if (op == '-') *(int*)output = *(const int*)arg1 - *(const int*)arg2;
+ return;
+ case ImGuiDataType_U32:
+ if (op == '+') *(unsigned int*)output = *(const unsigned int*)arg1 + *(const ImU32*)arg2;
+ else if (op == '-') *(unsigned int*)output = *(const unsigned int*)arg1 - *(const ImU32*)arg2;
+ return;
+ case ImGuiDataType_S64:
+ if (op == '+') *(ImS64*)output = *(const ImS64*)arg1 + *(const ImS64*)arg2;
+ else if (op == '-') *(ImS64*)output = *(const ImS64*)arg1 - *(const ImS64*)arg2;
+ return;
+ case ImGuiDataType_U64:
+ if (op == '+') *(ImU64*)output = *(const ImU64*)arg1 + *(const ImU64*)arg2;
+ else if (op == '-') *(ImU64*)output = *(const ImU64*)arg1 - *(const ImU64*)arg2;
+ return;
+ case ImGuiDataType_Float:
+ if (op == '+') *(float*)output = *(const float*)arg1 + *(const float*)arg2;
+ else if (op == '-') *(float*)output = *(const float*)arg1 - *(const float*)arg2;
+ return;
+ case ImGuiDataType_Double:
+ if (op == '+') *(double*)output = *(const double*)arg1 + *(const double*)arg2;
+ else if (op == '-') *(double*)output = *(const double*)arg1 - *(const double*)arg2;
+ return;
+ case ImGuiDataType_COUNT: break;
+ }
+ IM_ASSERT(0);
+}
+
+// User can input math operators (e.g. +100) to edit a numerical values.
+// NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess..
+static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* format)
+{
+ while (ImCharIsBlankA(*buf))
+ buf++;
+
+ // We don't support '-' op because it would conflict with inputing negative value.
+ // Instead you can use +-100 to subtract from an existing value
+ char op = buf[0];
+ if (op == '+' || op == '*' || op == '/')
+ {
+ buf++;
+ while (ImCharIsBlankA(*buf))
+ buf++;
+ }
+ else
+ {
+ op = 0;
+ }
+ if (!buf[0])
+ return false;
+
+ // Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all.
+ IM_ASSERT(data_type < ImGuiDataType_COUNT);
+ int data_backup[2];
+ IM_ASSERT(GDataTypeInfo[data_type].Size <= sizeof(data_backup));
+ memcpy(data_backup, data_ptr, GDataTypeInfo[data_type].Size);
+
+ if (format == NULL)
+ format = GDataTypeInfo[data_type].ScanFmt;
+
+ int arg1i = 0;
+ if (data_type == ImGuiDataType_S32)
+ {
+ int* v = (int*)data_ptr;
+ int arg0i = *v;
+ float arg1f = 0.0f;
+ if (op && sscanf(initial_value_buf, format, &arg0i) < 1)
+ return false;
+ // Store operand in a float so we can use fractional value for multipliers (*1.1), but constant always parsed as integer so we can fit big integers (e.g. 2000000003) past float precision
+ if (op == '+') { if (sscanf(buf, "%d", &arg1i)) *v = (int)(arg0i + arg1i); } // Add (use "+-" to subtract)
+ else if (op == '*') { if (sscanf(buf, "%f", &arg1f)) *v = (int)(arg0i * arg1f); } // Multiply
+ else if (op == '/') { if (sscanf(buf, "%f", &arg1f) && arg1f != 0.0f) *v = (int)(arg0i / arg1f); } // Divide
+ else { if (sscanf(buf, format, &arg1i) == 1) *v = arg1i; } // Assign constant
+ }
+ else if (data_type == ImGuiDataType_U32 || data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64)
+ {
+ // Assign constant
+ // FIXME: We don't bother handling support for legacy operators since they are a little too crappy. Instead we may implement a proper expression evaluator in the future.
+ sscanf(buf, format, data_ptr);
+ }
+ else if (data_type == ImGuiDataType_Float)
+ {
+ // For floats we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in
+ format = "%f";
+ float* v = (float*)data_ptr;
+ float arg0f = *v, arg1f = 0.0f;
+ if (op && sscanf(initial_value_buf, format, &arg0f) < 1)
+ return false;
+ if (sscanf(buf, format, &arg1f) < 1)
+ return false;
+ if (op == '+') { *v = arg0f + arg1f; } // Add (use "+-" to subtract)
+ else if (op == '*') { *v = arg0f * arg1f; } // Multiply
+ else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide
+ else { *v = arg1f; } // Assign constant
+ }
+ else if (data_type == ImGuiDataType_Double)
+ {
+ format = "%lf"; // scanf differentiate float/double unlike printf which forces everything to double because of ellipsis
+ double* v = (double*)data_ptr;
+ double arg0f = *v, arg1f = 0.0;
+ if (op && sscanf(initial_value_buf, format, &arg0f) < 1)
+ return false;
+ if (sscanf(buf, format, &arg1f) < 1)
+ return false;
+ if (op == '+') { *v = arg0f + arg1f; } // Add (use "+-" to subtract)
+ else if (op == '*') { *v = arg0f * arg1f; } // Multiply
+ else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide
+ else { *v = arg1f; } // Assign constant
+ }
+ return memcmp(data_backup, data_ptr, GDataTypeInfo[data_type].Size) != 0;
+}
+
+static float GetMinimumStepAtDecimalPrecision(int decimal_precision)
+{
+ static const float min_steps[10] = { 1.0f, 0.1f, 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f, 0.0000001f, 0.00000001f, 0.000000001f };
+ if (decimal_precision < 0)
+ return FLT_MIN;
+ return (decimal_precision >= 0 && decimal_precision < 10) ? min_steps[decimal_precision] : ImPow(10.0f, (float)-decimal_precision);
+}
+
+template<typename TYPE>
+static const char* ImAtoi(const char* src, TYPE* output)
+{
+ int negative = 0;
+ if (*src == '-') { negative = 1; src++; }
+ if (*src == '+') { src++; }
+ TYPE v = 0;
+ while (*src >= '0' && *src <= '9')
+ v = (v * 10) + (*src++ - '0');
+ *output = negative ? -v : v;
+ return src;
+}
+
+template<typename TYPE, typename SIGNEDTYPE>
+static inline TYPE RoundScalarWithFormat(const char* format, ImGuiDataType data_type, TYPE v)
+{
+ const char* fmt_start = ImParseFormatFindStart(format);
+ if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string
+ return v;
+ char v_str[64];
+ ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v);
+ const char* p = v_str;
+ while (*p == ' ')
+ p++;
+ if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
+ v = (TYPE)ImAtof(p);
+ else
+ ImAtoi(p, (SIGNEDTYPE*)&v);
+ return v;
+}
+
+//-------------------------------------------------------------------------
+// WIDGETS: Drags
+// - DragBehaviorT<>() [Internal]
+// - DragBehavior() [Internal]
+// - DragScalar()
+// - DragScalarN()
+// - DragFloat()
+// - DragFloat2()
+// - DragFloat3()
+// - DragFloat4()
+// - DragFloatRange2()
+// - DragInt()
+// - DragInt2()
+// - DragInt3()
+// - DragInt4()
+// - DragIntRange2()
+//-------------------------------------------------------------------------
+
+// This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls)
+template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
+static bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, float power)
+{
+ ImGuiContext& g = *GImGui;
+
+ // Default tweak speed
+ bool has_min_max = (v_min != v_max) && (v_max - v_max < FLT_MAX);
+ if (v_speed == 0.0f && has_min_max)
+ v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio);
+
+ // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings
+ float adjust_delta = 0.0f;
+ if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && g.IO.MouseDragMaxDistanceSqr[0] > 1.0f*1.0f)
+ {
+ adjust_delta = g.IO.MouseDelta.x;
+ if (g.IO.KeyAlt)
+ adjust_delta *= 1.0f/100.0f;
+ if (g.IO.KeyShift)
+ adjust_delta *= 10.0f;
+ }
+ else if (g.ActiveIdSource == ImGuiInputSource_Nav)
+ {
+ int decimal_precision = (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) ? ImParseFormatPrecision(format, 3) : 0;
+ adjust_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard|ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 1.0f/10.0f, 10.0f).x;
+ v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision));
+ }
+ adjust_delta *= v_speed;
+
+ // Clear current value on activation
+ // Avoid altering values and clamping when we are _already_ past the limits and heading in the same direction, so e.g. if range is 0..255, current value is 300 and we are pushing to the right side, keep the 300.
+ bool is_just_activated = g.ActiveIdIsJustActivated;
+ bool is_already_past_limits_and_pushing_outward = has_min_max && ((*v >= v_max && adjust_delta > 0.0f) || (*v <= v_min && adjust_delta < 0.0f));
+ if (is_just_activated || is_already_past_limits_and_pushing_outward)
+ {
+ g.DragCurrentAccum = 0.0f;
+ g.DragCurrentAccumDirty = false;
+ }
+ else if (adjust_delta != 0.0f)
+ {
+ g.DragCurrentAccum += adjust_delta;
+ g.DragCurrentAccumDirty = true;
+ }
+
+ if (!g.DragCurrentAccumDirty)
+ return false;
+
+ TYPE v_cur = *v;
+ FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f;
+
+ const bool is_power = (power != 1.0f && (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) && has_min_max);
+ if (is_power)
+ {
+ // Offset + round to user desired precision, with a curve on the v_min..v_max range to get more precision on one side of the range
+ FLOATTYPE v_old_norm_curved = ImPow((FLOATTYPE)(v_cur - v_min) / (FLOATTYPE)(v_max - v_min), (FLOATTYPE)1.0f / power);
+ FLOATTYPE v_new_norm_curved = v_old_norm_curved + (g.DragCurrentAccum / (v_max - v_min));
+ v_cur = v_min + (TYPE)ImPow(ImSaturate((float)v_new_norm_curved), power) * (v_max - v_min);
+ v_old_ref_for_accum_remainder = v_old_norm_curved;
+ }
+ else
+ {
+ v_cur += (TYPE)g.DragCurrentAccum;
+ }
+
+ // Round to user desired precision based on format string
+ v_cur = RoundScalarWithFormat<TYPE, SIGNEDTYPE>(format, data_type, v_cur);
+
+ // Preserve remainder after rounding has been applied. This also allow slow tweaking of values.
+ g.DragCurrentAccumDirty = false;
+ if (is_power)
+ {
+ FLOATTYPE v_cur_norm_curved = ImPow((FLOATTYPE)(v_cur - v_min) / (FLOATTYPE)(v_max - v_min), (FLOATTYPE)1.0f / power);
+ g.DragCurrentAccum -= (float)(v_cur_norm_curved - v_old_ref_for_accum_remainder);
+ }
+ else
+ {
+ g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v);
+ }
+
+ // Lose zero sign for float/double
+ if (v_cur == (TYPE)-0)
+ v_cur = (TYPE)0;
+
+ // Clamp values (handle overflow/wrap-around)
+ if (*v != v_cur && has_min_max)
+ {
+ if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f))
+ v_cur = v_min;
+ if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f))
+ v_cur = v_max;
+ }
+
+ // Apply result
+ if (*v == v_cur)
+ return false;
+ *v = v_cur;
+ return true;
+}
+
+bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* v, float v_speed, const void* v_min, const void* v_max, const char* format, float power)
+{
+ ImGuiContext& g = *GImGui;
+ if (g.ActiveId == id)
+ {
+ if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0])
+ ClearActiveID();
+ else if (g.ActiveIdSource == ImGuiInputSource_Nav && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
+ ClearActiveID();
+ }
+ if (g.ActiveId != id)
+ return false;
+
+ switch (data_type)
+ {
+ case ImGuiDataType_S32: return DragBehaviorT<ImS32, ImS32, float >(data_type, (ImS32*)v, v_speed, v_min ? *(const ImS32* )v_min : IM_S32_MIN, v_max ? *(const ImS32* )v_max : IM_S32_MAX, format, power);
+ case ImGuiDataType_U32: return DragBehaviorT<ImU32, ImS32, float >(data_type, (ImU32*)v, v_speed, v_min ? *(const ImU32* )v_min : IM_U32_MIN, v_max ? *(const ImU32* )v_max : IM_U32_MAX, format, power);
+ case ImGuiDataType_S64: return DragBehaviorT<ImS64, ImS64, double>(data_type, (ImS64*)v, v_speed, v_min ? *(const ImS64* )v_min : IM_S64_MIN, v_max ? *(const ImS64* )v_max : IM_S64_MAX, format, power);
+ case ImGuiDataType_U64: return DragBehaviorT<ImU64, ImS64, double>(data_type, (ImU64*)v, v_speed, v_min ? *(const ImU64* )v_min : IM_U64_MIN, v_max ? *(const ImU64* )v_max : IM_U64_MAX, format, power);
+ case ImGuiDataType_Float: return DragBehaviorT<float, float, float >(data_type, (float*)v, v_speed, v_min ? *(const float* )v_min : -FLT_MAX, v_max ? *(const float* )v_max : FLT_MAX, format, power);
+ case ImGuiDataType_Double: return DragBehaviorT<double,double,double>(data_type, (double*)v, v_speed, v_min ? *(const double*)v_min : -DBL_MAX, v_max ? *(const double*)v_max : DBL_MAX, format, power);
+ case ImGuiDataType_COUNT: break;
+ }
+ IM_ASSERT(0);
+ return false;
+}
+
+bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* v, float v_speed, const void* v_min, const void* v_max, const char* format, float power)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ if (power != 1.0f)
+ IM_ASSERT(v_min != NULL && v_max != NULL); // When using a power curve the drag needs to have known bounds
+
+ ImGuiContext& g = *GImGui;
+ const ImGuiStyle& style = g.Style;
+ const ImGuiID id = window->GetID(label);
+ const float w = CalcItemWidth();
+
+ const ImVec2 label_size = CalcTextSize(label, NULL, true);
+ const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
+ const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
+ const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
+
+ // NB- we don't call ItemSize() yet because we may turn into a text edit box below
+ if (!ItemAdd(total_bb, id, &frame_bb))
+ {
+ ItemSize(total_bb, style.FramePadding.y);
+ return false;
+ }
+ const bool hovered = ItemHoverable(frame_bb, id);
+
+ // Default format string when passing NULL
+ // Patch old "%.0f" format string to use "%d", read function comments for more details.
+ IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
+ if (format == NULL)
+ format = GDataTypeInfo[data_type].PrintFmt;
+ else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0)
+ format = PatchFormatStringFloatToInt(format);
+
+ // Tabbing or CTRL-clicking on Drag turns it into an input box
+ bool start_text_input = false;
+ const bool tab_focus_requested = FocusableItemRegister(window, id);
+ if (tab_focus_requested || (hovered && (g.IO.MouseClicked[0] || g.IO.MouseDoubleClicked[0])) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id))
+ {
+ SetActiveID(id, window);
+ SetFocusID(id, window);
+ FocusWindow(window);
+ g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
+ if (tab_focus_requested || g.IO.KeyCtrl || g.IO.MouseDoubleClicked[0] || g.NavInputId == id)
+ {
+ start_text_input = true;
+ g.ScalarAsInputTextId = 0;
+ }
+ }
+ if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id))
+ return InputScalarAsWidgetReplacement(frame_bb, id, label, data_type, v, format);
+
+ // Actual drag behavior
+ ItemSize(total_bb, style.FramePadding.y);
+ const bool value_changed = DragBehavior(id, data_type, v, v_speed, v_min, v_max, format, power);
+ if (value_changed)
+ MarkItemEdited(id);
+
+ // Draw frame
+ const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
+ RenderNavHighlight(frame_bb, id);
+ RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding);
+
+ // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
+ char value_buf[64];
+ const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format);
+ RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
+
+ if (label_size.x > 0.0f)
+ RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
+
+ return value_changed;
+}
+
+bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* v, int components, float v_speed, const void* v_min, const void* v_max, const char* format, float power)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ ImGuiContext& g = *GImGui;
+ bool value_changed = false;
+ BeginGroup();
+ PushID(label);
+ PushMultiItemsWidths(components);
+ size_t type_size = GDataTypeInfo[data_type].Size;
+ for (int i = 0; i < components; i++)
+ {
+ PushID(i);
+ value_changed |= DragScalar("##v", data_type, v, v_speed, v_min, v_max, format, power);
+ SameLine(0, g.Style.ItemInnerSpacing.x);
+ PopID();
+ PopItemWidth();
+ v = (void*)((char*)v + type_size);
+ }
+ PopID();
+
+ TextUnformatted(label, FindRenderedTextEnd(label));
+ EndGroup();
+ return value_changed;
+}
+
+bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, float power)
+{
+ return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, power);
+}
+
+bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, float power)
+{
+ return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, power);
+}
+
+bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, float power)
+{
+ return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, power);
+}
+
+bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, float power)
+{
+ return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, power);
+}
+
+bool ImGui::DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed, float v_min, float v_max, const char* format, const char* format_max, float power)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ ImGuiContext& g = *GImGui;
+ PushID(label);
+ BeginGroup();
+ PushMultiItemsWidths(2);
+
+ bool value_changed = DragFloat("##min", v_current_min, v_speed, (v_min >= v_max) ? -FLT_MAX : v_min, (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max), format, power);
+ PopItemWidth();
+ SameLine(0, g.Style.ItemInnerSpacing.x);
+ value_changed |= DragFloat("##max", v_current_max, v_speed, (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min), (v_min >= v_max) ? FLT_MAX : v_max, format_max ? format_max : format, power);
+ PopItemWidth();
+ SameLine(0, g.Style.ItemInnerSpacing.x);
+
+ TextUnformatted(label, FindRenderedTextEnd(label));
+ EndGroup();
+ PopID();
+ return value_changed;
+}
+
+// NB: v_speed is float to allow adjusting the drag speed with more precision
+bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* format)
+{
+ return DragScalar(label, ImGuiDataType_S32, v, v_speed, &v_min, &v_max, format);
+}
+
+bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* format)
+{
+ return DragScalarN(label, ImGuiDataType_S32, v, 2, v_speed, &v_min, &v_max, format);
+}
+
+bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* format)
+{
+ return DragScalarN(label, ImGuiDataType_S32, v, 3, v_speed, &v_min, &v_max, format);
+}
+
+bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* format)
+{
+ return DragScalarN(label, ImGuiDataType_S32, v, 4, v_speed, &v_min, &v_max, format);
+}
+
+bool ImGui::DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed, int v_min, int v_max, const char* format, const char* format_max)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ ImGuiContext& g = *GImGui;
+ PushID(label);
+ BeginGroup();
+ PushMultiItemsWidths(2);
+
+ bool value_changed = DragInt("##min", v_current_min, v_speed, (v_min >= v_max) ? INT_MIN : v_min, (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max), format);
+ PopItemWidth();
+ SameLine(0, g.Style.ItemInnerSpacing.x);
+ value_changed |= DragInt("##max", v_current_max, v_speed, (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min), (v_min >= v_max) ? INT_MAX : v_max, format_max ? format_max : format);
+ PopItemWidth();
+ SameLine(0, g.Style.ItemInnerSpacing.x);
+
+ TextUnformatted(label, FindRenderedTextEnd(label));
+ EndGroup();
+ PopID();
+
+ return value_changed;
+}
+
+//-------------------------------------------------------------------------
+// WIDGETS: Sliders
+// - SliderBehaviorT<>() [Internal]
+// - SliderBehavior() [Internal]
+// - SliderScalar()
+// - SliderScalarN()
+// - SliderFloat()
+// - SliderFloat2()
+// - SliderFloat3()
+// - SliderFloat4()
+// - SliderAngle()
+// - SliderInt()
+// - SliderInt2()
+// - SliderInt3()
+// - SliderInt4()
+// - VSliderScalar()
+// - VSliderFloat()
+// - VSliderInt()
+//-------------------------------------------------------------------------
+
+template<typename TYPE, typename FLOATTYPE>
+static inline float SliderBehaviorCalcRatioFromValue(ImGuiDataType data_type, TYPE v, TYPE v_min, TYPE v_max, float power, float linear_zero_pos)
+{
+ if (v_min == v_max)
+ return 0.0f;
+
+ const bool is_power = (power != 1.0f) && (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double);
+ const TYPE v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min);
+ if (is_power)
+ {
+ if (v_clamped < 0.0f)
+ {
+ const float f = 1.0f - (float)((v_clamped - v_min) / (ImMin((TYPE)0, v_max) - v_min));
+ return (1.0f - ImPow(f, 1.0f/power)) * linear_zero_pos;
+ }
+ else
+ {
+ const float f = (float)((v_clamped - ImMax((TYPE)0, v_min)) / (v_max - ImMax((TYPE)0, v_min)));
+ return linear_zero_pos + ImPow(f, 1.0f/power) * (1.0f - linear_zero_pos);
+ }
+ }
+
+ // Linear slider
+ return (float)((FLOATTYPE)(v_clamped - v_min) / (FLOATTYPE)(v_max - v_min));
+}
+
+// FIXME: Move some of the code into SliderBehavior(). Current responsability is larger than what the equivalent DragBehaviorT<> does, we also do some rendering, etc.
+template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
+static bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, TYPE* v, const TYPE v_min, const TYPE v_max, const char* format, float power, ImGuiSliderFlags flags, ImRect* out_grab_bb)
+{
+ ImGuiContext& g = *GImGui;
+ const ImGuiStyle& style = g.Style;
+
+ const bool is_horizontal = (flags & ImGuiSliderFlags_Vertical) == 0;
+ const bool is_decimal = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
+ const bool is_power = (power != 1.0f) && is_decimal;
+
+ const float grab_padding = 2.0f;
+ const float slider_sz = is_horizontal ? (bb.GetWidth() - grab_padding * 2.0f) : (bb.GetHeight() - grab_padding * 2.0f);
+ float grab_sz = style.GrabMinSize;
+ SIGNEDTYPE v_range = (v_min < v_max ? v_max - v_min : v_min - v_max);
+ if (!is_decimal && v_range >= 0) // v_range < 0 may happen on integer overflows
+ grab_sz = ImMax((float)(slider_sz / (v_range + 1)), style.GrabMinSize); // For integer sliders: if possible have the grab size represent 1 unit
+ grab_sz = ImMin(grab_sz, slider_sz);
+ const float slider_usable_sz = slider_sz - grab_sz;
+ const float slider_usable_pos_min = (is_horizontal ? bb.Min.x : bb.Min.y) + grab_padding + grab_sz*0.5f;
+ const float slider_usable_pos_max = (is_horizontal ? bb.Max.x : bb.Max.y) - grab_padding - grab_sz*0.5f;
+
+ // For power curve sliders that cross over sign boundary we want the curve to be symmetric around 0.0f
+ float linear_zero_pos; // 0.0->1.0f
+ if (is_power && v_min * v_max < 0.0f)
+ {
+ // Different sign
+ const FLOATTYPE linear_dist_min_to_0 = ImPow(v_min >= 0 ? (FLOATTYPE)v_min : -(FLOATTYPE)v_min, (FLOATTYPE)1.0f/power);
+ const FLOATTYPE linear_dist_max_to_0 = ImPow(v_max >= 0 ? (FLOATTYPE)v_max : -(FLOATTYPE)v_max, (FLOATTYPE)1.0f/power);
+ linear_zero_pos = (float)(linear_dist_min_to_0 / (linear_dist_min_to_0 + linear_dist_max_to_0));
+ }
+ else
+ {
+ // Same sign
+ linear_zero_pos = v_min < 0.0f ? 1.0f : 0.0f;
+ }
+
+ // Process interacting with the slider
+ bool value_changed = false;
+ if (g.ActiveId == id)
+ {
+ bool set_new_value = false;
+ float clicked_t = 0.0f;
+ if (g.ActiveIdSource == ImGuiInputSource_Mouse)
+ {
+ if (!g.IO.MouseDown[0])
+ {
+ ClearActiveID();
+ }
+ else
+ {
+ const float mouse_abs_pos = is_horizontal ? g.IO.MousePos.x : g.IO.MousePos.y;
+ clicked_t = (slider_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - slider_usable_pos_min) / slider_usable_sz, 0.0f, 1.0f) : 0.0f;
+ if (!is_horizontal)
+ clicked_t = 1.0f - clicked_t;
+ set_new_value = true;
+ }
+ }
+ else if (g.ActiveIdSource == ImGuiInputSource_Nav)
+ {
+ const ImVec2 delta2 = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 0.0f, 0.0f);
+ float delta = is_horizontal ? delta2.x : -delta2.y;
+ if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
+ {
+ ClearActiveID();
+ }
+ else if (delta != 0.0f)
+ {
+ clicked_t = SliderBehaviorCalcRatioFromValue<TYPE,FLOATTYPE>(data_type, *v, v_min, v_max, power, linear_zero_pos);
+ const int decimal_precision = is_decimal ? ImParseFormatPrecision(format, 3) : 0;
+ if ((decimal_precision > 0) || is_power)
+ {
+ delta /= 100.0f; // Gamepad/keyboard tweak speeds in % of slider bounds
+ if (IsNavInputDown(ImGuiNavInput_TweakSlow))
+ delta /= 10.0f;
+ }
+ else
+ {
+ if ((v_range >= -100.0f && v_range <= 100.0f) || IsNavInputDown(ImGuiNavInput_TweakSlow))
+ delta = ((delta < 0.0f) ? -1.0f : +1.0f) / (float)v_range; // Gamepad/keyboard tweak speeds in integer steps
+ else
+ delta /= 100.0f;
+ }
+ if (IsNavInputDown(ImGuiNavInput_TweakFast))
+ delta *= 10.0f;
+ set_new_value = true;
+ if ((clicked_t >= 1.0f && delta > 0.0f) || (clicked_t <= 0.0f && delta < 0.0f)) // This is to avoid applying the saturation when already past the limits
+ set_new_value = false;
+ else
+ clicked_t = ImSaturate(clicked_t + delta);
+ }
+ }
+
+ if (set_new_value)
+ {
+ TYPE v_new;
+ if (is_power)
+ {
+ // Account for power curve scale on both sides of the zero
+ if (clicked_t < linear_zero_pos)
+ {
+ // Negative: rescale to the negative range before powering
+ float a = 1.0f - (clicked_t / linear_zero_pos);
+ a = ImPow(a, power);
+ v_new = ImLerp(ImMin(v_max, (TYPE)0), v_min, a);
+ }
+ else
+ {
+ // Positive: rescale to the positive range before powering
+ float a;
+ if (ImFabs(linear_zero_pos - 1.0f) > 1.e-6f)
+ a = (clicked_t - linear_zero_pos) / (1.0f - linear_zero_pos);
+ else
+ a = clicked_t;
+ a = ImPow(a, power);
+ v_new = ImLerp(ImMax(v_min, (TYPE)0), v_max, a);
+ }
+ }
+ else
+ {
+ // Linear slider
+ if (is_decimal)
+ {
+ v_new = ImLerp(v_min, v_max, clicked_t);
+ }
+ else
+ {
+ // For integer values we want the clicking position to match the grab box so we round above
+ // This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property..
+ FLOATTYPE v_new_off_f = (v_max - v_min) * clicked_t;
+ TYPE v_new_off_floor = (TYPE)(v_new_off_f);
+ TYPE v_new_off_round = (TYPE)(v_new_off_f + (FLOATTYPE)0.5);
+ if (!is_decimal && v_new_off_floor < v_new_off_round)
+ v_new = v_min + v_new_off_round;
+ else
+ v_new = v_min + v_new_off_floor;
+ }
+ }
+
+ // Round to user desired precision based on format string
+ v_new = RoundScalarWithFormat<TYPE,SIGNEDTYPE>(format, data_type, v_new);
+
+ // Apply result
+ if (*v != v_new)
+ {
+ *v = v_new;
+ value_changed = true;
+ }
+ }
+ }
+
+ // Output grab position so it can be displayed by the caller
+ float grab_t = SliderBehaviorCalcRatioFromValue<TYPE,FLOATTYPE>(data_type, *v, v_min, v_max, power, linear_zero_pos);
+ if (!is_horizontal)
+ grab_t = 1.0f - grab_t;
+ const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t);
+ if (is_horizontal)
+ *out_grab_bb = ImRect(grab_pos - grab_sz*0.5f, bb.Min.y + grab_padding, grab_pos + grab_sz*0.5f, bb.Max.y - grab_padding);
+ else
+ *out_grab_bb = ImRect(bb.Min.x + grab_padding, grab_pos - grab_sz*0.5f, bb.Max.x - grab_padding, grab_pos + grab_sz*0.5f);
+
+ return value_changed;
+}
+
+// For 32-bits and larger types, slider bounds are limited to half the natural type range.
+// So e.g. an integer Slider between INT_MAX-10 and INT_MAX will fail, but an integer Slider between INT_MAX/2-10 and INT_MAX/2 will be ok.
+// It would be possible to lift that limitation with some work but it doesn't seem to be worth it for sliders.
+bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power, ImGuiSliderFlags flags, ImRect* out_grab_bb)
+{
+ switch (data_type)
+ {
+ case ImGuiDataType_S32:
+ IM_ASSERT(*(const ImS32*)v_min >= IM_S32_MIN/2 && *(const ImS32*)v_max <= IM_S32_MAX/2);
+ return SliderBehaviorT<ImS32, ImS32, float >(bb, id, data_type, (ImS32*)v, *(const ImS32*)v_min, *(const ImS32*)v_max, format, power, flags, out_grab_bb);
+ case ImGuiDataType_U32:
+ IM_ASSERT(*(const ImU32*)v_min <= IM_U32_MAX/2);
+ return SliderBehaviorT<ImU32, ImS32, float >(bb, id, data_type, (ImU32*)v, *(const ImU32*)v_min, *(const ImU32*)v_max, format, power, flags, out_grab_bb);
+ case ImGuiDataType_S64:
+ IM_ASSERT(*(const ImS64*)v_min >= IM_S64_MIN/2 && *(const ImS64*)v_max <= IM_S64_MAX/2);
+ return SliderBehaviorT<ImS64, ImS64, double>(bb, id, data_type, (ImS64*)v, *(const ImS64*)v_min, *(const ImS64*)v_max, format, power, flags, out_grab_bb);
+ case ImGuiDataType_U64:
+ IM_ASSERT(*(const ImU64*)v_min <= IM_U64_MAX/2);
+ return SliderBehaviorT<ImU64, ImS64, double>(bb, id, data_type, (ImU64*)v, *(const ImU64*)v_min, *(const ImU64*)v_max, format, power, flags, out_grab_bb);
+ case ImGuiDataType_Float:
+ IM_ASSERT(*(const float*)v_min >= -FLT_MAX/2.0f && *(const float*)v_max <= FLT_MAX/2.0f);
+ return SliderBehaviorT<float, float, float >(bb, id, data_type, (float*)v, *(const float*)v_min, *(const float*)v_max, format, power, flags, out_grab_bb);
+ case ImGuiDataType_Double:
+ IM_ASSERT(*(const double*)v_min >= -DBL_MAX/2.0f && *(const double*)v_max <= DBL_MAX/2.0f);
+ return SliderBehaviorT<double,double,double>(bb, id, data_type, (double*)v, *(const double*)v_min, *(const double*)v_max, format, power, flags, out_grab_bb);
+ case ImGuiDataType_COUNT: break;
+ }
+ IM_ASSERT(0);
+ return false;
+}
+
+bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ ImGuiContext& g = *GImGui;
+ const ImGuiStyle& style = g.Style;
+ const ImGuiID id = window->GetID(label);
+ const float w = CalcItemWidth();
+
+ const ImVec2 label_size = CalcTextSize(label, NULL, true);
+ const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
+ const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
+
+ // NB- we don't call ItemSize() yet because we may turn into a text edit box below
+ if (!ItemAdd(total_bb, id, &frame_bb))
+ {
+ ItemSize(total_bb, style.FramePadding.y);
+ return false;
+ }
+
+ // Default format string when passing NULL
+ // Patch old "%.0f" format string to use "%d", read function comments for more details.
+ IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
+ if (format == NULL)
+ format = GDataTypeInfo[data_type].PrintFmt;
+ else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0)
+ format = PatchFormatStringFloatToInt(format);
+
+ // Tabbing or CTRL-clicking on Slider turns it into an input box
+ bool start_text_input = false;
+ const bool tab_focus_requested = FocusableItemRegister(window, id);
+ const bool hovered = ItemHoverable(frame_bb, id);
+ if (tab_focus_requested || (hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id))
+ {
+ SetActiveID(id, window);
+ SetFocusID(id, window);
+ FocusWindow(window);
+ g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
+ if (tab_focus_requested || g.IO.KeyCtrl || g.NavInputId == id)
+ {
+ start_text_input = true;
+ g.ScalarAsInputTextId = 0;
+ }
+ }
+ if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id))
+ return InputScalarAsWidgetReplacement(frame_bb, id, label, data_type, v, format);
+
+ ItemSize(total_bb, style.FramePadding.y);
+
+ // Draw frame
+ const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
+ RenderNavHighlight(frame_bb, id);
+ RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
+
+ // Slider behavior
+ ImRect grab_bb;
+ const bool value_changed = SliderBehavior(frame_bb, id, data_type, v, v_min, v_max, format, power, ImGuiSliderFlags_None, &grab_bb);
+ if (value_changed)
+ MarkItemEdited(id);
+
+ // Render grab
+ window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
+
+ // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
+ char value_buf[64];
+ const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format);
+ RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.5f));
+
+ if (label_size.x > 0.0f)
+ RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
+
+ return value_changed;
+}
+
+// Add multiple sliders on 1 line for compact edition of multiple components
+bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* v_min, const void* v_max, const char* format, float power)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ ImGuiContext& g = *GImGui;
+ bool value_changed = false;
+ BeginGroup();
+ PushID(label);
+ PushMultiItemsWidths(components);
+ size_t type_size = GDataTypeInfo[data_type].Size;
+ for (int i = 0; i < components; i++)
+ {
+ PushID(i);
+ value_changed |= SliderScalar("##v", data_type, v, v_min, v_max, format, power);
+ SameLine(0, g.Style.ItemInnerSpacing.x);
+ PopID();
+ PopItemWidth();
+ v = (void*)((char*)v + type_size);
+ }
+ PopID();
+
+ TextUnformatted(label, FindRenderedTextEnd(label));
+ EndGroup();
+ return value_changed;
+}
+
+bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, float power)
+{
+ return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, power);
+}
+
+bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, float power)
+{
+ return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, power);
+}
+
+bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, float power)
+{
+ return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, power);
+}
+
+bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, float power)
+{
+ return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, power);
+}
+
+bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max)
+{
+ float v_deg = (*v_rad) * 360.0f / (2*IM_PI);
+ bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, "%.0f deg", 1.0f);
+ *v_rad = v_deg * (2*IM_PI) / 360.0f;
+ return value_changed;
+}
+
+bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* format)
+{
+ return SliderScalar(label, ImGuiDataType_S32, v, &v_min, &v_max, format);
+}
+
+bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format)
+{
+ return SliderScalarN(label, ImGuiDataType_S32, v, 2, &v_min, &v_max, format);
+}
+
+bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format)
+{
+ return SliderScalarN(label, ImGuiDataType_S32, v, 3, &v_min, &v_max, format);
+}
+
+bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format)
+{
+ return SliderScalarN(label, ImGuiDataType_S32, v, 4, &v_min, &v_max, format);
+}
+
+bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ ImGuiContext& g = *GImGui;
+ const ImGuiStyle& style = g.Style;
+ const ImGuiID id = window->GetID(label);
+
+ const ImVec2 label_size = CalcTextSize(label, NULL, true);
+ const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
+ const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
+
+ ItemSize(bb, style.FramePadding.y);
+ if (!ItemAdd(frame_bb, id))
+ return false;
+
+ // Default format string when passing NULL
+ // Patch old "%.0f" format string to use "%d", read function comments for more details.
+ IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
+ if (format == NULL)
+ format = GDataTypeInfo[data_type].PrintFmt;
+ else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0)
+ format = PatchFormatStringFloatToInt(format);
+
+ const bool hovered = ItemHoverable(frame_bb, id);
+ if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavInputId == id)
+ {
+ SetActiveID(id, window);
+ SetFocusID(id, window);
+ FocusWindow(window);
+ g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
+ }
+
+ // Draw frame
+ const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
+ RenderNavHighlight(frame_bb, id);
+ RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
+
+ // Slider behavior
+ ImRect grab_bb;
+ const bool value_changed = SliderBehavior(frame_bb, id, data_type, v, v_min, v_max, format, power, ImGuiSliderFlags_Vertical, &grab_bb);
+ if (value_changed)
+ MarkItemEdited(id);
+
+ // Render grab
+ window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
+
+ // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
+ // For the vertical slider we allow centered text to overlap the frame padding
+ char value_buf[64];
+ const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format);
+ RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.0f));
+ if (label_size.x > 0.0f)
+ RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
+
+ return value_changed;
+}
+
+bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format, float power)
+{
+ return VSliderScalar(label, size, ImGuiDataType_Float, v, &v_min, &v_max, format, power);
+}
+
+bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format)
+{
+ return VSliderScalar(label, size, ImGuiDataType_S32, v, &v_min, &v_max, format);
+}
+
+//-------------------------------------------------------------------------
+// WIDGETS: Inputs (_excepted InputText_)
+// - ImParseFormatFindStart()
+// - ImParseFormatFindEnd()
+// - ImParseFormatTrimDecorations()
+// - ImParseFormatPrecision()
+// - InputScalarAsWidgetReplacement() [Internal]
+// - InputScalar()
+// - InputScalarN()
+// - InputFloat()
+// - InputFloat2()
+// - InputFloat3()
+// - InputFloat4()
+// - InputInt()
+// - InputInt2()
+// - InputInt3()
+// - InputInt4()
+// - InputDouble()
+//-------------------------------------------------------------------------
+
+// We don't use strchr() because our strings are usually very short and often start with '%'
+const char* ImParseFormatFindStart(const char* fmt)
+{
+ while (char c = fmt[0])
+ {
+ if (c == '%' && fmt[1] != '%')
+ return fmt;
+ else if (c == '%')
+ fmt++;
+ fmt++;
+ }
+ return fmt;
+}
+
+const char* ImParseFormatFindEnd(const char* fmt)
+{
+ // Printf/scanf types modifiers: I/L/h/j/l/t/w/z. Other uppercase letters qualify as types aka end of the format.
+ if (fmt[0] != '%')
+ return fmt;
+ const unsigned int ignored_uppercase_mask = (1 << ('I'-'A')) | (1 << ('L'-'A'));
+ const unsigned int ignored_lowercase_mask = (1 << ('h'-'a')) | (1 << ('j'-'a')) | (1 << ('l'-'a')) | (1 << ('t'-'a')) | (1 << ('w'-'a')) | (1 << ('z'-'a'));
+ for (char c; (c = *fmt) != 0; fmt++)
+ {
+ if (c >= 'A' && c <= 'Z' && ((1 << (c - 'A')) & ignored_uppercase_mask) == 0)
+ return fmt + 1;
+ if (c >= 'a' && c <= 'z' && ((1 << (c - 'a')) & ignored_lowercase_mask) == 0)
+ return fmt + 1;
+ }
+ return fmt;
+}
+
+// Extract the format out of a format string with leading or trailing decorations
+// fmt = "blah blah" -> return fmt
+// fmt = "%.3f" -> return fmt
+// fmt = "hello %.3f" -> return fmt + 6
+// fmt = "%.3f hello" -> return buf written with "%.3f"
+const char* ImParseFormatTrimDecorations(const char* fmt, char* buf, int buf_size)
+{
+ const char* fmt_start = ImParseFormatFindStart(fmt);
+ if (fmt_start[0] != '%')
+ return fmt;
+ const char* fmt_end = ImParseFormatFindEnd(fmt_start);
+ if (fmt_end[0] == 0) // If we only have leading decoration, we don't need to copy the data.
+ return fmt_start;
+ ImStrncpy(buf, fmt_start, ImMin((int)(fmt_end + 1 - fmt_start), buf_size));
+ return buf;
+}
+
+// Parse display precision back from the display format string
+// FIXME: This is still used by some navigation code path to infer a minimum tweak step, but we should aim to rework widgets so it isn't needed.
+int ImParseFormatPrecision(const char* fmt, int default_precision)
+{
+ fmt = ImParseFormatFindStart(fmt);
+ if (fmt[0] != '%')
+ return default_precision;
+ fmt++;
+ while (*fmt >= '0' && *fmt <= '9')
+ fmt++;
+ int precision = INT_MAX;
+ if (*fmt == '.')
+ {
+ fmt = ImAtoi<int>(fmt + 1, &precision);
+ if (precision < 0 || precision > 99)
+ precision = default_precision;
+ }
+ if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation
+ precision = -1;
+ if ((*fmt == 'g' || *fmt == 'G') && precision == INT_MAX)
+ precision = -1;
+ return (precision == INT_MAX) ? default_precision : precision;
+}
+
+// Create text input in place of a slider (when CTRL+Clicking on slider)
+// FIXME: Logic is messy and confusing.
+bool ImGui::InputScalarAsWidgetReplacement(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* data_ptr, const char* format)
+{
+ ImGuiContext& g = *GImGui;
+ ImGuiWindow* window = GetCurrentWindow();
+
+ // Our replacement widget will override the focus ID (registered previously to allow for a TAB focus to happen)
+ // On the first frame, g.ScalarAsInputTextId == 0, then on subsequent frames it becomes == id
+ SetActiveID(g.ScalarAsInputTextId, window);
+ g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
+ SetHoveredID(0);
+ FocusableItemUnregister(window);
+
+ char fmt_buf[32];
+ char data_buf[32];
+ format = ImParseFormatTrimDecorations(format, fmt_buf, IM_ARRAYSIZE(fmt_buf));
+ DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, data_ptr, format);
+ ImStrTrimBlanks(data_buf);
+ ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | ((data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) ? ImGuiInputTextFlags_CharsScientific : ImGuiInputTextFlags_CharsDecimal);
+ bool value_changed = InputTextEx(label, data_buf, IM_ARRAYSIZE(data_buf), bb.GetSize(), flags);
+ if (g.ScalarAsInputTextId == 0) // First frame we started displaying the InputText widget
+ {
+ IM_ASSERT(g.ActiveId == id); // InputText ID expected to match the Slider ID
+ g.ScalarAsInputTextId = g.ActiveId;
+ SetHoveredID(id);
+ }
+ if (value_changed)
+ return DataTypeApplyOpFromText(data_buf, g.InputTextState.InitialText.Data, data_type, data_ptr, NULL);
+ return false;
+}
+
+// NB: format here must be a simple "%xx" format string with no prefix/suffix (unlike the Drag/Slider functions "format" argument)
+bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* data_ptr, const void* step, const void* step_fast, const char* format, ImGuiInputTextFlags extra_flags)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ ImGuiContext& g = *GImGui;
+ const ImGuiStyle& style = g.Style;
+
+ IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
+ if (format == NULL)
+ format = GDataTypeInfo[data_type].PrintFmt;
+
+ char buf[64];
+ DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, data_ptr, format);
+
+ bool value_changed = false;
+ if ((extra_flags & (ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsScientific)) == 0)
+ extra_flags |= ImGuiInputTextFlags_CharsDecimal;
+ extra_flags |= ImGuiInputTextFlags_AutoSelectAll;
+
+ if (step != NULL)
+ {
+ const float button_size = GetFrameHeight();
+
+ BeginGroup(); // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive()
+ PushID(label);
+ PushItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
+ if (InputText("", buf, IM_ARRAYSIZE(buf), extra_flags)) // PushId(label) + "" gives us the expected ID from outside point of view
+ value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialText.Data, data_type, data_ptr, format);
+ PopItemWidth();
+
+ // Step buttons
+ SameLine(0, style.ItemInnerSpacing.x);
+ if (ButtonEx("-", ImVec2(button_size, button_size), ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups))
+ {
+ DataTypeApplyOp(data_type, '-', data_ptr, data_ptr, g.IO.KeyCtrl && step_fast ? step_fast : step);
+ value_changed = true;
+ }
+ SameLine(0, style.ItemInnerSpacing.x);
+ if (ButtonEx("+", ImVec2(button_size, button_size), ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups))
+ {
+ DataTypeApplyOp(data_type, '+', data_ptr, data_ptr, g.IO.KeyCtrl && step_fast ? step_fast : step);
+ value_changed = true;
+ }
+ SameLine(0, style.ItemInnerSpacing.x);
+ TextUnformatted(label, FindRenderedTextEnd(label));
+
+ PopID();
+ EndGroup();
+ }
+ else
+ {
+ if (InputText(label, buf, IM_ARRAYSIZE(buf), extra_flags))
+ value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialText.Data, data_type, data_ptr, format);
+ }
+
+ return value_changed;
+}
+
+bool ImGui::InputScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* step, const void* step_fast, const char* format, ImGuiInputTextFlags extra_flags)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ ImGuiContext& g = *GImGui;
+ bool value_changed = false;
+ BeginGroup();
+ PushID(label);
+ PushMultiItemsWidths(components);
+ size_t type_size = GDataTypeInfo[data_type].Size;
+ for (int i = 0; i < components; i++)
+ {
+ PushID(i);
+ value_changed |= InputScalar("##v", data_type, v, step, step_fast, format, extra_flags);
+ SameLine(0, g.Style.ItemInnerSpacing.x);
+ PopID();
+ PopItemWidth();
+ v = (void*)((char*)v + type_size);
+ }
+ PopID();
+
+ TextUnformatted(label, FindRenderedTextEnd(label));
+ EndGroup();
+ return value_changed;
+}
+
+bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, const char* format, ImGuiInputTextFlags extra_flags)
+{
+ extra_flags |= ImGuiInputTextFlags_CharsScientific;
+ return InputScalar(label, ImGuiDataType_Float, (void*)v, (void*)(step>0.0f ? &step : NULL), (void*)(step_fast>0.0f ? &step_fast : NULL), format, extra_flags);
+}
+
+bool ImGui::InputFloat2(const char* label, float v[2], const char* format, ImGuiInputTextFlags extra_flags)
+{
+ return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, extra_flags);
+}
+
+bool ImGui::InputFloat3(const char* label, float v[3], const char* format, ImGuiInputTextFlags extra_flags)
+{
+ return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, extra_flags);
+}
+
+bool ImGui::InputFloat4(const char* label, float v[4], const char* format, ImGuiInputTextFlags extra_flags)
+{
+ return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, extra_flags);
+}
+
+// Prefer using "const char* format" directly, which is more flexible and consistent with other API.
+#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
+bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, int decimal_precision, ImGuiInputTextFlags extra_flags)
+{
+ char format[16] = "%f";
+ if (decimal_precision >= 0)
+ ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);
+ return InputFloat(label, v, step, step_fast, format, extra_flags);
+}
+
+bool ImGui::InputFloat2(const char* label, float v[2], int decimal_precision, ImGuiInputTextFlags extra_flags)
+{
+ char format[16] = "%f";
+ if (decimal_precision >= 0)
+ ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);
+ return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, extra_flags);
+}
+
+bool ImGui::InputFloat3(const char* label, float v[3], int decimal_precision, ImGuiInputTextFlags extra_flags)
+{
+ char format[16] = "%f";
+ if (decimal_precision >= 0)
+ ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);
+ return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, extra_flags);
+}
+
+bool ImGui::InputFloat4(const char* label, float v[4], int decimal_precision, ImGuiInputTextFlags extra_flags)
+{
+ char format[16] = "%f";
+ if (decimal_precision >= 0)
+ ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);
+ return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, extra_flags);
+}
+#endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS
+
+bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags extra_flags)
+{
+ // Hexadecimal input provided as a convenience but the flag name is awkward. Typically you'd use InputText() to parse your own data, if you want to handle prefixes.
+ const char* format = (extra_flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d";
+ return InputScalar(label, ImGuiDataType_S32, (void*)v, (void*)(step>0 ? &step : NULL), (void*)(step_fast>0 ? &step_fast : NULL), format, extra_flags);
+}
+
+bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags extra_flags)
+{
+ return InputScalarN(label, ImGuiDataType_S32, v, 2, NULL, NULL, "%d", extra_flags);
+}
+
+bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags extra_flags)
+{
+ return InputScalarN(label, ImGuiDataType_S32, v, 3, NULL, NULL, "%d", extra_flags);
+}
+
+bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags extra_flags)
+{
+ return InputScalarN(label, ImGuiDataType_S32, v, 4, NULL, NULL, "%d", extra_flags);
+}
+
+bool ImGui::InputDouble(const char* label, double* v, double step, double step_fast, const char* format, ImGuiInputTextFlags extra_flags)
+{
+ extra_flags |= ImGuiInputTextFlags_CharsScientific;
+ return InputScalar(label, ImGuiDataType_Double, (void*)v, (void*)(step>0.0 ? &step : NULL), (void*)(step_fast>0.0 ? &step_fast : NULL), format, extra_flags);
+}
+
+//-------------------------------------------------------------------------
+// WIDGETS: InputText
+// - InputText()
+// - InputTextMultiline()
+// - InputTextEx() [Internal]
+//-------------------------------------------------------------------------
+
+bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
+{
+ IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
+ return InputTextEx(label, buf, (int)buf_size, ImVec2(0,0), flags, callback, user_data);
+}
+
+bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
+{
+ return InputTextEx(label, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data);
+}
+
+static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end)
+{
+ int line_count = 0;
+ const char* s = text_begin;
+ while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding
+ if (c == '\n')
+ line_count++;
+ s--;
+ if (s[0] != '\n' && s[0] != '\r')
+ line_count++;
+ *out_text_end = s;
+ return line_count;
+}
+
+static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line)
+{
+ ImFont* font = GImGui->Font;
+ const float line_height = GImGui->FontSize;
+ const float scale = line_height / font->FontSize;
+
+ ImVec2 text_size = ImVec2(0,0);
+ float line_width = 0.0f;
+
+ const ImWchar* s = text_begin;
+ while (s < text_end)
+ {
+ unsigned int c = (unsigned int)(*s++);
+ if (c == '\n')
+ {
+ text_size.x = ImMax(text_size.x, line_width);
+ text_size.y += line_height;
+ line_width = 0.0f;
+ if (stop_on_new_line)
+ break;
+ continue;
+ }
+ if (c == '\r')
+ continue;
+
+ const float char_width = font->GetCharAdvance((unsigned short)c) * scale;
+ line_width += char_width;
+ }
+
+ if (text_size.x < line_width)
+ text_size.x = line_width;
+
+ if (out_offset)
+ *out_offset = ImVec2(line_width, text_size.y + line_height); // offset allow for the possibility of sitting after a trailing \n
+
+ if (line_width > 0 || text_size.y == 0.0f) // whereas size.y will ignore the trailing \n
+ text_size.y += line_height;
+
+ if (remaining)
+ *remaining = s;
+
+ return text_size;
+}
+
+// Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, wchar characters. InputText converts between UTF-8 and wchar)
+namespace ImGuiStb
+{
+
+static int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return obj->CurLenW; }
+static ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx) { return obj->TextW[idx]; }
+static float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { ImWchar c = obj->TextW[line_start_idx+char_idx]; if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; return GImGui->Font->GetCharAdvance(c) * (GImGui->FontSize / GImGui->Font->FontSize); }
+static int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x10000 ? 0 : key; }
+static ImWchar STB_TEXTEDIT_NEWLINE = '\n';
+static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx)
+{
+ const ImWchar* text = obj->TextW.Data;
+ const ImWchar* text_remaining = NULL;
+ const ImVec2 size = InputTextCalcTextSizeW(text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true);
+ r->x0 = 0.0f;
+ r->x1 = size.x;
+ r->baseline_y_delta = size.y;
+ r->ymin = 0.0f;
+ r->ymax = size.y;
+ r->num_chars = (int)(text_remaining - (text + line_start_idx));
+}
+
+static bool is_separator(unsigned int c) { return ImCharIsBlankW(c) || c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|'; }
+static int is_word_boundary_from_right(STB_TEXTEDIT_STRING* obj, int idx) { return idx > 0 ? (is_separator( obj->TextW[idx-1] ) && !is_separator( obj->TextW[idx] ) ) : 1; }
+static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; }
+#ifdef __APPLE__ // FIXME: Move setting to IO structure
+static int is_word_boundary_from_left(STB_TEXTEDIT_STRING* obj, int idx) { return idx > 0 ? (!is_separator( obj->TextW[idx-1] ) && is_separator( obj->TextW[idx] ) ) : 1; }
+static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; }
+#else
+static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; }
+#endif
+#define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h
+#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL
+
+static void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int pos, int n)
+{
+ ImWchar* dst = obj->TextW.Data + pos;
+
+ // We maintain our buffer length in both UTF-8 and wchar formats
+ obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n);
+ obj->CurLenW -= n;
+
+ // Offset remaining text
+ const ImWchar* src = obj->TextW.Data + pos + n;
+ while (ImWchar c = *src++)
+ *dst++ = c;
+ *dst = '\0';
+}
+
+static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int pos, const ImWchar* new_text, int new_text_len)
+{
+ const bool is_resizable = (obj->UserFlags & ImGuiInputTextFlags_CallbackResize) != 0;
+ const int text_len = obj->CurLenW;
+ IM_ASSERT(pos <= text_len);
+
+ const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len);
+ if (!is_resizable && (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufCapacityA))
+ return false;
+
+ // Grow internal buffer if needed
+ if (new_text_len + text_len + 1 > obj->TextW.Size)
+ {
+ if (!is_resizable)
+ return false;
+ IM_ASSERT(text_len < obj->TextW.Size);
+ obj->TextW.resize(text_len + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1);
+ }
+
+ ImWchar* text = obj->TextW.Data;
+ if (pos != text_len)
+ memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar));
+ memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar));
+
+ obj->CurLenW += new_text_len;
+ obj->CurLenA += new_text_len_utf8;
+ obj->TextW[obj->CurLenW] = '\0';
+
+ return true;
+}
+
+// We don't use an enum so we can build even with conflicting symbols (if another user of stb_textedit.h leak their STB_TEXTEDIT_K_* symbols)
+#define STB_TEXTEDIT_K_LEFT 0x10000 // keyboard input to move cursor left
+#define STB_TEXTEDIT_K_RIGHT 0x10001 // keyboard input to move cursor right
+#define STB_TEXTEDIT_K_UP 0x10002 // keyboard input to move cursor up
+#define STB_TEXTEDIT_K_DOWN 0x10003 // keyboard input to move cursor down
+#define STB_TEXTEDIT_K_LINESTART 0x10004 // keyboard input to move cursor to start of line
+#define STB_TEXTEDIT_K_LINEEND 0x10005 // keyboard input to move cursor to end of line
+#define STB_TEXTEDIT_K_TEXTSTART 0x10006 // keyboard input to move cursor to start of text
+#define STB_TEXTEDIT_K_TEXTEND 0x10007 // keyboard input to move cursor to end of text
+#define STB_TEXTEDIT_K_DELETE 0x10008 // keyboard input to delete selection or character under cursor
+#define STB_TEXTEDIT_K_BACKSPACE 0x10009 // keyboard input to delete selection or character left of cursor
+#define STB_TEXTEDIT_K_UNDO 0x1000A // keyboard input to perform undo
+#define STB_TEXTEDIT_K_REDO 0x1000B // keyboard input to perform redo
+#define STB_TEXTEDIT_K_WORDLEFT 0x1000C // keyboard input to move cursor left one word
+#define STB_TEXTEDIT_K_WORDRIGHT 0x1000D // keyboard input to move cursor right one word
+#define STB_TEXTEDIT_K_SHIFT 0x20000
+
+#define STB_TEXTEDIT_IMPLEMENTATION
+#include "stb_textedit.h"
+
+}
+
+void ImGuiInputTextState::OnKeyPressed(int key)
+{
+ stb_textedit_key(this, &StbState, key);
+ CursorFollow = true;
+ CursorAnimReset();
+}
+
+ImGuiInputTextCallbackData::ImGuiInputTextCallbackData()
+{
+ memset(this, 0, sizeof(*this));
+}
+
+// Public API to manipulate UTF-8 text
+// We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar)
+// FIXME: The existence of this rarely exercised code path is a bit of a nuisance.
+void ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count)
+{
+ IM_ASSERT(pos + bytes_count <= BufTextLen);
+ char* dst = Buf + pos;
+ const char* src = Buf + pos + bytes_count;
+ while (char c = *src++)
+ *dst++ = c;
+ *dst = '\0';
+
+ if (CursorPos + bytes_count >= pos)
+ CursorPos -= bytes_count;
+ else if (CursorPos >= pos)
+ CursorPos = pos;
+ SelectionStart = SelectionEnd = CursorPos;
+ BufDirty = true;
+ BufTextLen -= bytes_count;
+}
+
+void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end)
+{
+ const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0;
+ const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text);
+ if (new_text_len + BufTextLen >= BufSize)
+ {
+ if (!is_resizable)
+ return;
+
+ // Contrary to STB_TEXTEDIT_INSERTCHARS() this is working in the UTF8 buffer, hence the midly similar code (until we remove the U16 buffer alltogether!)
+ ImGuiContext& g = *GImGui;
+ ImGuiInputTextState* edit_state = &g.InputTextState;
+ IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID);
+ IM_ASSERT(Buf == edit_state->TempBuffer.Data);
+ int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1;
+ edit_state->TempBuffer.reserve(new_buf_size + 1);
+ Buf = edit_state->TempBuffer.Data;
+ BufSize = edit_state->BufCapacityA = new_buf_size;
+ }
+
+ if (BufTextLen != pos)
+ memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos));
+ memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char));
+ Buf[BufTextLen + new_text_len] = '\0';
+
+ if (CursorPos >= pos)
+ CursorPos += new_text_len;
+ SelectionStart = SelectionEnd = CursorPos;
+ BufDirty = true;
+ BufTextLen += new_text_len;
+}
+
+// Return false to discard a character.
+static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
+{
+ unsigned int c = *p_char;
+
+ if (c < 128 && c != ' ' && !isprint((int)(c & 0xFF)))
+ {
+ bool pass = false;
+ pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline));
+ pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput));
+ if (!pass)
+ return false;
+ }
+
+ if (c >= 0xE000 && c <= 0xF8FF) // Filter private Unicode range. I don't imagine anybody would want to input them. GLFW on OSX seems to send private characters for special keys like arrow keys.
+ return false;
+
+ if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific))
+ {
+ if (flags & ImGuiInputTextFlags_CharsDecimal)
+ if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/'))
+ return false;
+
+ if (flags & ImGuiInputTextFlags_CharsScientific)
+ if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E'))
+ return false;
+
+ if (flags & ImGuiInputTextFlags_CharsHexadecimal)
+ if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F'))
+ return false;
+
+ if (flags & ImGuiInputTextFlags_CharsUppercase)
+ if (c >= 'a' && c <= 'z')
+ *p_char = (c += (unsigned int)('A'-'a'));
+
+ if (flags & ImGuiInputTextFlags_CharsNoBlank)
+ if (ImCharIsBlankW(c))
+ return false;
+ }
+
+ if (flags & ImGuiInputTextFlags_CallbackCharFilter)
+ {
+ ImGuiInputTextCallbackData callback_data;
+ memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData));
+ callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter;
+ callback_data.EventChar = (ImWchar)c;
+ callback_data.Flags = flags;
+ callback_data.UserData = user_data;
+ if (callback(&callback_data) != 0)
+ return false;
+ *p_char = callback_data.EventChar;
+ if (!callback_data.EventChar)
+ return false;
+ }
+
+ return true;
+}
+
+// Edit a string of text
+// - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!".
+// This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match
+// Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator.
+// - When active, hold on a privately held copy of the text (and apply back to 'buf'). So changing 'buf' while the InputText is active has no effect.
+// - If you want to use ImGui::InputText() with std::string, see misc/stl/imgui_stl.h
+// (FIXME: Rather messy function partly because we are doing UTF8 > u16 > UTF8 conversions on the go to more easily handle stb_textedit calls. Ideally we should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188)
+bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* callback_user_data)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys)
+ IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key)
+
+ ImGuiContext& g = *GImGui;
+ const ImGuiIO& io = g.IO;
+ const ImGuiStyle& style = g.Style;
+
+ const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;
+ const bool is_editable = (flags & ImGuiInputTextFlags_ReadOnly) == 0;
+ const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;
+ const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0;
+ const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0;
+ if (is_resizable)
+ IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag!
+
+ if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope,
+ BeginGroup();
+ const ImGuiID id = window->GetID(label);
+ const ImVec2 label_size = CalcTextSize(label, NULL, true);
+ ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), (is_multiline ? GetTextLineHeight() * 8.0f : label_size.y) + style.FramePadding.y*2.0f); // Arbitrary default of 8 lines high for multi-line
+ const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
+ const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? (style.ItemInnerSpacing.x + label_size.x) : 0.0f, 0.0f));
+
+ ImGuiWindow* draw_window = window;
+ if (is_multiline)
+ {
+ ItemAdd(total_bb, id, &frame_bb);
+ if (!BeginChildFrame(id, frame_bb.GetSize()))
+ {
+ EndChildFrame();
+ EndGroup();
+ return false;
+ }
+ draw_window = GetCurrentWindow();
+ draw_window->DC.NavLayerActiveMaskNext |= draw_window->DC.NavLayerCurrentMask; // This is to ensure that EndChild() will display a navigation highlight
+ size.x -= draw_window->ScrollbarSizes.x;
+ }
+ else
+ {
+ ItemSize(total_bb, style.FramePadding.y);
+ if (!ItemAdd(total_bb, id, &frame_bb))
+ return false;
+ }
+ const bool hovered = ItemHoverable(frame_bb, id);
+ if (hovered)
+ g.MouseCursor = ImGuiMouseCursor_TextInput;
+
+ // Password pushes a temporary font with only a fallback glyph
+ if (is_password)
+ {
+ const ImFontGlyph* glyph = g.Font->FindGlyph('*');
+ ImFont* password_font = &g.InputTextPasswordFont;
+ password_font->FontSize = g.Font->FontSize;
+ password_font->Scale = g.Font->Scale;
+ password_font->DisplayOffset = g.Font->DisplayOffset;
+ password_font->Ascent = g.Font->Ascent;
+ password_font->Descent = g.Font->Descent;
+ password_font->ContainerAtlas = g.Font->ContainerAtlas;
+ password_font->FallbackGlyph = glyph;
+ password_font->FallbackAdvanceX = glyph->AdvanceX;
+ IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty());
+ PushFont(password_font);
+ }
+
+ // NB: we are only allowed to access 'edit_state' if we are the active widget.
+ ImGuiInputTextState& edit_state = g.InputTextState;
+
+ const bool focus_requested = FocusableItemRegister(window, id, (flags & (ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_AllowTabInput)) == 0); // Using completion callback disable keyboard tabbing
+ const bool focus_requested_by_code = focus_requested && (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent);
+ const bool focus_requested_by_tab = focus_requested && !focus_requested_by_code;
+
+ const bool user_clicked = hovered && io.MouseClicked[0];
+ const bool user_scrolled = is_multiline && g.ActiveId == 0 && edit_state.ID == id && g.ActiveIdPreviousFrame == draw_window->GetIDNoKeepAlive("#SCROLLY");
+ const bool user_nav_input_start = (g.ActiveId != id) && ((g.NavInputId == id) || (g.NavActivateId == id && g.NavInputSource == ImGuiInputSource_NavKeyboard));
+
+ bool clear_active_id = false;
+
+ bool select_all = (g.ActiveId != id) && ((flags & ImGuiInputTextFlags_AutoSelectAll) != 0 || user_nav_input_start) && (!is_multiline);
+ if (focus_requested || user_clicked || user_scrolled || user_nav_input_start)
+ {
+ if (g.ActiveId != id)
+ {
+ // Start edition
+ // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar)
+ // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode)
+ const int prev_len_w = edit_state.CurLenW;
+ const int init_buf_len = (int)strlen(buf);
+ edit_state.TextW.resize(buf_size+1); // wchar count <= UTF-8 count. we use +1 to make sure that .Data isn't NULL so it doesn't crash.
+ edit_state.InitialText.resize(init_buf_len + 1); // UTF-8. we use +1 to make sure that .Data isn't NULL so it doesn't crash.
+ memcpy(edit_state.InitialText.Data, buf, init_buf_len + 1);
+ const char* buf_end = NULL;
+ edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, buf_size, buf, NULL, &buf_end);
+ edit_state.CurLenA = (int)(buf_end - buf); // We can't get the result from ImStrncpy() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8.
+ edit_state.CursorAnimReset();
+
+ // Preserve cursor position and undo/redo stack if we come back to same widget
+ // FIXME: We should probably compare the whole buffer to be on the safety side. Comparing buf (utf8) and edit_state.Text (wchar).
+ const bool recycle_state = (edit_state.ID == id) && (prev_len_w == edit_state.CurLenW);
+ if (recycle_state)
+ {
+ // Recycle existing cursor/selection/undo stack but clamp position
+ // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler.
+ edit_state.CursorClamp();
+ }
+ else
+ {
+ edit_state.ID = id;
+ edit_state.ScrollX = 0.0f;
+ stb_textedit_initialize_state(&edit_state.StbState, !is_multiline);
+ if (!is_multiline && focus_requested_by_code)
+ select_all = true;
+ }
+ if (flags & ImGuiInputTextFlags_AlwaysInsertMode)
+ edit_state.StbState.insert_mode = true;
+ if (!is_multiline && (focus_requested_by_tab || (user_clicked && io.KeyCtrl)))
+ select_all = true;
+ }
+ SetActiveID(id, window);
+ SetFocusID(id, window);
+ FocusWindow(window);
+ if (!is_multiline && !(flags & ImGuiInputTextFlags_CallbackHistory))
+ g.ActiveIdAllowNavDirFlags |= ((1 << ImGuiDir_Up) | (1 << ImGuiDir_Down));
+ }
+ else if (io.MouseClicked[0])
+ {
+ // Release focus when we click outside
+ clear_active_id = true;
+ }
+
+ bool value_changed = false;
+ bool enter_pressed = false;
+ int backup_current_text_length = 0;
+
+ if (g.ActiveId == id)
+ {
+ if (!is_editable && !g.ActiveIdIsJustActivated)
+ {
+ // When read-only we always use the live data passed to the function
+ edit_state.TextW.resize(buf_size+1);
+ const char* buf_end = NULL;
+ edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, edit_state.TextW.Size, buf, NULL, &buf_end);
+ edit_state.CurLenA = (int)(buf_end - buf);
+ edit_state.CursorClamp();
+ }
+
+ backup_current_text_length = edit_state.CurLenA;
+ edit_state.BufCapacityA = buf_size;
+ edit_state.UserFlags = flags;
+ edit_state.UserCallback = callback;
+ edit_state.UserCallbackData = callback_user_data;
+
+ // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget.
+ // Down the line we should have a cleaner library-wide concept of Selected vs Active.
+ g.ActiveIdAllowOverlap = !io.MouseDown[0];
+ g.WantTextInputNextFrame = 1;
+
+ // Edit in progress
+ const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + edit_state.ScrollX;
+ const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y - style.FramePadding.y) : (g.FontSize*0.5f));
+
+ const bool is_osx = io.ConfigMacOSXBehaviors;
+ if (select_all || (hovered && !is_osx && io.MouseDoubleClicked[0]))
+ {
+ edit_state.SelectAll();
+ edit_state.SelectedAllMouseLock = true;
+ }
+ else if (hovered && is_osx && io.MouseDoubleClicked[0])
+ {
+ // Double-click select a word only, OS X style (by simulating keystrokes)
+ edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT);
+ edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
+ }
+ else if (io.MouseClicked[0] && !edit_state.SelectedAllMouseLock)
+ {
+ if (hovered)
+ {
+ stb_textedit_click(&edit_state, &edit_state.StbState, mouse_x, mouse_y);
+ edit_state.CursorAnimReset();
+ }
+ }
+ else if (io.MouseDown[0] && !edit_state.SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f))
+ {
+ stb_textedit_drag(&edit_state, &edit_state.StbState, mouse_x, mouse_y);
+ edit_state.CursorAnimReset();
+ edit_state.CursorFollow = true;
+ }
+ if (edit_state.SelectedAllMouseLock && !io.MouseDown[0])
+ edit_state.SelectedAllMouseLock = false;
+
+ if (io.InputCharacters[0])
+ {
+ // Process text input (before we check for Return because using some IME will effectively send a Return?)
+ // We ignore CTRL inputs, but need to allow ALT+CTRL as some keyboards (e.g. German) use AltGR (which _is_ Alt+Ctrl) to input certain characters.
+ bool ignore_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeySuper);
+ if (!ignore_inputs && is_editable && !user_nav_input_start)
+ for (int n = 0; n < IM_ARRAYSIZE(io.InputCharacters) && io.InputCharacters[n]; n++)
+ {
+ // Insert character if they pass filtering
+ unsigned int c = (unsigned int)io.InputCharacters[n];
+ if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))
+ edit_state.OnKeyPressed((int)c);
+ }
+
+ // Consume characters
+ memset(g.IO.InputCharacters, 0, sizeof(g.IO.InputCharacters));
+ }
+ }
+
+ bool cancel_edit = false;
+ if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id)
+ {
+ // Handle key-presses
+ const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);
+ const bool is_osx = io.ConfigMacOSXBehaviors;
+ const bool is_shortcut_key = (is_osx ? (io.KeySuper && !io.KeyCtrl) : (io.KeyCtrl && !io.KeySuper)) && !io.KeyAlt && !io.KeyShift; // OS X style: Shortcuts using Cmd/Super instead of Ctrl
+ const bool is_osx_shift_shortcut = is_osx && io.KeySuper && io.KeyShift && !io.KeyCtrl && !io.KeyAlt;
+ const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl
+ const bool is_startend_key_down = is_osx && io.KeySuper && !io.KeyCtrl && !io.KeyAlt; // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End
+ const bool is_ctrl_key_only = io.KeyCtrl && !io.KeyShift && !io.KeyAlt && !io.KeySuper;
+ const bool is_shift_key_only = io.KeyShift && !io.KeyCtrl && !io.KeyAlt && !io.KeySuper;
+
+ const bool is_cut = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Delete))) && is_editable && !is_password && (!is_multiline || edit_state.HasSelection());
+ const bool is_copy = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_C)) || (is_ctrl_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && !is_password && (!is_multiline || edit_state.HasSelection());
+ const bool is_paste = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && is_editable;
+ const bool is_undo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Z)) && is_editable && is_undoable);
+ const bool is_redo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Y)) || (is_osx_shift_shortcut && IsKeyPressedMap(ImGuiKey_Z))) && is_editable && is_undoable;
+
+ if (IsKeyPressedMap(ImGuiKey_LeftArrow)) { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); }
+ else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); }
+ else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); }
+ else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); }
+ else if (IsKeyPressedMap(ImGuiKey_Home)) { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); }
+ else if (IsKeyPressedMap(ImGuiKey_End)) { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); }
+ else if (IsKeyPressedMap(ImGuiKey_Delete) && is_editable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); }
+ else if (IsKeyPressedMap(ImGuiKey_Backspace) && is_editable)
+ {
+ if (!edit_state.HasSelection())
+ {
+ if (is_wordmove_key_down) edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT|STB_TEXTEDIT_K_SHIFT);
+ else if (is_osx && io.KeySuper && !io.KeyAlt && !io.KeyCtrl) edit_state.OnKeyPressed(STB_TEXTEDIT_K_LINESTART|STB_TEXTEDIT_K_SHIFT);
+ }
+ edit_state.OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);
+ }
+ else if (IsKeyPressedMap(ImGuiKey_Enter))
+ {
+ bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0;
+ if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl))
+ {
+ enter_pressed = clear_active_id = true;
+ }
+ else if (is_editable)
+ {
+ unsigned int c = '\n'; // Insert new line
+ if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))
+ edit_state.OnKeyPressed((int)c);
+ }
+ }
+ else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !io.KeyCtrl && !io.KeyShift && !io.KeyAlt && is_editable)
+ {
+ unsigned int c = '\t'; // Insert TAB
+ if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))
+ edit_state.OnKeyPressed((int)c);
+ }
+ else if (IsKeyPressedMap(ImGuiKey_Escape))
+ {
+ clear_active_id = cancel_edit = true;
+ }
+ else if (is_undo || is_redo)
+ {
+ edit_state.OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO);
+ edit_state.ClearSelection();
+ }
+ else if (is_shortcut_key && IsKeyPressedMap(ImGuiKey_A))
+ {
+ edit_state.SelectAll();
+ edit_state.CursorFollow = true;
+ }
+ else if (is_cut || is_copy)
+ {
+ // Cut, Copy
+ if (io.SetClipboardTextFn)
+ {
+ const int ib = edit_state.HasSelection() ? ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end) : 0;
+ const int ie = edit_state.HasSelection() ? ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end) : edit_state.CurLenW;
+ edit_state.TempBuffer.resize((ie-ib) * 4 + 1);
+ ImTextStrToUtf8(edit_state.TempBuffer.Data, edit_state.TempBuffer.Size, edit_state.TextW.Data+ib, edit_state.TextW.Data+ie);
+ SetClipboardText(edit_state.TempBuffer.Data);
+ }
+ if (is_cut)
+ {
+ if (!edit_state.HasSelection())
+ edit_state.SelectAll();
+ edit_state.CursorFollow = true;
+ stb_textedit_cut(&edit_state, &edit_state.StbState);
+ }
+ }
+ else if (is_paste)
+ {
+ if (const char* clipboard = GetClipboardText())
+ {
+ // Filter pasted buffer
+ const int clipboard_len = (int)strlen(clipboard);
+ ImWchar* clipboard_filtered = (ImWchar*)ImGui::MemAlloc((clipboard_len+1) * sizeof(ImWchar));
+ int clipboard_filtered_len = 0;
+ for (const char* s = clipboard; *s; )
+ {
+ unsigned int c;
+ s += ImTextCharFromUtf8(&c, s, NULL);
+ if (c == 0)
+ break;
+ if (c >= 0x10000 || !InputTextFilterCharacter(&c, flags, callback, callback_user_data))
+ continue;
+ clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c;
+ }
+ clipboard_filtered[clipboard_filtered_len] = 0;
+ if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation
+ {
+ stb_textedit_paste(&edit_state, &edit_state.StbState, clipboard_filtered, clipboard_filtered_len);
+ edit_state.CursorFollow = true;
+ }
+ ImGui::MemFree(clipboard_filtered);
+ }
+ }
+ }
+
+ if (g.ActiveId == id)
+ {
+ const char* apply_new_text = NULL;
+ int apply_new_text_length = 0;
+ if (cancel_edit)
+ {
+ // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents.
+ if (is_editable && strcmp(buf, edit_state.InitialText.Data) != 0)
+ {
+ apply_new_text = edit_state.InitialText.Data;
+ apply_new_text_length = edit_state.InitialText.Size - 1;
+ }
+ }
+
+ // When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer before clearing ActiveId, even though strictly speaking it wasn't modified on this frame.
+ // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail. Also this allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage.
+ bool apply_edit_back_to_user_buffer = !cancel_edit || (enter_pressed && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0);
+ if (apply_edit_back_to_user_buffer)
+ {
+ // Apply new value immediately - copy modified buffer back
+ // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer
+ // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect.
+ // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks.
+ if (is_editable)
+ {
+ edit_state.TempBuffer.resize(edit_state.TextW.Size * 4 + 1);
+ ImTextStrToUtf8(edit_state.TempBuffer.Data, edit_state.TempBuffer.Size, edit_state.TextW.Data, NULL);
+ }
+
+ // User callback
+ if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackAlways)) != 0)
+ {
+ IM_ASSERT(callback != NULL);
+
+ // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment.
+ ImGuiInputTextFlags event_flag = 0;
+ ImGuiKey event_key = ImGuiKey_COUNT;
+ if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && IsKeyPressedMap(ImGuiKey_Tab))
+ {
+ event_flag = ImGuiInputTextFlags_CallbackCompletion;
+ event_key = ImGuiKey_Tab;
+ }
+ else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_UpArrow))
+ {
+ event_flag = ImGuiInputTextFlags_CallbackHistory;
+ event_key = ImGuiKey_UpArrow;
+ }
+ else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_DownArrow))
+ {
+ event_flag = ImGuiInputTextFlags_CallbackHistory;
+ event_key = ImGuiKey_DownArrow;
+ }
+ else if (flags & ImGuiInputTextFlags_CallbackAlways)
+ event_flag = ImGuiInputTextFlags_CallbackAlways;
+
+ if (event_flag)
+ {
+ ImGuiInputTextCallbackData callback_data;
+ memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData));
+ callback_data.EventFlag = event_flag;
+ callback_data.Flags = flags;
+ callback_data.UserData = callback_user_data;
+
+ callback_data.EventKey = event_key;
+ callback_data.Buf = edit_state.TempBuffer.Data;
+ callback_data.BufTextLen = edit_state.CurLenA;
+ callback_data.BufSize = edit_state.BufCapacityA;
+ callback_data.BufDirty = false;
+
+ // We have to convert from wchar-positions to UTF-8-positions, which can be pretty slow (an incentive to ditch the ImWchar buffer, see https://github.com/nothings/stb/issues/188)
+ ImWchar* text = edit_state.TextW.Data;
+ const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.cursor);
+ const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_start);
+ const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_end);
+
+ // Call user code
+ callback(&callback_data);
+
+ // Read back what user may have modified
+ IM_ASSERT(callback_data.Buf == edit_state.TempBuffer.Data); // Invalid to modify those fields
+ IM_ASSERT(callback_data.BufSize == edit_state.BufCapacityA);
+ IM_ASSERT(callback_data.Flags == flags);
+ if (callback_data.CursorPos != utf8_cursor_pos) { edit_state.StbState.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); edit_state.CursorFollow = true; }
+ if (callback_data.SelectionStart != utf8_selection_start) { edit_state.StbState.select_start = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); }
+ if (callback_data.SelectionEnd != utf8_selection_end) { edit_state.StbState.select_end = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); }
+ if (callback_data.BufDirty)
+ {
+ 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)
+ edit_state.TextW.resize(edit_state.TextW.Size + (callback_data.BufTextLen - backup_current_text_length));
+ edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, edit_state.TextW.Size, callback_data.Buf, NULL);
+ edit_state.CurLenA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
+ edit_state.CursorAnimReset();
+ }
+ }
+ }
+
+ // Will copy result string if modified
+ if (is_editable && strcmp(edit_state.TempBuffer.Data, buf) != 0)
+ {
+ apply_new_text = edit_state.TempBuffer.Data;
+ apply_new_text_length = edit_state.CurLenA;
+ }
+ }
+
+ // Copy result to user buffer
+ if (apply_new_text)
+ {
+ IM_ASSERT(apply_new_text_length >= 0);
+ if (backup_current_text_length != apply_new_text_length && is_resizable)
+ {
+ ImGuiInputTextCallbackData callback_data;
+ callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;
+ callback_data.Flags = flags;
+ callback_data.Buf = buf;
+ callback_data.BufTextLen = apply_new_text_length;
+ callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1);
+ callback_data.UserData = callback_user_data;
+ callback(&callback_data);
+ buf = callback_data.Buf;
+ buf_size = callback_data.BufSize;
+ apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1);
+ IM_ASSERT(apply_new_text_length <= buf_size);
+ }
+
+ // If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size.
+ ImStrncpy(buf, edit_state.TempBuffer.Data, ImMin(apply_new_text_length + 1, buf_size));
+ value_changed = true;
+ }
+
+ // Clear temporary user storage
+ edit_state.UserFlags = 0;
+ edit_state.UserCallback = NULL;
+ edit_state.UserCallbackData = NULL;
+ }
+
+ // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)
+ if (clear_active_id && g.ActiveId == id)
+ ClearActiveID();
+
+ // Render
+ // Select which buffer we are going to display. When ImGuiInputTextFlags_NoLiveEdit is set 'buf' might still be the old value. We set buf to NULL to prevent accidental usage from now on.
+ const char* buf_display = (g.ActiveId == id && is_editable) ? edit_state.TempBuffer.Data : buf; buf = NULL;
+
+ // Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line
+ // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether.
+ // Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash.
+ const int buf_display_max_length = 2 * 1024 * 1024;
+
+ if (!is_multiline)
+ {
+ RenderNavHighlight(frame_bb, id);
+ RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
+ }
+
+ const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x, frame_bb.Min.y + size.y); // Not using frame_bb.Max because we have adjusted size
+ ImVec2 render_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding;
+ ImVec2 text_size(0.f, 0.f);
+ const bool is_currently_scrolling = (edit_state.ID == id && is_multiline && g.ActiveId == draw_window->GetIDNoKeepAlive("#SCROLLY"));
+ if (g.ActiveId == id || is_currently_scrolling)
+ {
+ edit_state.CursorAnim += io.DeltaTime;
+
+ // This is going to be messy. We need to:
+ // - Display the text (this alone can be more easily clipped)
+ // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation)
+ // - Measure text height (for scrollbar)
+ // We are attempting to do most of that in **one main pass** to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for selection rendering (we could merge them by an extra refactoring effort)
+ // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.
+ const ImWchar* text_begin = edit_state.TextW.Data;
+ ImVec2 cursor_offset, select_start_offset;
+
+ {
+ // Count lines + find lines numbers straddling 'cursor' and 'select_start' position.
+ const ImWchar* searches_input_ptr[2];
+ searches_input_ptr[0] = text_begin + edit_state.StbState.cursor;
+ searches_input_ptr[1] = NULL;
+ int searches_remaining = 1;
+ int searches_result_line_number[2] = { -1, -999 };
+ if (edit_state.StbState.select_start != edit_state.StbState.select_end)
+ {
+ searches_input_ptr[1] = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end);
+ searches_result_line_number[1] = -1;
+ searches_remaining++;
+ }
+
+ // Iterate all lines to find our line numbers
+ // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter.
+ searches_remaining += is_multiline ? 1 : 0;
+ int line_count = 0;
+ for (const ImWchar* s = text_begin; *s != 0; s++)
+ if (*s == '\n')
+ {
+ line_count++;
+ if (searches_result_line_number[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_number[0] = line_count; if (--searches_remaining <= 0) break; }
+ if (searches_result_line_number[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_number[1] = line_count; if (--searches_remaining <= 0) break; }
+ }
+ line_count++;
+ if (searches_result_line_number[0] == -1) searches_result_line_number[0] = line_count;
+ if (searches_result_line_number[1] == -1) searches_result_line_number[1] = line_count;
+
+ // Calculate 2d position by finding the beginning of the line and measuring distance
+ cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x;
+ cursor_offset.y = searches_result_line_number[0] * g.FontSize;
+ if (searches_result_line_number[1] >= 0)
+ {
+ select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x;
+ select_start_offset.y = searches_result_line_number[1] * g.FontSize;
+ }
+
+ // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224)
+ if (is_multiline)
+ text_size = ImVec2(size.x, line_count * g.FontSize);
+ }
+
+ // Scroll
+ if (edit_state.CursorFollow)
+ {
+ // Horizontal scroll in chunks of quarter width
+ if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll))
+ {
+ const float scroll_increment_x = size.x * 0.25f;
+ if (cursor_offset.x < edit_state.ScrollX)
+ edit_state.ScrollX = (float)(int)ImMax(0.0f, cursor_offset.x - scroll_increment_x);
+ else if (cursor_offset.x - size.x >= edit_state.ScrollX)
+ edit_state.ScrollX = (float)(int)(cursor_offset.x - size.x + scroll_increment_x);
+ }
+ else
+ {
+ edit_state.ScrollX = 0.0f;
+ }
+
+ // Vertical scroll
+ if (is_multiline)
+ {
+ float scroll_y = draw_window->Scroll.y;
+ if (cursor_offset.y - g.FontSize < scroll_y)
+ scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);
+ else if (cursor_offset.y - size.y >= scroll_y)
+ scroll_y = cursor_offset.y - size.y;
+ draw_window->DC.CursorPos.y += (draw_window->Scroll.y - scroll_y); // To avoid a frame of lag
+ draw_window->Scroll.y = scroll_y;
+ render_pos.y = draw_window->DC.CursorPos.y;
+ }
+ }
+ edit_state.CursorFollow = false;
+ const ImVec2 render_scroll = ImVec2(edit_state.ScrollX, 0.0f);
+
+ // Draw selection
+ if (edit_state.StbState.select_start != edit_state.StbState.select_end)
+ {
+ const ImWchar* text_selected_begin = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end);
+ const ImWchar* text_selected_end = text_begin + ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end);
+
+ float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection.
+ float bg_offy_dn = is_multiline ? 0.0f : 2.0f;
+ ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg);
+ ImVec2 rect_pos = render_pos + select_start_offset - render_scroll;
+ for (const ImWchar* p = text_selected_begin; p < text_selected_end; )
+ {
+ if (rect_pos.y > clip_rect.w + g.FontSize)
+ break;
+ if (rect_pos.y < clip_rect.y)
+ {
+ while (p < text_selected_end)
+ if (*p++ == '\n')
+ break;
+ }
+ else
+ {
+ ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true);
+ if (rect_size.x <= 0.0f) rect_size.x = (float)(int)(g.Font->GetCharAdvance((unsigned short)' ') * 0.50f); // So we can see selected empty lines
+ ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos +ImVec2(rect_size.x, bg_offy_dn));
+ rect.ClipWith(clip_rect);
+ if (rect.Overlaps(clip_rect))
+ draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);
+ }
+ rect_pos.x = render_pos.x - render_scroll.x;
+ rect_pos.y += g.FontSize;
+ }
+ }
+
+ const int buf_display_len = edit_state.CurLenA;
+ if (is_multiline || buf_display_len < buf_display_max_length)
+ draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, GetColorU32(ImGuiCol_Text), buf_display, buf_display + buf_display_len, 0.0f, is_multiline ? NULL : &clip_rect);
+
+ // Draw blinking cursor
+ bool cursor_is_visible = (!g.IO.ConfigCursorBlink) || (g.InputTextState.CursorAnim <= 0.0f) || ImFmod(g.InputTextState.CursorAnim, 1.20f) <= 0.80f;
+ ImVec2 cursor_screen_pos = render_pos + cursor_offset - render_scroll;
+ ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y-g.FontSize+0.5f, cursor_screen_pos.x+1.0f, cursor_screen_pos.y-1.5f);
+ if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect))
+ draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text));
+
+ // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.)
+ if (is_editable)
+ g.PlatformImePos = ImVec2(cursor_screen_pos.x - 1, cursor_screen_pos.y - g.FontSize);
+ }
+ else
+ {
+ // Render text only
+ const char* buf_end = NULL;
+ if (is_multiline)
+ text_size = ImVec2(size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_end) * g.FontSize); // We don't need width
+ else
+ buf_end = buf_display + strlen(buf_display);
+ if (is_multiline || (buf_end - buf_display) < buf_display_max_length)
+ draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos, GetColorU32(ImGuiCol_Text), buf_display, buf_end, 0.0f, is_multiline ? NULL : &clip_rect);
+ }
+
+ if (is_multiline)
+ {
+ Dummy(text_size + ImVec2(0.0f, g.FontSize)); // Always add room to scroll an extra line
+ EndChildFrame();
+ EndGroup();
+ }
+
+ if (is_password)
+ PopFont();
+
+ // Log as text
+ if (g.LogEnabled && !is_password)
+ LogRenderedText(&render_pos, buf_display, NULL);
+
+ if (label_size.x > 0)
+ RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
+
+ if (value_changed)
+ MarkItemEdited(id);
+
+ if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0)
+ return enter_pressed;
+ else
+ return value_changed;
+}
+
+//-------------------------------------------------------------------------
+// WIDGETS: Color Editor / Picker
+// - ColorEdit3()
+// - ColorEdit4()
+// - ColorPicker3()
+// - RenderColorRectWithAlphaCheckerboard() [Internal]
+// - ColorPicker4()
+// - ColorButton()
+// - SetColorEditOptions()
+// - ColorTooltip() [Internal]
+// - ColorEditOptionsPopup() [Internal]
+// - ColorPickerOptionsPopup() [Internal]
+//-------------------------------------------------------------------------
+
+bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags)
+{
+ return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha);
+}
+
+// Edit colors components (each component in 0.0f..1.0f range).
+// See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
+// With typical options: Left-click on colored square to open color picker. Right-click to open option menu. CTRL-Click over input fields to edit them and TAB to go to next item.
+bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ ImGuiContext& g = *GImGui;
+ const ImGuiStyle& style = g.Style;
+ const float square_sz = GetFrameHeight();
+ const float w_extra = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x);
+ const float w_items_all = CalcItemWidth() - w_extra;
+ const char* label_display_end = FindRenderedTextEnd(label);
+
+ BeginGroup();
+ PushID(label);
+
+ // If we're not showing any slider there's no point in doing any HSV conversions
+ const ImGuiColorEditFlags flags_untouched = flags;
+ if (flags & ImGuiColorEditFlags_NoInputs)
+ flags = (flags & (~ImGuiColorEditFlags__InputsMask)) | ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_NoOptions;
+
+ // Context menu: display and modify options (before defaults are applied)
+ if (!(flags & ImGuiColorEditFlags_NoOptions))
+ ColorEditOptionsPopup(col, flags);
+
+ // Read stored options
+ if (!(flags & ImGuiColorEditFlags__InputsMask))
+ flags |= (g.ColorEditOptions & ImGuiColorEditFlags__InputsMask);
+ if (!(flags & ImGuiColorEditFlags__DataTypeMask))
+ flags |= (g.ColorEditOptions & ImGuiColorEditFlags__DataTypeMask);
+ if (!(flags & ImGuiColorEditFlags__PickerMask))
+ flags |= (g.ColorEditOptions & ImGuiColorEditFlags__PickerMask);
+ flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask));
+
+ const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0;
+ const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0;
+ const int components = alpha ? 4 : 3;
+
+ // Convert to the formats we need
+ float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f };
+ if (flags & ImGuiColorEditFlags_HSV)
+ ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
+ int i[4] = { IM_F32_TO_INT8_UNBOUND(f[0]), IM_F32_TO_INT8_UNBOUND(f[1]), IM_F32_TO_INT8_UNBOUND(f[2]), IM_F32_TO_INT8_UNBOUND(f[3]) };
+
+ bool value_changed = false;
+ bool value_changed_as_float = false;
+
+ if ((flags & (ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_HSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
+ {
+ // RGB/HSV 0..255 Sliders
+ const float w_item_one = ImMax(1.0f, (float)(int)((w_items_all - (style.ItemInnerSpacing.x) * (components-1)) / (float)components));
+ const float w_item_last = ImMax(1.0f, (float)(int)(w_items_all - (w_item_one + style.ItemInnerSpacing.x) * (components-1)));
+
+ const bool hide_prefix = (w_item_one <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x);
+ const char* ids[4] = { "##X", "##Y", "##Z", "##W" };
+ const char* fmt_table_int[3][4] =
+ {
+ { "%3d", "%3d", "%3d", "%3d" }, // Short display
+ { "R:%3d", "G:%3d", "B:%3d", "A:%3d" }, // Long display for RGBA
+ { "H:%3d", "S:%3d", "V:%3d", "A:%3d" } // Long display for HSVA
+ };
+ const char* fmt_table_float[3][4] =
+ {
+ { "%0.3f", "%0.3f", "%0.3f", "%0.3f" }, // Short display
+ { "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA
+ { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" } // Long display for HSVA
+ };
+ const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_HSV) ? 2 : 1;
+
+ PushItemWidth(w_item_one);
+ for (int n = 0; n < components; n++)
+ {
+ if (n > 0)
+ SameLine(0, style.ItemInnerSpacing.x);
+ if (n + 1 == components)
+ PushItemWidth(w_item_last);
+ if (flags & ImGuiColorEditFlags_Float)
+ value_changed = value_changed_as_float = value_changed | DragFloat(ids[n], &f[n], 1.0f/255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]);
+ else
+ value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]);
+ if (!(flags & ImGuiColorEditFlags_NoOptions))
+ OpenPopupOnItemClick("context");
+ }
+ PopItemWidth();
+ PopItemWidth();
+ }
+ else if ((flags & ImGuiColorEditFlags_HEX) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
+ {
+ // RGB Hexadecimal Input
+ char buf[64];
+ if (alpha)
+ ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255), ImClamp(i[3],0,255));
+ else
+ ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255));
+ PushItemWidth(w_items_all);
+ if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase))
+ {
+ value_changed = true;
+ char* p = buf;
+ while (*p == '#' || ImCharIsBlankA(*p))
+ p++;
+ i[0] = i[1] = i[2] = i[3] = 0;
+ if (alpha)
+ sscanf(p, "%02X%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2], (unsigned int*)&i[3]); // Treat at unsigned (%X is unsigned)
+ else
+ sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]);
+ }
+ if (!(flags & ImGuiColorEditFlags_NoOptions))
+ OpenPopupOnItemClick("context");
+ PopItemWidth();
+ }
+
+ ImGuiWindow* picker_active_window = NULL;
+ if (!(flags & ImGuiColorEditFlags_NoSmallPreview))
+ {
+ if (!(flags & ImGuiColorEditFlags_NoInputs))
+ SameLine(0, style.ItemInnerSpacing.x);
+
+ const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f);
+ if (ColorButton("##ColorButton", col_v4, flags))
+ {
+ if (!(flags & ImGuiColorEditFlags_NoPicker))
+ {
+ // Store current color and open a picker
+ g.ColorPickerRef = col_v4;
+ OpenPopup("picker");
+ SetNextWindowPos(window->DC.LastItemRect.GetBL() + ImVec2(-1,style.ItemSpacing.y));
+ }
+ }
+ if (!(flags & ImGuiColorEditFlags_NoOptions))
+ OpenPopupOnItemClick("context");
+
+ if (BeginPopup("picker"))
+ {
+ picker_active_window = g.CurrentWindow;
+ if (label != label_display_end)
+ {
+ TextUnformatted(label, label_display_end);
+ Separator();
+ }
+ ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar;
+ ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf;
+ PushItemWidth(square_sz * 12.0f); // Use 256 + bar sizes?
+ value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x);
+ PopItemWidth();
+ EndPopup();
+ }
+ }
+
+ if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel))
+ {
+ SameLine(0, style.ItemInnerSpacing.x);
+ TextUnformatted(label, label_display_end);
+ }
+
+ // Convert back
+ if (picker_active_window == NULL)
+ {
+ if (!value_changed_as_float)
+ for (int n = 0; n < 4; n++)
+ f[n] = i[n] / 255.0f;
+ if (flags & ImGuiColorEditFlags_HSV)
+ ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
+ if (value_changed)
+ {
+ col[0] = f[0];
+ col[1] = f[1];
+ col[2] = f[2];
+ if (alpha)
+ col[3] = f[3];
+ }
+ }
+
+ PopID();
+ EndGroup();
+
+ // Drag and Drop Target
+ // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test.
+ if ((window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget())
+ {
+ if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
+ {
+ memcpy((float*)col, payload->Data, sizeof(float) * 3);
+ value_changed = true;
+ }
+ if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
+ {
+ memcpy((float*)col, payload->Data, sizeof(float) * components);
+ value_changed = true;
+ }
+ EndDragDropTarget();
+ }
+
+ // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4().
+ if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window)
+ window->DC.LastItemId = g.ActiveId;
+
+ if (value_changed)
+ MarkItemEdited(window->DC.LastItemId);
+
+ return value_changed;
+}
+
+bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags)
+{
+ float col4[4] = { col[0], col[1], col[2], 1.0f };
+ if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha))
+ return false;
+ col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2];
+ return true;
+}
+
+static inline ImU32 ImAlphaBlendColor(ImU32 col_a, ImU32 col_b)
+{
+ float t = ((col_b >> IM_COL32_A_SHIFT) & 0xFF) / 255.f;
+ int r = ImLerp((int)(col_a >> IM_COL32_R_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_R_SHIFT) & 0xFF, t);
+ int g = ImLerp((int)(col_a >> IM_COL32_G_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_G_SHIFT) & 0xFF, t);
+ int b = ImLerp((int)(col_a >> IM_COL32_B_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_B_SHIFT) & 0xFF, t);
+ return IM_COL32(r, g, b, 0xFF);
+}
+
+// Helper for ColorPicker4()
+// NB: This is rather brittle and will show artifact when rounding this enabled if rounded corners overlap multiple cells. Caller currently responsible for avoiding that.
+// I spent a non reasonable amount of time trying to getting this right for ColorButton with rounding+anti-aliasing+ImGuiColorEditFlags_HalfAlphaPreview flag + various grid sizes and offsets, and eventually gave up... probably more reasonable to disable rounding alltogether.
+void ImGui::RenderColorRectWithAlphaCheckerboard(ImVec2 p_min, ImVec2 p_max, ImU32 col, float grid_step, ImVec2 grid_off, float rounding, int rounding_corners_flags)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (((col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) < 0xFF)
+ {
+ ImU32 col_bg1 = GetColorU32(ImAlphaBlendColor(IM_COL32(204,204,204,255), col));
+ ImU32 col_bg2 = GetColorU32(ImAlphaBlendColor(IM_COL32(128,128,128,255), col));
+ window->DrawList->AddRectFilled(p_min, p_max, col_bg1, rounding, rounding_corners_flags);
+
+ int yi = 0;
+ for (float y = p_min.y + grid_off.y; y < p_max.y; y += grid_step, yi++)
+ {
+ float y1 = ImClamp(y, p_min.y, p_max.y), y2 = ImMin(y + grid_step, p_max.y);
+ if (y2 <= y1)
+ continue;
+ for (float x = p_min.x + grid_off.x + (yi & 1) * grid_step; x < p_max.x; x += grid_step * 2.0f)
+ {
+ float x1 = ImClamp(x, p_min.x, p_max.x), x2 = ImMin(x + grid_step, p_max.x);
+ if (x2 <= x1)
+ continue;
+ int rounding_corners_flags_cell = 0;
+ if (y1 <= p_min.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopRight; }
+ if (y2 >= p_max.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotRight; }
+ rounding_corners_flags_cell &= rounding_corners_flags;
+ window->DrawList->AddRectFilled(ImVec2(x1,y1), ImVec2(x2,y2), col_bg2, rounding_corners_flags_cell ? rounding : 0.0f, rounding_corners_flags_cell);
+ }
+ }
+ }
+ else
+ {
+ window->DrawList->AddRectFilled(p_min, p_max, col, rounding, rounding_corners_flags);
+ }
+}
+
+// Helper for ColorPicker4()
+static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w)
+{
+ ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x + 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Right, IM_COL32_BLACK);
+ ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x, pos.y), half_sz, ImGuiDir_Right, IM_COL32_WHITE);
+ ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x - 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Left, IM_COL32_BLACK);
+ ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x, pos.y), half_sz, ImGuiDir_Left, IM_COL32_WHITE);
+}
+
+// Note: ColorPicker4() only accesses 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
+// FIXME: we adjust the big color square height based on item width, which may cause a flickering feedback loop (if automatic height makes a vertical scrollbar appears, affecting automatic width..)
+bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col)
+{
+ ImGuiContext& g = *GImGui;
+ ImGuiWindow* window = GetCurrentWindow();
+ ImDrawList* draw_list = window->DrawList;
+
+ ImGuiStyle& style = g.Style;
+ ImGuiIO& io = g.IO;
+
+ PushID(label);
+ BeginGroup();
+
+ if (!(flags & ImGuiColorEditFlags_NoSidePreview))
+ flags |= ImGuiColorEditFlags_NoSmallPreview;
+
+ // Context menu: display and store options.
+ if (!(flags & ImGuiColorEditFlags_NoOptions))
+ ColorPickerOptionsPopup(col, flags);
+
+ // Read stored options
+ if (!(flags & ImGuiColorEditFlags__PickerMask))
+ flags |= ((g.ColorEditOptions & ImGuiColorEditFlags__PickerMask) ? g.ColorEditOptions : ImGuiColorEditFlags__OptionsDefault) & ImGuiColorEditFlags__PickerMask;
+ IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check that only 1 is selected
+ if (!(flags & ImGuiColorEditFlags_NoOptions))
+ flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar);
+
+ // Setup
+ int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4;
+ bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha);
+ ImVec2 picker_pos = window->DC.CursorPos;
+ float square_sz = GetFrameHeight();
+ float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars
+ float sv_picker_size = ImMax(bars_width * 1, CalcItemWidth() - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box
+ float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x;
+ float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x;
+ float bars_triangles_half_sz = (float)(int)(bars_width * 0.20f);
+
+ float backup_initial_col[4];
+ memcpy(backup_initial_col, col, components * sizeof(float));
+
+ float wheel_thickness = sv_picker_size * 0.08f;
+ float wheel_r_outer = sv_picker_size * 0.50f;
+ float wheel_r_inner = wheel_r_outer - wheel_thickness;
+ ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size*0.5f);
+
+ // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic.
+ float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f);
+ ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point.
+ ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point.
+ ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point.
+
+ float H,S,V;
+ ColorConvertRGBtoHSV(col[0], col[1], col[2], H, S, V);
+
+ bool value_changed = false, value_changed_h = false, value_changed_sv = false;
+
+ PushItemFlag(ImGuiItemFlags_NoNav, true);
+ if (flags & ImGuiColorEditFlags_PickerHueWheel)
+ {
+ // Hue wheel + SV triangle logic
+ InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size));
+ if (IsItemActive())
+ {
+ ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center;
+ ImVec2 current_off = g.IO.MousePos - wheel_center;
+ float initial_dist2 = ImLengthSqr(initial_off);
+ if (initial_dist2 >= (wheel_r_inner-1)*(wheel_r_inner-1) && initial_dist2 <= (wheel_r_outer+1)*(wheel_r_outer+1))
+ {
+ // Interactive with Hue wheel
+ H = ImAtan2(current_off.y, current_off.x) / IM_PI*0.5f;
+ if (H < 0.0f)
+ H += 1.0f;
+ value_changed = value_changed_h = true;
+ }
+ float cos_hue_angle = ImCos(-H * 2.0f * IM_PI);
+ float sin_hue_angle = ImSin(-H * 2.0f * IM_PI);
+ if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle)))
+ {
+ // Interacting with SV triangle
+ ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle);
+ if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated))
+ current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated);
+ float uu, vv, ww;
+ ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww);
+ V = ImClamp(1.0f - vv, 0.0001f, 1.0f);
+ S = ImClamp(uu / V, 0.0001f, 1.0f);
+ value_changed = value_changed_sv = true;
+ }
+ }
+ if (!(flags & ImGuiColorEditFlags_NoOptions))
+ OpenPopupOnItemClick("context");
+ }
+ else if (flags & ImGuiColorEditFlags_PickerHueBar)
+ {
+ // SV rectangle logic
+ InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size));
+ if (IsItemActive())
+ {
+ S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size-1));
+ V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
+ value_changed = value_changed_sv = true;
+ }
+ if (!(flags & ImGuiColorEditFlags_NoOptions))
+ OpenPopupOnItemClick("context");
+
+ // Hue bar logic
+ SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y));
+ InvisibleButton("hue", ImVec2(bars_width, sv_picker_size));
+ if (IsItemActive())
+ {
+ H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
+ value_changed = value_changed_h = true;
+ }
+ }
+
+ // Alpha bar logic
+ if (alpha_bar)
+ {
+ SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y));
+ InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size));
+ if (IsItemActive())
+ {
+ col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
+ value_changed = true;
+ }
+ }
+ PopItemFlag(); // ImGuiItemFlags_NoNav
+
+ if (!(flags & ImGuiColorEditFlags_NoSidePreview))
+ {
+ SameLine(0, style.ItemInnerSpacing.x);
+ BeginGroup();
+ }
+
+ if (!(flags & ImGuiColorEditFlags_NoLabel))
+ {
+ const char* label_display_end = FindRenderedTextEnd(label);
+ if (label != label_display_end)
+ {
+ if ((flags & ImGuiColorEditFlags_NoSidePreview))
+ SameLine(0, style.ItemInnerSpacing.x);
+ TextUnformatted(label, label_display_end);
+ }
+ }
+
+ if (!(flags & ImGuiColorEditFlags_NoSidePreview))
+ {
+ PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);
+ ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
+ if ((flags & ImGuiColorEditFlags_NoLabel))
+ Text("Current");
+ ColorButton("##current", col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2));
+ if (ref_col != NULL)
+ {
+ Text("Original");
+ ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]);
+ if (ColorButton("##original", ref_col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2)))
+ {
+ memcpy(col, ref_col, components * sizeof(float));
+ value_changed = true;
+ }
+ }
+ PopItemFlag();
+ EndGroup();
+ }
+
+ // Convert back color to RGB
+ if (value_changed_h || value_changed_sv)
+ ColorConvertHSVtoRGB(H >= 1.0f ? H - 10 * 1e-6f : H, S > 0.0f ? S : 10*1e-6f, V > 0.0f ? V : 1e-6f, col[0], col[1], col[2]);
+
+ // R,G,B and H,S,V slider color editor
+ bool value_changed_fix_hue_wrap = false;
+ if ((flags & ImGuiColorEditFlags_NoInputs) == 0)
+ {
+ PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x);
+ ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf;
+ ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker;
+ if (flags & ImGuiColorEditFlags_RGB || (flags & ImGuiColorEditFlags__InputsMask) == 0)
+ if (ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_RGB))
+ {
+ // FIXME: Hackily differenciating using the DragInt (ActiveId != 0 && !ActiveIdAllowOverlap) vs. using the InputText or DropTarget.
+ // For the later we don't want to run the hue-wrap canceling code. If you are well versed in HSV picker please provide your input! (See #2050)
+ value_changed_fix_hue_wrap = (g.ActiveId != 0 && !g.ActiveIdAllowOverlap);
+ value_changed = true;
+ }
+ if (flags & ImGuiColorEditFlags_HSV || (flags & ImGuiColorEditFlags__InputsMask) == 0)
+ value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_HSV);
+ if (flags & ImGuiColorEditFlags_HEX || (flags & ImGuiColorEditFlags__InputsMask) == 0)
+ value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_HEX);
+ PopItemWidth();
+ }
+
+ // Try to cancel hue wrap (after ColorEdit4 call), if any
+ if (value_changed_fix_hue_wrap)
+ {
+ float new_H, new_S, new_V;
+ ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V);
+ if (new_H <= 0 && H > 0)
+ {
+ if (new_V <= 0 && V != new_V)
+ ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]);
+ else if (new_S <= 0)
+ ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]);
+ }
+ }
+
+ ImVec4 hue_color_f(1, 1, 1, 1); ColorConvertHSVtoRGB(H, 1, 1, hue_color_f.x, hue_color_f.y, hue_color_f.z);
+ ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f);
+ ImU32 col32_no_alpha = ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 1.0f));
+
+ const ImU32 hue_colors[6+1] = { IM_COL32(255,0,0,255), IM_COL32(255,255,0,255), IM_COL32(0,255,0,255), IM_COL32(0,255,255,255), IM_COL32(0,0,255,255), IM_COL32(255,0,255,255), IM_COL32(255,0,0,255) };
+ ImVec2 sv_cursor_pos;
+
+ if (flags & ImGuiColorEditFlags_PickerHueWheel)
+ {
+ // Render Hue Wheel
+ const float aeps = 1.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out).
+ const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12);
+ for (int n = 0; n < 6; n++)
+ {
+ const float a0 = (n) /6.0f * 2.0f * IM_PI - aeps;
+ const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps;
+ const int vert_start_idx = draw_list->VtxBuffer.Size;
+ draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc);
+ draw_list->PathStroke(IM_COL32_WHITE, false, wheel_thickness);
+ const int vert_end_idx = draw_list->VtxBuffer.Size;
+
+ // Paint colors over existing vertices
+ ImVec2 gradient_p0(wheel_center.x + ImCos(a0) * wheel_r_inner, wheel_center.y + ImSin(a0) * wheel_r_inner);
+ ImVec2 gradient_p1(wheel_center.x + ImCos(a1) * wheel_r_inner, wheel_center.y + ImSin(a1) * wheel_r_inner);
+ ShadeVertsLinearColorGradientKeepAlpha(draw_list, vert_start_idx, vert_end_idx, gradient_p0, gradient_p1, hue_colors[n], hue_colors[n+1]);
+ }
+
+ // Render Cursor + preview on Hue Wheel
+ float cos_hue_angle = ImCos(H * 2.0f * IM_PI);
+ float sin_hue_angle = ImSin(H * 2.0f * IM_PI);
+ ImVec2 hue_cursor_pos(wheel_center.x + cos_hue_angle * (wheel_r_inner+wheel_r_outer)*0.5f, wheel_center.y + sin_hue_angle * (wheel_r_inner+wheel_r_outer)*0.5f);
+ float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f;
+ int hue_cursor_segments = ImClamp((int)(hue_cursor_rad / 1.4f), 9, 32);
+ draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments);
+ draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad+1, IM_COL32(128,128,128,255), hue_cursor_segments);
+ draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, IM_COL32_WHITE, hue_cursor_segments);
+
+ // Render SV triangle (rotated according to hue)
+ ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle);
+ ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle);
+ ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle);
+ ImVec2 uv_white = GetFontTexUvWhitePixel();
+ draw_list->PrimReserve(6, 6);
+ draw_list->PrimVtx(tra, uv_white, hue_color32);
+ draw_list->PrimVtx(trb, uv_white, hue_color32);
+ draw_list->PrimVtx(trc, uv_white, IM_COL32_WHITE);
+ draw_list->PrimVtx(tra, uv_white, IM_COL32_BLACK_TRANS);
+ draw_list->PrimVtx(trb, uv_white, IM_COL32_BLACK);
+ draw_list->PrimVtx(trc, uv_white, IM_COL32_BLACK_TRANS);
+ draw_list->AddTriangle(tra, trb, trc, IM_COL32(128,128,128,255), 1.5f);
+ sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V));
+ }
+ else if (flags & ImGuiColorEditFlags_PickerHueBar)
+ {
+ // Render SV Square
+ draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_WHITE, hue_color32, hue_color32, IM_COL32_WHITE);
+ draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_BLACK_TRANS, IM_COL32_BLACK_TRANS, IM_COL32_BLACK, IM_COL32_BLACK);
+ RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), 0.0f);
+ sv_cursor_pos.x = ImClamp((float)(int)(picker_pos.x + ImSaturate(S) * sv_picker_size + 0.5f), picker_pos.x + 2, picker_pos.x + sv_picker_size - 2); // Sneakily prevent the circle to stick out too much
+ sv_cursor_pos.y = ImClamp((float)(int)(picker_pos.y + ImSaturate(1 - V) * sv_picker_size + 0.5f), picker_pos.y + 2, picker_pos.y + sv_picker_size - 2);
+
+ // Render Hue Bar
+ for (int i = 0; i < 6; ++i)
+ draw_list->AddRectFilledMultiColor(ImVec2(bar0_pos_x, picker_pos.y + i * (sv_picker_size / 6)), ImVec2(bar0_pos_x + bars_width, picker_pos.y + (i + 1) * (sv_picker_size / 6)), hue_colors[i], hue_colors[i], hue_colors[i + 1], hue_colors[i + 1]);
+ float bar0_line_y = (float)(int)(picker_pos.y + H * sv_picker_size + 0.5f);
+ RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f);
+ RenderArrowsForVerticalBar(draw_list, ImVec2(bar0_pos_x - 1, bar0_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f);
+ }
+
+ // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range)
+ float sv_cursor_rad = value_changed_sv ? 10.0f : 6.0f;
+ draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, col32_no_alpha, 12);
+ draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad+1, IM_COL32(128,128,128,255), 12);
+ draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, IM_COL32_WHITE, 12);
+
+ // Render alpha bar
+ if (alpha_bar)
+ {
+ float alpha = ImSaturate(col[3]);
+ ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size);
+ RenderColorRectWithAlphaCheckerboard(bar1_bb.Min, bar1_bb.Max, IM_COL32(0,0,0,0), bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f));
+ draw_list->AddRectFilledMultiColor(bar1_bb.Min, bar1_bb.Max, col32_no_alpha, col32_no_alpha, col32_no_alpha & ~IM_COL32_A_MASK, col32_no_alpha & ~IM_COL32_A_MASK);
+ float bar1_line_y = (float)(int)(picker_pos.y + (1.0f - alpha) * sv_picker_size + 0.5f);
+ RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f);
+ RenderArrowsForVerticalBar(draw_list, ImVec2(bar1_pos_x - 1, bar1_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f);
+ }
+
+ EndGroup();
+
+ if (value_changed && memcmp(backup_initial_col, col, components * sizeof(float)) == 0)
+ value_changed = false;
+ if (value_changed)
+ MarkItemEdited(window->DC.LastItemId);
+
+ PopID();
+
+ return value_changed;
+}
+
+// A little colored square. Return true when clicked.
+// FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip.
+// 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip.
+bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, ImVec2 size)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ ImGuiContext& g = *GImGui;
+ const ImGuiID id = window->GetID(desc_id);
+ float default_size = GetFrameHeight();
+ if (size.x == 0.0f)
+ size.x = default_size;
+ if (size.y == 0.0f)
+ size.y = default_size;
+ const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
+ ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
+ if (!ItemAdd(bb, id))
+ return false;
+
+ bool hovered, held;
+ bool pressed = ButtonBehavior(bb, id, &hovered, &held);
+
+ if (flags & ImGuiColorEditFlags_NoAlpha)
+ flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf);
+
+ ImVec4 col_without_alpha(col.x, col.y, col.z, 1.0f);
+ float grid_step = ImMin(size.x, size.y) / 2.99f;
+ float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f);
+ ImRect bb_inner = bb;
+ float off = -0.75f; // The border (using Col_FrameBg) tends to look off when color is near-opaque and rounding is enabled. This offset seemed like a good middle ground to reduce those artifacts.
+ bb_inner.Expand(off);
+ if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col.w < 1.0f)
+ {
+ float mid_x = (float)(int)((bb_inner.Min.x + bb_inner.Max.x) * 0.5f + 0.5f);
+ RenderColorRectWithAlphaCheckerboard(ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawCornerFlags_TopRight| ImDrawCornerFlags_BotRight);
+ window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_without_alpha), rounding, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotLeft);
+ }
+ else
+ {
+ // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha
+ ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col : col_without_alpha;
+ if (col_source.w < 1.0f)
+ RenderColorRectWithAlphaCheckerboard(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding);
+ else
+ window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding, ImDrawCornerFlags_All);
+ }
+ RenderNavHighlight(bb, id);
+ if (g.Style.FrameBorderSize > 0.0f)
+ RenderFrameBorder(bb.Min, bb.Max, rounding);
+ else
+ window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border
+
+ // Drag and Drop Source
+ // NB: The ActiveId test is merely an optional micro-optimization, BeginDragDropSource() does the same test.
+ if (g.ActiveId == id && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropSource())
+ {
+ if (flags & ImGuiColorEditFlags_NoAlpha)
+ SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col, sizeof(float) * 3, ImGuiCond_Once);
+ else
+ SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col, sizeof(float) * 4, ImGuiCond_Once);
+ ColorButton(desc_id, col, flags);
+ SameLine();
+ TextUnformatted("Color");
+ EndDragDropSource();
+ }
+
+ // Tooltip
+ if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered)
+ ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf));
+
+ if (pressed)
+ MarkItemEdited(id);
+
+ return pressed;
+}
+
+void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags)
+{
+ ImGuiContext& g = *GImGui;
+ if ((flags & ImGuiColorEditFlags__InputsMask) == 0)
+ flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__InputsMask;
+ if ((flags & ImGuiColorEditFlags__DataTypeMask) == 0)
+ flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__DataTypeMask;
+ if ((flags & ImGuiColorEditFlags__PickerMask) == 0)
+ flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__PickerMask;
+ IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__InputsMask))); // Check only 1 option is selected
+ IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__DataTypeMask))); // Check only 1 option is selected
+ IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check only 1 option is selected
+ g.ColorEditOptions = flags;
+}
+
+// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
+void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags)
+{
+ ImGuiContext& g = *GImGui;
+
+ int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]);
+ BeginTooltipEx(0, true);
+
+ const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text;
+ if (text_end > text)
+ {
+ TextUnformatted(text, text_end);
+ Separator();
+ }
+
+ ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2);
+ ColorButton("##preview", ImVec4(col[0], col[1], col[2], col[3]), (flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz);
+ SameLine();
+ if (flags & ImGuiColorEditFlags_NoAlpha)
+ Text("#%02X%02X%02X\nR: %d, G: %d, B: %d\n(%.3f, %.3f, %.3f)", cr, cg, cb, cr, cg, cb, col[0], col[1], col[2]);
+ else
+ Text("#%02X%02X%02X%02X\nR:%d, G:%d, B:%d, A:%d\n(%.3f, %.3f, %.3f, %.3f)", cr, cg, cb, ca, cr, cg, cb, ca, col[0], col[1], col[2], col[3]);
+ EndTooltip();
+}
+
+void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags)
+{
+ bool allow_opt_inputs = !(flags & ImGuiColorEditFlags__InputsMask);
+ bool allow_opt_datatype = !(flags & ImGuiColorEditFlags__DataTypeMask);
+ if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context"))
+ return;
+ ImGuiContext& g = *GImGui;
+ ImGuiColorEditFlags opts = g.ColorEditOptions;
+ if (allow_opt_inputs)
+ {
+ if (RadioButton("RGB", (opts & ImGuiColorEditFlags_RGB) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_RGB;
+ if (RadioButton("HSV", (opts & ImGuiColorEditFlags_HSV) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HSV;
+ if (RadioButton("HEX", (opts & ImGuiColorEditFlags_HEX) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HEX;
+ }
+ if (allow_opt_datatype)
+ {
+ if (allow_opt_inputs) Separator();
+ if (RadioButton("0..255", (opts & ImGuiColorEditFlags_Uint8) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Uint8;
+ if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Float;
+ }
+
+ if (allow_opt_inputs || allow_opt_datatype)
+ Separator();
+ if (Button("Copy as..", ImVec2(-1,0)))
+ OpenPopup("Copy");
+ if (BeginPopup("Copy"))
+ {
+ int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]);
+ char buf[64];
+ ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
+ if (Selectable(buf))
+ SetClipboardText(buf);
+ ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca);
+ if (Selectable(buf))
+ SetClipboardText(buf);
+ if (flags & ImGuiColorEditFlags_NoAlpha)
+ ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X", cr, cg, cb);
+ else
+ ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X%02X", cr, cg, cb, ca);
+ if (Selectable(buf))
+ SetClipboardText(buf);
+ EndPopup();
+ }
+
+ g.ColorEditOptions = opts;
+ EndPopup();
+}
+
+void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags)
+{
+ bool allow_opt_picker = !(flags & ImGuiColorEditFlags__PickerMask);
+ bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar);
+ if ((!allow_opt_picker && !allow_opt_alpha_bar) || !ImGui::BeginPopup("context"))
+ return;
+ ImGuiContext& g = *GImGui;
+ if (allow_opt_picker)
+ {
+ ImVec2 picker_size(g.FontSize * 8, ImMax(g.FontSize * 8 - (ImGui::GetFrameHeight() + g.Style.ItemInnerSpacing.x), 1.0f)); // FIXME: Picker size copied from main picker function
+ ImGui::PushItemWidth(picker_size.x);
+ for (int picker_type = 0; picker_type < 2; picker_type++)
+ {
+ // Draw small/thumbnail version of each picker type (over an invisible button for selection)
+ if (picker_type > 0) ImGui::Separator();
+ ImGui::PushID(picker_type);
+ ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs|ImGuiColorEditFlags_NoOptions|ImGuiColorEditFlags_NoLabel|ImGuiColorEditFlags_NoSidePreview|(flags & ImGuiColorEditFlags_NoAlpha);
+ if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar;
+ if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel;
+ ImVec2 backup_pos = ImGui::GetCursorScreenPos();
+ if (ImGui::Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup
+ g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags__PickerMask) | (picker_flags & ImGuiColorEditFlags__PickerMask);
+ ImGui::SetCursorScreenPos(backup_pos);
+ ImVec4 dummy_ref_col;
+ memcpy(&dummy_ref_col.x, ref_col, sizeof(float) * (picker_flags & ImGuiColorEditFlags_NoAlpha ? 3 : 4));
+ ImGui::ColorPicker4("##dummypicker", &dummy_ref_col.x, picker_flags);
+ ImGui::PopID();
+ }
+ ImGui::PopItemWidth();
+ }
+ if (allow_opt_alpha_bar)
+ {
+ if (allow_opt_picker) ImGui::Separator();
+ ImGui::CheckboxFlags("Alpha Bar", (unsigned int*)&g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar);
+ }
+ ImGui::EndPopup();
+}
+
+//-------------------------------------------------------------------------
+// WIDGETS: Trees
+// - TreeNode()
+// - TreeNodeV()
+// - TreeNodeEx()
+// - TreeNodeExV()
+// - TreeNodeBehavior() [Internal]
+// - TreePush()
+// - TreePop()
+// - TreeAdvanceToLabelPos()
+// - GetTreeNodeToLabelSpacing()
+// - SetNextTreeNodeOpen()
+// - CollapsingHeader()
+//-------------------------------------------------------------------------
+
+bool ImGui::TreeNode(const char* str_id, const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ bool is_open = TreeNodeExV(str_id, 0, fmt, args);
+ va_end(args);
+ return is_open;
+}
+
+bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ bool is_open = TreeNodeExV(ptr_id, 0, fmt, args);
+ va_end(args);
+ return is_open;
+}
+
+bool ImGui::TreeNode(const char* label)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+ return TreeNodeBehavior(window->GetID(label), 0, label, NULL);
+}
+
+bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args)
+{
+ return TreeNodeExV(str_id, 0, fmt, args);
+}
+
+bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args)
+{
+ return TreeNodeExV(ptr_id, 0, fmt, args);
+}
+
+bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ return TreeNodeBehavior(window->GetID(label), flags, label, NULL);
+}
+
+bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ bool is_open = TreeNodeExV(str_id, flags, fmt, args);
+ va_end(args);
+ return is_open;
+}
+
+bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ bool is_open = TreeNodeExV(ptr_id, flags, fmt, args);
+ va_end(args);
+ return is_open;
+}
+
+bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ ImGuiContext& g = *GImGui;
+ const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
+ return TreeNodeBehavior(window->GetID(str_id), flags, g.TempBuffer, label_end);
+}
+
+bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ ImGuiContext& g = *GImGui;
+ const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
+ return TreeNodeBehavior(window->GetID(ptr_id), flags, g.TempBuffer, label_end);
+}
+
+bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags)
+{
+ if (flags & ImGuiTreeNodeFlags_Leaf)
+ return true;
+
+ // We only write to the tree storage if the user clicks (or explicitly use SetNextTreeNode*** functions)
+ ImGuiContext& g = *GImGui;
+ ImGuiWindow* window = g.CurrentWindow;
+ ImGuiStorage* storage = window->DC.StateStorage;
+
+ bool is_open;
+ if (g.NextTreeNodeOpenCond != 0)
+ {
+ if (g.NextTreeNodeOpenCond & ImGuiCond_Always)
+ {
+ is_open = g.NextTreeNodeOpenVal;
+ storage->SetInt(id, is_open);
+ }
+ else
+ {
+ // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently.
+ const int stored_value = storage->GetInt(id, -1);
+ if (stored_value == -1)
+ {
+ is_open = g.NextTreeNodeOpenVal;
+ storage->SetInt(id, is_open);
+ }
+ else
+ {
+ is_open = stored_value != 0;
+ }
+ }
+ g.NextTreeNodeOpenCond = 0;
+ }
+ else
+ {
+ is_open = storage->GetInt(id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0;
+ }
+
+ // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior).
+ // NB- If we are above max depth we still allow manually opened nodes to be logged.
+ if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && window->DC.TreeDepth < g.LogAutoExpandMaxDepth)
+ is_open = true;
+
+ return is_open;
+}
+
+bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ ImGuiContext& g = *GImGui;
+ const ImGuiStyle& style = g.Style;
+ const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0;
+ const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, 0.0f);
+
+ if (!label_end)
+ label_end = FindRenderedTextEnd(label);
+ const ImVec2 label_size = CalcTextSize(label, label_end, false);
+
+ // We vertically grow up to current line height up the typical widget height.
+ const float text_base_offset_y = ImMax(padding.y, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it
+ const float frame_height = ImMax(ImMin(window->DC.CurrentLineSize.y, g.FontSize + style.FramePadding.y*2), label_size.y + padding.y*2);
+ ImRect frame_bb = ImRect(window->DC.CursorPos, ImVec2(window->Pos.x + GetContentRegionMax().x, window->DC.CursorPos.y + frame_height));
+ if (display_frame)
+ {
+ // Framed header expand a little outside the default padding
+ frame_bb.Min.x -= (float)(int)(window->WindowPadding.x*0.5f) - 1;
+ frame_bb.Max.x += (float)(int)(window->WindowPadding.x*0.5f) - 1;
+ }
+
+ const float text_offset_x = (g.FontSize + (display_frame ? padding.x*3 : padding.x*2)); // Collapser arrow width + Spacing
+ const float text_width = g.FontSize + (label_size.x > 0.0f ? label_size.x + padding.x*2 : 0.0f); // Include collapser
+ ItemSize(ImVec2(text_width, frame_height), text_base_offset_y);
+
+ // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing
+ // (Ideally we'd want to add a flag for the user to specify if we want the hit test to be done up to the right side of the content or not)
+ const ImRect interact_bb = display_frame ? frame_bb : ImRect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + text_width + style.ItemSpacing.x*2, frame_bb.Max.y);
+ bool is_open = TreeNodeBehaviorIsOpen(id, flags);
+
+ // Store a flag for the current depth to tell if we will allow closing this node when navigating one of its child.
+ // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
+ // This is currently only support 32 level deep and we are fine with (1 << Depth) overflowing into a zero.
+ if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
+ window->DC.TreeDepthMayJumpToParentOnPop |= (1 << window->DC.TreeDepth);
+
+ bool item_add = ItemAdd(interact_bb, id);
+ window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HasDisplayRect;
+ window->DC.LastItemDisplayRect = frame_bb;
+
+ if (!item_add)
+ {
+ if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
+ TreePushRawID(id);
+ return is_open;
+ }
+
+ // Flags that affects opening behavior:
+ // - 0(default) ..................... single-click anywhere to open
+ // - OpenOnDoubleClick .............. double-click anywhere to open
+ // - OpenOnArrow .................... single-click on arrow to open
+ // - OpenOnDoubleClick|OpenOnArrow .. single-click on arrow or double-click anywhere to open
+ ImGuiButtonFlags button_flags = ImGuiButtonFlags_NoKeyModifiers | ((flags & ImGuiTreeNodeFlags_AllowItemOverlap) ? ImGuiButtonFlags_AllowItemOverlap : 0);
+ if (!(flags & ImGuiTreeNodeFlags_Leaf))
+ button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
+ if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
+ button_flags |= ImGuiButtonFlags_PressedOnDoubleClick | ((flags & ImGuiTreeNodeFlags_OpenOnArrow) ? ImGuiButtonFlags_PressedOnClickRelease : 0);
+
+ bool hovered, held, pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags);
+ if (!(flags & ImGuiTreeNodeFlags_Leaf))
+ {
+ bool toggled = false;
+ if (pressed)
+ {
+ toggled = !(flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) || (g.NavActivateId == id);
+ if (flags & ImGuiTreeNodeFlags_OpenOnArrow)
+ toggled |= IsMouseHoveringRect(interact_bb.Min, ImVec2(interact_bb.Min.x + text_offset_x, interact_bb.Max.y)) && (!g.NavDisableMouseHover);
+ if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
+ toggled |= g.IO.MouseDoubleClicked[0];
+ if (g.DragDropActive && is_open) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again.
+ toggled = false;
+ }
+
+ if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Left && is_open)
+ {
+ toggled = true;
+ NavMoveRequestCancel();
+ }
+ if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right && !is_open) // If there's something upcoming on the line we may want to give it the priority?
+ {
+ toggled = true;
+ NavMoveRequestCancel();
+ }
+
+ if (toggled)
+ {
+ is_open = !is_open;
+ window->DC.StateStorage->SetInt(id, is_open);
+ }
+ }
+ if (flags & ImGuiTreeNodeFlags_AllowItemOverlap)
+ SetItemAllowOverlap();
+
+ // Render
+ const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
+ const ImVec2 text_pos = frame_bb.Min + ImVec2(text_offset_x, text_base_offset_y);
+ if (display_frame)
+ {
+ // Framed type
+ RenderFrame(frame_bb.Min, frame_bb.Max, col, true, style.FrameRounding);
+ RenderNavHighlight(frame_bb, id, ImGuiNavHighlightFlags_TypeThin);
+ RenderArrow(frame_bb.Min + ImVec2(padding.x, text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 1.0f);
+ if (g.LogEnabled)
+ {
+ // NB: '##' is normally used to hide text (as a library-wide feature), so we need to specify the text range to make sure the ## aren't stripped out here.
+ const char log_prefix[] = "\n##";
+ const char log_suffix[] = "##";
+ LogRenderedText(&text_pos, log_prefix, log_prefix+3);
+ RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
+ LogRenderedText(&text_pos, log_suffix+1, log_suffix+3);
+ }
+ else
+ {
+ RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
+ }
+ }
+ else
+ {
+ // Unframed typed for tree nodes
+ if (hovered || (flags & ImGuiTreeNodeFlags_Selected))
+ {
+ RenderFrame(frame_bb.Min, frame_bb.Max, col, false);
+ RenderNavHighlight(frame_bb, id, ImGuiNavHighlightFlags_TypeThin);
+ }
+
+ if (flags & ImGuiTreeNodeFlags_Bullet)
+ RenderBullet(frame_bb.Min + ImVec2(text_offset_x * 0.5f, g.FontSize*0.50f + text_base_offset_y));
+ else if (!(flags & ImGuiTreeNodeFlags_Leaf))
+ RenderArrow(frame_bb.Min + ImVec2(padding.x, g.FontSize*0.15f + text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 0.70f);
+ if (g.LogEnabled)
+ LogRenderedText(&text_pos, ">");
+ RenderText(text_pos, label, label_end, false);
+ }
+
+ if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
+ TreePushRawID(id);
+ return is_open;
+}
+
+void ImGui::TreePush(const char* str_id)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ Indent();
+ window->DC.TreeDepth++;
+ PushID(str_id ? str_id : "#TreePush");
+}
+
+void ImGui::TreePush(const void* ptr_id)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ Indent();
+ window->DC.TreeDepth++;
+ PushID(ptr_id ? ptr_id : (const void*)"#TreePush");
+}
+
+void ImGui::TreePushRawID(ImGuiID id)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ Indent();
+ window->DC.TreeDepth++;
+ window->IDStack.push_back(id);
+}
+
+void ImGui::TreePop()
+{
+ ImGuiContext& g = *GImGui;
+ ImGuiWindow* window = g.CurrentWindow;
+ Unindent();
+
+ window->DC.TreeDepth--;
+ if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
+ if (g.NavIdIsAlive && (window->DC.TreeDepthMayJumpToParentOnPop & (1 << window->DC.TreeDepth)))
+ {
+ SetNavID(window->IDStack.back(), g.NavLayer);
+ NavMoveRequestCancel();
+ }
+ window->DC.TreeDepthMayJumpToParentOnPop &= (1 << window->DC.TreeDepth) - 1;
+
+ IM_ASSERT(window->IDStack.Size > 1); // There should always be 1 element in the IDStack (pushed during window creation). If this triggers you called TreePop/PopID too much.
+ PopID();
+}
+
+void ImGui::TreeAdvanceToLabelPos()
+{
+ ImGuiContext& g = *GImGui;
+ g.CurrentWindow->DC.CursorPos.x += GetTreeNodeToLabelSpacing();
+}
+
+// Horizontal distance preceding label when using TreeNode() or Bullet()
+float ImGui::GetTreeNodeToLabelSpacing()
+{
+ ImGuiContext& g = *GImGui;
+ return g.FontSize + (g.Style.FramePadding.x * 2.0f);
+}
+
+void ImGui::SetNextTreeNodeOpen(bool is_open, ImGuiCond cond)
+{
+ ImGuiContext& g = *GImGui;
+ if (g.CurrentWindow->SkipItems)
+ return;
+ g.NextTreeNodeOpenVal = is_open;
+ g.NextTreeNodeOpenCond = cond ? cond : ImGuiCond_Always;
+}
+
+// CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag).
+// This is basically the same as calling TreeNodeEx(label, ImGuiTreeNodeFlags_CollapsingHeader). You can remove the _NoTreePushOnOpen flag if you want behavior closer to normal TreeNode().
+bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ return TreeNodeBehavior(window->GetID(label), flags | ImGuiTreeNodeFlags_CollapsingHeader, label);
+}
+
+bool ImGui::CollapsingHeader(const char* label, bool* p_open, ImGuiTreeNodeFlags flags)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ if (p_open && !*p_open)
+ return false;
+
+ ImGuiID id = window->GetID(label);
+ bool is_open = TreeNodeBehavior(id, flags | ImGuiTreeNodeFlags_CollapsingHeader | (p_open ? ImGuiTreeNodeFlags_AllowItemOverlap : 0), label);
+ if (p_open)
+ {
+ // Create a small overlapping close button // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc.
+ ImGuiContext& g = *GImGui;
+ ImGuiItemHoveredDataBackup last_item_backup;
+ float button_radius = g.FontSize * 0.5f;
+ ImVec2 button_center = ImVec2(ImMin(window->DC.LastItemRect.Max.x, window->ClipRect.Max.x) - g.Style.FramePadding.x - button_radius, window->DC.LastItemRect.GetCenter().y);
+ if (CloseButton(window->GetID((void*)(intptr_t)(id+1)), button_center, button_radius))
+ *p_open = false;
+ last_item_backup.Restore();
+ }
+
+ return is_open;
+}
+
+//-------------------------------------------------------------------------
+// WIDGETS: Selectables
+// - Selectable()
+//-------------------------------------------------------------------------
+
+// Tip: pass an empty label (e.g. "##dummy") then you can use the space to draw other text or image.
+// But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID or use ##unique_id.
+bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ ImGuiContext& g = *GImGui;
+ const ImGuiStyle& style = g.Style;
+
+ if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet) // FIXME-OPT: Avoid if vertically clipped.
+ PopClipRect();
+
+ ImGuiID id = window->GetID(label);
+ ImVec2 label_size = CalcTextSize(label, NULL, true);
+ ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y);
+ ImVec2 pos = window->DC.CursorPos;
+ pos.y += window->DC.CurrentLineTextBaseOffset;
+ ImRect bb_inner(pos, pos + size);
+ ItemSize(bb_inner);
+
+ // Fill horizontal space.
+ ImVec2 window_padding = window->WindowPadding;
+ float max_x = (flags & ImGuiSelectableFlags_SpanAllColumns) ? GetWindowContentRegionMax().x : GetContentRegionMax().x;
+ float w_draw = ImMax(label_size.x, window->Pos.x + max_x - window_padding.x - window->DC.CursorPos.x);
+ ImVec2 size_draw((size_arg.x != 0 && !(flags & ImGuiSelectableFlags_DrawFillAvailWidth)) ? size_arg.x : w_draw, size_arg.y != 0.0f ? size_arg.y : size.y);
+ ImRect bb(pos, pos + size_draw);
+ if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_DrawFillAvailWidth))
+ bb.Max.x += window_padding.x;
+
+ // Selectables are tightly packed together, we extend the box to cover spacing between selectable.
+ float spacing_L = (float)(int)(style.ItemSpacing.x * 0.5f);
+ float spacing_U = (float)(int)(style.ItemSpacing.y * 0.5f);
+ float spacing_R = style.ItemSpacing.x - spacing_L;
+ float spacing_D = style.ItemSpacing.y - spacing_U;
+ bb.Min.x -= spacing_L;
+ bb.Min.y -= spacing_U;
+ bb.Max.x += spacing_R;
+ bb.Max.y += spacing_D;
+ if (!ItemAdd(bb, (flags & ImGuiSelectableFlags_Disabled) ? 0 : id))
+ {
+ if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet)
+ PushColumnClipRect();
+ return false;
+ }
+
+ // 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;
+ if (flags & ImGuiSelectableFlags_PressedOnClick) button_flags |= ImGuiButtonFlags_PressedOnClick;
+ if (flags & ImGuiSelectableFlags_PressedOnRelease) button_flags |= ImGuiButtonFlags_PressedOnRelease;
+ if (flags & ImGuiSelectableFlags_Disabled) button_flags |= ImGuiButtonFlags_Disabled;
+ if (flags & ImGuiSelectableFlags_AllowDoubleClick) button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick;
+ bool hovered, held;
+ bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
+ if (flags & ImGuiSelectableFlags_Disabled)
+ selected = false;
+
+ // Hovering selectable with mouse updates NavId accordingly so navigation can be resumed with gamepad/keyboard (this doesn't happen on most widgets)
+ if (pressed || hovered)
+ if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent)
+ {
+ g.NavDisableHighlight = true;
+ SetNavID(id, window->DC.NavLayerCurrent);
+ }
+ if (pressed)
+ MarkItemEdited(id);
+
+ // Render
+ if (hovered || selected)
+ {
+ const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
+ RenderFrame(bb.Min, bb.Max, col, false, 0.0f);
+ RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding);
+ }
+
+ if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet)
+ {
+ PushColumnClipRect();
+ bb.Max.x -= (GetContentRegionMax().x - max_x);
+ }
+
+ if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
+ RenderTextClipped(bb_inner.Min, bb.Max, label, NULL, &label_size, ImVec2(0.0f,0.0f));
+ if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor();
+
+ // Automatically close popups
+ if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(window->DC.ItemFlags & ImGuiItemFlags_SelectableDontClosePopup))
+ CloseCurrentPopup();
+ return pressed;
+}
+
+bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
+{
+ if (Selectable(label, *p_selected, flags, size_arg))
+ {
+ *p_selected = !*p_selected;
+ return true;
+ }
+ return false;
+}
+
+//-------------------------------------------------------------------------
+// WIDGETS: List Box
+// - ListBox()
+// - ListBoxHeader()
+// - ListBoxFooter()
+//-------------------------------------------------------------------------
+
+// FIXME: Rename to BeginListBox()
+// Helper to calculate the size of a listbox and display a label on the right.
+// Tip: To have a list filling the entire window width, PushItemWidth(-1) and pass an empty label "##empty"
+bool ImGui::ListBoxHeader(const char* label, const ImVec2& size_arg)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ const ImGuiStyle& style = GetStyle();
+ const ImGuiID id = GetID(label);
+ const ImVec2 label_size = CalcTextSize(label, NULL, true);
+
+ // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
+ ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.4f + style.ItemSpacing.y);
+ ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y));
+ ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
+ ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
+ window->DC.LastItemRect = bb; // Forward storage for ListBoxFooter.. dodgy.
+
+ BeginGroup();
+ if (label_size.x > 0)
+ RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
+
+ BeginChildFrame(id, frame_bb.GetSize());
+ return true;
+}
+
+// FIXME: Rename to BeginListBox()
+bool ImGui::ListBoxHeader(const char* label, int items_count, int height_in_items)
+{
+ // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
+ // We don't add +0.40f if items_count <= height_in_items. It is slightly dodgy, because it means a dynamic list of items will make the widget resize occasionally when it crosses that size.
+ // I am expecting that someone will come and complain about this behavior in a remote future, then we can advise on a better solution.
+ if (height_in_items < 0)
+ height_in_items = ImMin(items_count, 7);
+ float height_in_items_f = height_in_items < items_count ? (height_in_items + 0.40f) : (height_in_items + 0.00f);
+
+ // We include ItemSpacing.y so that a list sized for the exact number of items doesn't make a scrollbar appears. We could also enforce that by passing a flag to BeginChild().
+ ImVec2 size;
+ size.x = 0.0f;
+ size.y = GetTextLineHeightWithSpacing() * height_in_items_f + GetStyle().ItemSpacing.y;
+ return ListBoxHeader(label, size);
+}
+
+// FIXME: Rename to EndListBox()
+void ImGui::ListBoxFooter()
+{
+ ImGuiWindow* parent_window = GetCurrentWindow()->ParentWindow;
+ const ImRect bb = parent_window->DC.LastItemRect;
+ const ImGuiStyle& style = GetStyle();
+
+ EndChildFrame();
+
+ // Redeclare item size so that it includes the label (we have stored the full size in LastItemRect)
+ // We call SameLine() to restore DC.CurrentLine* data
+ SameLine();
+ parent_window->DC.CursorPos = bb.Min;
+ ItemSize(bb, style.FramePadding.y);
+ EndGroup();
+}
+
+bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items)
+{
+ const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items);
+ return value_changed;
+}
+
+bool ImGui::ListBox(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int height_in_items)
+{
+ if (!ListBoxHeader(label, items_count, height_in_items))
+ return false;
+
+ // Assume all items have even height (= 1 line of text). If you need items of different or variable sizes you can create a custom version of ListBox() in your code without using the clipper.
+ ImGuiContext& g = *GImGui;
+ bool value_changed = false;
+ ImGuiListClipper clipper(items_count, GetTextLineHeightWithSpacing()); // We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to.
+ while (clipper.Step())
+ for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
+ {
+ const bool item_selected = (i == *current_item);
+ const char* item_text;
+ if (!items_getter(data, i, &item_text))
+ item_text = "*Unknown item*";
+
+ PushID(i);
+ if (Selectable(item_text, item_selected))
+ {
+ *current_item = i;
+ value_changed = true;
+ }
+ if (item_selected)
+ SetItemDefaultFocus();
+ PopID();
+ }
+ ListBoxFooter();
+ if (value_changed)
+ MarkItemEdited(g.CurrentWindow->DC.LastItemId);
+
+ return value_changed;
+}
+
+//-------------------------------------------------------------------------
+// WIDGETS: Data Plotting
+// - PlotEx() [Internal]
+// - PlotLines()
+// - PlotHistogram()
+//-------------------------------------------------------------------------
+
+void ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return;
+
+ ImGuiContext& g = *GImGui;
+ const ImGuiStyle& style = g.Style;
+
+ const ImVec2 label_size = CalcTextSize(label, NULL, true);
+ if (graph_size.x == 0.0f)
+ graph_size.x = CalcItemWidth();
+ if (graph_size.y == 0.0f)
+ graph_size.y = label_size.y + (style.FramePadding.y * 2);
+
+ const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(graph_size.x, graph_size.y));
+ const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
+ const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0));
+ ItemSize(total_bb, style.FramePadding.y);
+ if (!ItemAdd(total_bb, 0, &frame_bb))
+ return;
+ const bool hovered = ItemHoverable(inner_bb, 0);
+
+ // Determine scale from values if not specified
+ if (scale_min == FLT_MAX || scale_max == FLT_MAX)
+ {
+ float v_min = FLT_MAX;
+ float v_max = -FLT_MAX;
+ for (int i = 0; i < values_count; i++)
+ {
+ const float v = values_getter(data, i);
+ v_min = ImMin(v_min, v);
+ v_max = ImMax(v_max, v);
+ }
+ if (scale_min == FLT_MAX)
+ scale_min = v_min;
+ if (scale_max == FLT_MAX)
+ scale_max = v_max;
+ }
+
+ RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
+
+ if (values_count > 0)
+ {
+ int res_w = ImMin((int)graph_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
+ int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
+
+ // Tooltip on hover
+ int v_hovered = -1;
+ if (hovered)
+ {
+ const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f);
+ const int v_idx = (int)(t * item_count);
+ IM_ASSERT(v_idx >= 0 && v_idx < values_count);
+
+ const float v0 = values_getter(data, (v_idx + values_offset) % values_count);
+ const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count);
+ if (plot_type == ImGuiPlotType_Lines)
+ SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx+1, v1);
+ else if (plot_type == ImGuiPlotType_Histogram)
+ SetTooltip("%d: %8.4g", v_idx, v0);
+ v_hovered = v_idx;
+ }
+
+ const float t_step = 1.0f / (float)res_w;
+ const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min));
+
+ float v0 = values_getter(data, (0 + values_offset) % values_count);
+ float t0 = 0.0f;
+ ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) ); // Point in the normalized space of our target rectangle
+ float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (-scale_min * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f); // Where does the zero line stands
+
+ const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram);
+ const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered);
+
+ for (int n = 0; n < res_w; n++)
+ {
+ const float t1 = t0 + t_step;
+ const int v1_idx = (int)(t0 * item_count + 0.5f);
+ IM_ASSERT(v1_idx >= 0 && v1_idx < values_count);
+ const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count);
+ const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) );
+
+ // NB: Draw calls are merged together by the DrawList system. Still, we should render our batch are lower level to save a bit of CPU.
+ ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0);
+ ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t));
+ if (plot_type == ImGuiPlotType_Lines)
+ {
+ window->DrawList->AddLine(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);
+ }
+ else if (plot_type == ImGuiPlotType_Histogram)
+ {
+ if (pos1.x >= pos0.x + 2.0f)
+ pos1.x -= 1.0f;
+ window->DrawList->AddRectFilled(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);
+ }
+
+ t0 = t1;
+ tp0 = tp1;
+ }
+ }
+
+ // Text overlay
+ if (overlay_text)
+ RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, overlay_text, NULL, NULL, ImVec2(0.5f,0.0f));
+
+ if (label_size.x > 0.0f)
+ RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
+}
+
+struct ImGuiPlotArrayGetterData
+{
+ const float* Values;
+ int Stride;
+
+ ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; }
+};
+
+static float Plot_ArrayGetter(void* data, int idx)
+{
+ ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data;
+ const float v = *(const float*)(const void*)((const unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride);
+ return v;
+}
+
+void ImGui::PlotLines(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride)
+{
+ ImGuiPlotArrayGetterData data(values, stride);
+ PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
+}
+
+void ImGui::PlotLines(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size)
+{
+ PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
+}
+
+void ImGui::PlotHistogram(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride)
+{
+ ImGuiPlotArrayGetterData data(values, stride);
+ PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
+}
+
+void ImGui::PlotHistogram(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size)
+{
+ PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
+}
+
+//-------------------------------------------------------------------------
+// WIDGETS: Value() helpers
+// - Value()
+//-------------------------------------------------------------------------
+
+void ImGui::Value(const char* prefix, bool b)
+{
+ Text("%s: %s", prefix, (b ? "true" : "false"));
+}
+
+void ImGui::Value(const char* prefix, int v)
+{
+ Text("%s: %d", prefix, v);
+}
+
+void ImGui::Value(const char* prefix, unsigned int v)
+{
+ Text("%s: %d", prefix, v);
+}
+
+void ImGui::Value(const char* prefix, float v, const char* float_format)
+{
+ if (float_format)
+ {
+ char fmt[64];
+ ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format);
+ Text(fmt, prefix, v);
+ }
+ else
+ {
+ Text("%s: %.3f", prefix, v);
+ }
+}
+
+//-------------------------------------------------------------------------
+// WIDGETS: Menus
+// - ImGuiMenuColumns
+// - BeginMainMenuBar()
+// - EndMainMenuBar()
+// - BeginMenuBar()
+// - EndMenuBar()
+// - BeginMenu()
+// - EndMenu()
+// - MenuItem()
+//-------------------------------------------------------------------------
+
+// Helpers for internal use
+ImGuiMenuColumns::ImGuiMenuColumns()
+{
+ Count = 0;
+ 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));
+ Count = count;
+ Width = NextWidth = 0.0f;
+ Spacing = spacing;
+ if (clear) memset(NextWidths, 0, sizeof(NextWidths));
+ for (int i = 0; i < Count; i++)
+ {
+ if (i > 0 && NextWidths[i] > 0.0f)
+ Width += Spacing;
+ Pos[i] = (float)(int)Width;
+ Width += NextWidths[i];
+ NextWidths[i] = 0.0f;
+ }
+}
+
+float ImGuiMenuColumns::DeclColumns(float w0, float w1, float w2) // not using va_arg because they promote float to double
+{
+ NextWidth = 0.0f;
+ NextWidths[0] = ImMax(NextWidths[0], w0);
+ NextWidths[1] = ImMax(NextWidths[1], w1);
+ NextWidths[2] = ImMax(NextWidths[2], w2);
+ for (int i = 0; i < 3; i++)
+ NextWidth += NextWidths[i] + ((i > 0 && NextWidths[i] > 0.0f) ? Spacing : 0.0f);
+ return ImMax(Width, NextWidth);
+}
+
+float ImGuiMenuColumns::CalcExtraSpace(float avail_w)
+{
+ return ImMax(0.0f, avail_w - Width);
+}
+
+// For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.
+bool ImGui::BeginMainMenuBar()
+{
+ ImGuiContext& g = *GImGui;
+ g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f));
+ SetNextWindowPos(ImVec2(0.0f, 0.0f));
+ SetNextWindowSize(ImVec2(g.IO.DisplaySize.x, g.NextWindowData.MenuBarOffsetMinVal.y + g.FontBaseSize + g.Style.FramePadding.y));
+ PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
+ PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0,0));
+ ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;
+ bool is_open = Begin("##MainMenuBar", NULL, window_flags) && BeginMenuBar();
+ PopStyleVar(2);
+ g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f);
+ if (!is_open)
+ {
+ End();
+ return false;
+ }
+ return true;
+}
+
+void ImGui::EndMainMenuBar()
+{
+ EndMenuBar();
+
+ // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window
+ ImGuiContext& g = *GImGui;
+ if (g.CurrentWindow == g.NavWindow && g.NavLayer == 0)
+ FocusFrontMostActiveWindowIgnoringOne(g.NavWindow);
+
+ End();
+}
+
+bool ImGui::BeginMenuBar()
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+ if (!(window->Flags & ImGuiWindowFlags_MenuBar))
+ return false;
+
+ IM_ASSERT(!window->DC.MenuBarAppending);
+ BeginGroup(); // Backup position on layer 0
+ PushID("##menubar");
+
+ // We don't clip with current window clipping rectangle as it is already set to the area below. However we clip with window full rect.
+ // We remove 1 worth of rounding to Max.x to that text in long menus and small windows don't tend to display over the lower-right rounded area, which looks particularly glitchy.
+ ImRect bar_rect = window->MenuBarRect();
+ ImRect clip_rect(ImFloor(bar_rect.Min.x + 0.5f), ImFloor(bar_rect.Min.y + window->WindowBorderSize + 0.5f), ImFloor(ImMax(bar_rect.Min.x, bar_rect.Max.x - window->WindowRounding) + 0.5f), ImFloor(bar_rect.Max.y + 0.5f));
+ clip_rect.ClipWith(window->OuterRectClipped);
+ PushClipRect(clip_rect.Min, clip_rect.Max, false);
+
+ window->DC.CursorPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y);
+ window->DC.LayoutType = ImGuiLayoutType_Horizontal;
+ window->DC.NavLayerCurrent++;
+ window->DC.NavLayerCurrentMask <<= 1;
+ window->DC.MenuBarAppending = true;
+ AlignTextToFramePadding();
+ return true;
+}
+
+void ImGui::EndMenuBar()
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return;
+ ImGuiContext& g = *GImGui;
+
+ // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings.
+ if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
+ {
+ ImGuiWindow* nav_earliest_child = g.NavWindow;
+ while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu))
+ nav_earliest_child = nav_earliest_child->ParentWindow;
+ if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && g.NavMoveRequestForward == ImGuiNavForward_None)
+ {
+ // To do so we claim focus back, restore NavId and then process the movement request for yet another frame.
+ // This involve a one-frame delay which isn't very problematic in this situation. We could remove it by scoring in advance for multiple window (probably not worth the hassle/cost)
+ IM_ASSERT(window->DC.NavLayerActiveMaskNext & 0x02); // Sanity check
+ FocusWindow(window);
+ SetNavIDWithRectRel(window->NavLastIds[1], 1, window->NavRectRel[1]);
+ g.NavLayer = 1;
+ g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection.
+ g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued;
+ NavMoveRequestCancel();
+ }
+ }
+
+ IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar);
+ IM_ASSERT(window->DC.MenuBarAppending);
+ 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().AdvanceCursor = false;
+ EndGroup(); // Restore position on layer 0
+ window->DC.LayoutType = ImGuiLayoutType_Vertical;
+ window->DC.NavLayerCurrent--;
+ window->DC.NavLayerCurrentMask >>= 1;
+ window->DC.MenuBarAppending = false;
+}
+
+bool ImGui::BeginMenu(const char* label, bool enabled)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ ImGuiContext& g = *GImGui;
+ const ImGuiStyle& style = g.Style;
+ const ImGuiID id = window->GetID(label);
+
+ ImVec2 label_size = CalcTextSize(label, NULL, true);
+
+ bool pressed;
+ bool menu_is_open = IsPopupOpen(id);
+ bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].OpenParentId == window->IDStack.back());
+ ImGuiWindow* backed_nav_window = g.NavWindow;
+ if (menuset_is_open)
+ g.NavWindow = window; // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent)
+
+ // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu (using FindBestWindowPosForPopup).
+ ImVec2 popup_pos, pos = window->DC.CursorPos;
+ if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
+ {
+ // Menu inside an horizontal menu bar
+ // Selectable extend their highlight by half ItemSpacing in each direction.
+ // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin()
+ popup_pos = ImVec2(pos.x - window->WindowPadding.x, pos.y - style.FramePadding.y + window->MenuBarHeight());
+ window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f);
+ PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f);
+ float w = label_size.x;
+ pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));
+ PopStyleVar();
+ window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
+ }
+ else
+ {
+ // Menu inside a menu
+ popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y);
+ float w = window->MenuColumns.DeclColumns(label_size.x, 0.0f, (float)(int)(g.FontSize * 1.20f)); // Feedback to next frame
+ float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w);
+ pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_DrawFillAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));
+ if (!enabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
+ RenderArrow(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.30f, 0.0f), ImGuiDir_Right);
+ if (!enabled) PopStyleColor();
+ }
+
+ const bool hovered = enabled && ItemHoverable(window->DC.LastItemRect, id);
+ if (menuset_is_open)
+ g.NavWindow = backed_nav_window;
+
+ bool want_open = false, want_close = false;
+ if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
+ {
+ // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive.
+ bool moving_within_opened_triangle = false;
+ if (g.HoveredWindow == window && g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].ParentWindow == window && !(window->Flags & ImGuiWindowFlags_MenuBar))
+ {
+ if (ImGuiWindow* next_window = g.OpenPopupStack[g.CurrentPopupStack.Size].Window)
+ {
+ ImRect next_window_rect = next_window->Rect();
+ ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta;
+ ImVec2 tb = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR();
+ ImVec2 tc = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR();
+ float extra = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack.
+ ta.x += (window->Pos.x < next_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues
+ tb.y = ta.y + ImMax((tb.y - extra) - ta.y, -100.0f); // triangle is maximum 200 high to limit the slope and the bias toward large sub-menus // FIXME: Multiply by fb_scale?
+ tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f);
+ moving_within_opened_triangle = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos);
+ //window->DrawList->PushClipRectFullScreen(); window->DrawList->AddTriangleFilled(ta, tb, tc, moving_within_opened_triangle ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); window->DrawList->PopClipRect(); // Debug
+ }
+ }
+
+ want_close = (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_within_opened_triangle);
+ want_open = (!menu_is_open && hovered && !moving_within_opened_triangle) || (!menu_is_open && hovered && pressed);
+
+ if (g.NavActivateId == id)
+ {
+ want_close = menu_is_open;
+ want_open = !menu_is_open;
+ }
+ if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open
+ {
+ want_open = true;
+ NavMoveRequestCancel();
+ }
+ }
+ else
+ {
+ // Menu bar
+ if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it
+ {
+ want_close = true;
+ want_open = menu_is_open = false;
+ }
+ else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others
+ {
+ want_open = true;
+ }
+ else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open
+ {
+ want_open = true;
+ NavMoveRequestCancel();
+ }
+ }
+
+ if (!enabled) // explicitly close if an open menu becomes disabled, facilitate users code a lot in pattern such as 'if (BeginMenu("options", has_object)) { ..use object.. }'
+ want_close = true;
+ if (want_close && IsPopupOpen(id))
+ ClosePopupToLevel(g.CurrentPopupStack.Size);
+
+ if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.CurrentPopupStack.Size)
+ {
+ // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame.
+ OpenPopup(label);
+ return false;
+ }
+
+ menu_is_open |= want_open;
+ if (want_open)
+ OpenPopup(label);
+
+ if (menu_is_open)
+ {
+ // Sub-menus are ChildWindow so that mouse can be hovering across them (otherwise top-most popup menu would steal focus and not allow hovering on parent menu)
+ SetNextWindowPos(popup_pos, ImGuiCond_Always);
+ ImGuiWindowFlags flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus;
+ if (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
+ flags |= ImGuiWindowFlags_ChildWindow;
+ menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
+ }
+
+ return menu_is_open;
+}
+
+void ImGui::EndMenu()
+{
+ // Nav: When a left move request _within our child menu_ failed, close the menu.
+ // A menu doesn't close itself because EndMenuBar() wants the catch the last Left<>Right inputs.
+ // However, it means that with the current code, a BeginMenu() from outside another menu or a menu-bar won't be closable with the Left direction.
+ ImGuiContext& g = *GImGui;
+ ImGuiWindow* window = g.CurrentWindow;
+ if (g.NavWindow && g.NavWindow->ParentWindow == window && g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet() && window->DC.LayoutType == ImGuiLayoutType_Vertical)
+ {
+ ClosePopupToLevel(g.OpenPopupStack.Size - 1);
+ NavMoveRequestCancel();
+ }
+
+ EndPopup();
+}
+
+bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems)
+ return false;
+
+ ImGuiContext& g = *GImGui;
+ ImGuiStyle& style = g.Style;
+ ImVec2 pos = window->DC.CursorPos;
+ ImVec2 label_size = CalcTextSize(label, NULL, true);
+
+ ImGuiSelectableFlags flags = ImGuiSelectableFlags_PressedOnRelease | (enabled ? 0 : ImGuiSelectableFlags_Disabled);
+ bool pressed;
+ if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
+ {
+ // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful
+ // Note that in this situation we render neither the shortcut neither the selected tick mark
+ float w = label_size.x;
+ window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f);
+ PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f);
+ pressed = Selectable(label, false, flags, ImVec2(w, 0.0f));
+ PopStyleVar();
+ window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
+ }
+ else
+ {
+ ImVec2 shortcut_size = shortcut ? CalcTextSize(shortcut, NULL) : ImVec2(0.0f, 0.0f);
+ float w = window->MenuColumns.DeclColumns(label_size.x, shortcut_size.x, (float)(int)(g.FontSize * 1.20f)); // Feedback for next frame
+ float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w);
+ pressed = Selectable(label, false, flags | ImGuiSelectableFlags_DrawFillAvailWidth, ImVec2(w, 0.0f));
+ if (shortcut_size.x > 0.0f)
+ {
+ PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
+ RenderText(pos + ImVec2(window->MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false);
+ PopStyleColor();
+ }
+ if (selected)
+ RenderCheckMark(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), g.FontSize * 0.866f);
+ }
+ return pressed;
+}
+
+bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled)
+{
+ if (MenuItem(label, shortcut, p_selected ? *p_selected : false, enabled))
+ {
+ if (p_selected)
+ *p_selected = !*p_selected;
+ return true;
+ }
+ return false;
+}
+