1 | // dear imgui, v1.90.5 |
2 | // (widgets code) |
3 | |
4 | /* |
5 | |
6 | Index of this file: |
7 | |
8 | // [SECTION] Forward Declarations |
9 | // [SECTION] Widgets: Text, etc. |
10 | // [SECTION] Widgets: Main (Button, Image, Checkbox, RadioButton, ProgressBar, Bullet, etc.) |
11 | // [SECTION] Widgets: Low-level Layout helpers (Spacing, Dummy, NewLine, Separator, etc.) |
12 | // [SECTION] Widgets: ComboBox |
13 | // [SECTION] Data Type and Data Formatting Helpers |
14 | // [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc. |
15 | // [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc. |
16 | // [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc. |
17 | // [SECTION] Widgets: InputText, InputTextMultiline |
18 | // [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc. |
19 | // [SECTION] Widgets: TreeNode, CollapsingHeader, etc. |
20 | // [SECTION] Widgets: Selectable |
21 | // [SECTION] Widgets: Typing-Select support |
22 | // [SECTION] Widgets: Multi-Select support |
23 | // [SECTION] Widgets: ListBox |
24 | // [SECTION] Widgets: PlotLines, PlotHistogram |
25 | // [SECTION] Widgets: Value helpers |
26 | // [SECTION] Widgets: MenuItem, BeginMenu, EndMenu, etc. |
27 | // [SECTION] Widgets: BeginTabBar, EndTabBar, etc. |
28 | // [SECTION] Widgets: BeginTabItem, EndTabItem, etc. |
29 | // [SECTION] Widgets: Columns, BeginColumns, EndColumns, etc. |
30 | |
31 | */ |
32 | |
33 | #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) |
34 | #define _CRT_SECURE_NO_WARNINGS |
35 | #endif |
36 | |
37 | #ifndef IMGUI_DEFINE_MATH_OPERATORS |
38 | #define IMGUI_DEFINE_MATH_OPERATORS |
39 | #endif |
40 | |
41 | #include "imgui.h" |
42 | #ifndef IMGUI_DISABLE |
43 | #include "imgui_internal.h" |
44 | |
45 | // System includes |
46 | #include <stdint.h> // intptr_t |
47 | |
48 | //------------------------------------------------------------------------- |
49 | // Warnings |
50 | //------------------------------------------------------------------------- |
51 | |
52 | // Visual Studio warnings |
53 | #ifdef _MSC_VER |
54 | #pragma warning (disable: 4127) // condition expression is constant |
55 | #pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen |
56 | #if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later |
57 | #pragma warning (disable: 5054) // operator '|': deprecated between enumerations of different types |
58 | #endif |
59 | #pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2). |
60 | #pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). |
61 | #endif |
62 | |
63 | // Clang/GCC warnings with -Weverything |
64 | #if defined(__clang__) |
65 | #if __has_warning("-Wunknown-warning-option") |
66 | #pragma clang diagnostic ignored "-Wunknown-warning-option" // warning: unknown warning group 'xxx' // not all warnings are known by all Clang versions and they tend to be rename-happy.. so ignoring warnings triggers new warnings on some configuration. Great! |
67 | #endif |
68 | #pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' |
69 | #pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse. |
70 | #pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok. |
71 | #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. |
72 | #pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness |
73 | #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some standard header variations use #define NULL 0 |
74 | #pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using printf() is a misery with this as C++ va_arg ellipsis changes float to double. |
75 | #pragma clang diagnostic ignored "-Wenum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') |
76 | #pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated |
77 | #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision |
78 | #elif defined(__GNUC__) |
79 | #pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind |
80 | #pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked |
81 | #pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead |
82 | #pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated |
83 | #endif |
84 | |
85 | //------------------------------------------------------------------------- |
86 | // Data |
87 | //------------------------------------------------------------------------- |
88 | |
89 | // Widgets |
90 | static const float DRAGDROP_HOLD_TO_OPEN_TIMER = 0.70f; // Time for drag-hold to activate items accepting the ImGuiButtonFlags_PressedOnDragDropHold button behavior. |
91 | static const float DRAG_MOUSE_THRESHOLD_FACTOR = 0.50f; // Multiplier for the default value of io.MouseDragThreshold to make DragFloat/DragInt react faster to mouse drags. |
92 | |
93 | // Those MIN/MAX values are not define because we need to point to them |
94 | static const signed char IM_S8_MIN = -128; |
95 | static const signed char IM_S8_MAX = 127; |
96 | static const unsigned char IM_U8_MIN = 0; |
97 | static const unsigned char IM_U8_MAX = 0xFF; |
98 | static const signed short IM_S16_MIN = -32768; |
99 | static const signed short IM_S16_MAX = 32767; |
100 | static const unsigned short IM_U16_MIN = 0; |
101 | static const unsigned short IM_U16_MAX = 0xFFFF; |
102 | static const ImS32 IM_S32_MIN = INT_MIN; // (-2147483647 - 1), (0x80000000); |
103 | static const ImS32 IM_S32_MAX = INT_MAX; // (2147483647), (0x7FFFFFFF) |
104 | static const ImU32 IM_U32_MIN = 0; |
105 | static const ImU32 IM_U32_MAX = UINT_MAX; // (0xFFFFFFFF) |
106 | #ifdef LLONG_MIN |
107 | static const ImS64 IM_S64_MIN = LLONG_MIN; // (-9223372036854775807ll - 1ll); |
108 | static const ImS64 IM_S64_MAX = LLONG_MAX; // (9223372036854775807ll); |
109 | #else |
110 | static const ImS64 IM_S64_MIN = -9223372036854775807LL - 1; |
111 | static const ImS64 IM_S64_MAX = 9223372036854775807LL; |
112 | #endif |
113 | static const ImU64 IM_U64_MIN = 0; |
114 | #ifdef ULLONG_MAX |
115 | static const ImU64 IM_U64_MAX = ULLONG_MAX; // (0xFFFFFFFFFFFFFFFFull); |
116 | #else |
117 | static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1); |
118 | #endif |
119 | |
120 | //------------------------------------------------------------------------- |
121 | // [SECTION] Forward Declarations |
122 | //------------------------------------------------------------------------- |
123 | |
124 | // For InputTextEx() |
125 | static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard = false); |
126 | static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end); |
127 | static ImVec2 InputTextCalcTextSizeW(ImGuiContext* ctx, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false); |
128 | |
129 | //------------------------------------------------------------------------- |
130 | // [SECTION] Widgets: Text, etc. |
131 | //------------------------------------------------------------------------- |
132 | // - TextEx() [Internal] |
133 | // - TextUnformatted() |
134 | // - Text() |
135 | // - TextV() |
136 | // - TextColored() |
137 | // - TextColoredV() |
138 | // - TextDisabled() |
139 | // - TextDisabledV() |
140 | // - TextWrapped() |
141 | // - TextWrappedV() |
142 | // - LabelText() |
143 | // - LabelTextV() |
144 | // - BulletText() |
145 | // - BulletTextV() |
146 | //------------------------------------------------------------------------- |
147 | |
148 | void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags) |
149 | { |
150 | ImGuiWindow* window = GetCurrentWindow(); |
151 | if (window->SkipItems) |
152 | return; |
153 | ImGuiContext& g = *GImGui; |
154 | |
155 | // Accept null ranges |
156 | if (text == text_end) |
157 | text = text_end = "" ; |
158 | |
159 | // Calculate length |
160 | const char* text_begin = text; |
161 | if (text_end == NULL) |
162 | text_end = text + strlen(s: text); // FIXME-OPT |
163 | |
164 | const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); |
165 | const float wrap_pos_x = window->DC.TextWrapPos; |
166 | const bool wrap_enabled = (wrap_pos_x >= 0.0f); |
167 | if (text_end - text <= 2000 || wrap_enabled) |
168 | { |
169 | // Common case |
170 | const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(pos: window->DC.CursorPos, wrap_pos_x) : 0.0f; |
171 | const ImVec2 text_size = CalcTextSize(text: text_begin, text_end, hide_text_after_double_hash: false, wrap_width); |
172 | |
173 | ImRect bb(text_pos, text_pos + text_size); |
174 | ItemSize(size: text_size, text_baseline_y: 0.0f); |
175 | if (!ItemAdd(bb, id: 0)) |
176 | return; |
177 | |
178 | // Render (we don't hide text after ## in this end-user function) |
179 | RenderTextWrapped(pos: bb.Min, text: text_begin, text_end, wrap_width); |
180 | } |
181 | else |
182 | { |
183 | // Long text! |
184 | // Perform manual coarse clipping to optimize for long multi-line text |
185 | // - From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled. |
186 | // - 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. |
187 | // - We use memchr(), pay attention that well optimized versions of those str/mem functions are much faster than a casually written loop. |
188 | const char* line = text; |
189 | const float line_height = GetTextLineHeight(); |
190 | ImVec2 text_size(0, 0); |
191 | |
192 | // Lines to skip (can't skip when logging text) |
193 | ImVec2 pos = text_pos; |
194 | if (!g.LogEnabled) |
195 | { |
196 | int lines_skippable = (int)((window->ClipRect.Min.y - text_pos.y) / line_height); |
197 | if (lines_skippable > 0) |
198 | { |
199 | int lines_skipped = 0; |
200 | while (line < text_end && lines_skipped < lines_skippable) |
201 | { |
202 | const char* line_end = (const char*)memchr(s: line, c: '\n', n: text_end - line); |
203 | if (!line_end) |
204 | line_end = text_end; |
205 | if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0) |
206 | text_size.x = ImMax(lhs: text_size.x, rhs: CalcTextSize(text: line, text_end: line_end).x); |
207 | line = line_end + 1; |
208 | lines_skipped++; |
209 | } |
210 | pos.y += lines_skipped * line_height; |
211 | } |
212 | } |
213 | |
214 | // Lines to render |
215 | if (line < text_end) |
216 | { |
217 | ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height)); |
218 | while (line < text_end) |
219 | { |
220 | if (IsClippedEx(bb: line_rect, id: 0)) |
221 | break; |
222 | |
223 | const char* line_end = (const char*)memchr(s: line, c: '\n', n: text_end - line); |
224 | if (!line_end) |
225 | line_end = text_end; |
226 | text_size.x = ImMax(lhs: text_size.x, rhs: CalcTextSize(text: line, text_end: line_end).x); |
227 | RenderText(pos, text: line, text_end: line_end, hide_text_after_hash: false); |
228 | line = line_end + 1; |
229 | line_rect.Min.y += line_height; |
230 | line_rect.Max.y += line_height; |
231 | pos.y += line_height; |
232 | } |
233 | |
234 | // Count remaining lines |
235 | int lines_skipped = 0; |
236 | while (line < text_end) |
237 | { |
238 | const char* line_end = (const char*)memchr(s: line, c: '\n', n: text_end - line); |
239 | if (!line_end) |
240 | line_end = text_end; |
241 | if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0) |
242 | text_size.x = ImMax(lhs: text_size.x, rhs: CalcTextSize(text: line, text_end: line_end).x); |
243 | line = line_end + 1; |
244 | lines_skipped++; |
245 | } |
246 | pos.y += lines_skipped * line_height; |
247 | } |
248 | text_size.y = (pos - text_pos).y; |
249 | |
250 | ImRect bb(text_pos, text_pos + text_size); |
251 | ItemSize(size: text_size, text_baseline_y: 0.0f); |
252 | ItemAdd(bb, id: 0); |
253 | } |
254 | } |
255 | |
256 | void ImGui::TextUnformatted(const char* text, const char* text_end) |
257 | { |
258 | TextEx(text, text_end, flags: ImGuiTextFlags_NoWidthForLargeClippedText); |
259 | } |
260 | |
261 | void ImGui::Text(const char* fmt, ...) |
262 | { |
263 | va_list args; |
264 | va_start(args, fmt); |
265 | TextV(fmt, args); |
266 | va_end(args); |
267 | } |
268 | |
269 | void ImGui::TextV(const char* fmt, va_list args) |
270 | { |
271 | ImGuiWindow* window = GetCurrentWindow(); |
272 | if (window->SkipItems) |
273 | return; |
274 | |
275 | const char* text, *text_end; |
276 | ImFormatStringToTempBufferV(out_buf: &text, out_buf_end: &text_end, fmt, args); |
277 | TextEx(text, text_end, flags: ImGuiTextFlags_NoWidthForLargeClippedText); |
278 | } |
279 | |
280 | void ImGui::TextColored(const ImVec4& col, const char* fmt, ...) |
281 | { |
282 | va_list args; |
283 | va_start(args, fmt); |
284 | TextColoredV(col, fmt, args); |
285 | va_end(args); |
286 | } |
287 | |
288 | void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args) |
289 | { |
290 | PushStyleColor(idx: ImGuiCol_Text, col); |
291 | TextV(fmt, args); |
292 | PopStyleColor(); |
293 | } |
294 | |
295 | void ImGui::TextDisabled(const char* fmt, ...) |
296 | { |
297 | va_list args; |
298 | va_start(args, fmt); |
299 | TextDisabledV(fmt, args); |
300 | va_end(args); |
301 | } |
302 | |
303 | void ImGui::TextDisabledV(const char* fmt, va_list args) |
304 | { |
305 | ImGuiContext& g = *GImGui; |
306 | PushStyleColor(idx: ImGuiCol_Text, col: g.Style.Colors[ImGuiCol_TextDisabled]); |
307 | TextV(fmt, args); |
308 | PopStyleColor(); |
309 | } |
310 | |
311 | void ImGui::TextWrapped(const char* fmt, ...) |
312 | { |
313 | va_list args; |
314 | va_start(args, fmt); |
315 | TextWrappedV(fmt, args); |
316 | va_end(args); |
317 | } |
318 | |
319 | void ImGui::TextWrappedV(const char* fmt, va_list args) |
320 | { |
321 | ImGuiContext& g = *GImGui; |
322 | const bool need_backup = (g.CurrentWindow->DC.TextWrapPos < 0.0f); // Keep existing wrap position if one is already set |
323 | if (need_backup) |
324 | PushTextWrapPos(wrap_local_pos_x: 0.0f); |
325 | TextV(fmt, args); |
326 | if (need_backup) |
327 | PopTextWrapPos(); |
328 | } |
329 | |
330 | void ImGui::LabelText(const char* label, const char* fmt, ...) |
331 | { |
332 | va_list args; |
333 | va_start(args, fmt); |
334 | LabelTextV(label, fmt, args); |
335 | va_end(args); |
336 | } |
337 | |
338 | // Add a label+text combo aligned to other label+value widgets |
339 | void ImGui::LabelTextV(const char* label, const char* fmt, va_list args) |
340 | { |
341 | ImGuiWindow* window = GetCurrentWindow(); |
342 | if (window->SkipItems) |
343 | return; |
344 | |
345 | ImGuiContext& g = *GImGui; |
346 | const ImGuiStyle& style = g.Style; |
347 | const float w = CalcItemWidth(); |
348 | |
349 | const char* value_text_begin, *value_text_end; |
350 | ImFormatStringToTempBufferV(out_buf: &value_text_begin, out_buf_end: &value_text_end, fmt, args); |
351 | const ImVec2 value_size = CalcTextSize(text: value_text_begin, text_end: value_text_end, hide_text_after_double_hash: false); |
352 | const ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: true); |
353 | |
354 | const ImVec2 pos = window->DC.CursorPos; |
355 | const ImRect value_bb(pos, pos + ImVec2(w, value_size.y + style.FramePadding.y * 2)); |
356 | const ImRect total_bb(pos, pos + ImVec2(w + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), ImMax(lhs: value_size.y, rhs: label_size.y) + style.FramePadding.y * 2)); |
357 | ItemSize(bb: total_bb, text_baseline_y: style.FramePadding.y); |
358 | if (!ItemAdd(bb: total_bb, id: 0)) |
359 | return; |
360 | |
361 | // Render |
362 | RenderTextClipped(pos_min: value_bb.Min + style.FramePadding, pos_max: value_bb.Max, text: value_text_begin, text_end: value_text_end, text_size_if_known: &value_size, align: ImVec2(0.0f, 0.0f)); |
363 | if (label_size.x > 0.0f) |
364 | RenderText(pos: ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), text: label); |
365 | } |
366 | |
367 | void ImGui::BulletText(const char* fmt, ...) |
368 | { |
369 | va_list args; |
370 | va_start(args, fmt); |
371 | BulletTextV(fmt, args); |
372 | va_end(args); |
373 | } |
374 | |
375 | // Text with a little bullet aligned to the typical tree node. |
376 | void ImGui::BulletTextV(const char* fmt, va_list args) |
377 | { |
378 | ImGuiWindow* window = GetCurrentWindow(); |
379 | if (window->SkipItems) |
380 | return; |
381 | |
382 | ImGuiContext& g = *GImGui; |
383 | const ImGuiStyle& style = g.Style; |
384 | |
385 | const char* text_begin, *text_end; |
386 | ImFormatStringToTempBufferV(out_buf: &text_begin, out_buf_end: &text_end, fmt, args); |
387 | const ImVec2 label_size = CalcTextSize(text: text_begin, text_end, hide_text_after_double_hash: false); |
388 | const ImVec2 total_size = ImVec2(g.FontSize + (label_size.x > 0.0f ? (label_size.x + style.FramePadding.x * 2) : 0.0f), label_size.y); // Empty text doesn't add padding |
389 | ImVec2 pos = window->DC.CursorPos; |
390 | pos.y += window->DC.CurrLineTextBaseOffset; |
391 | ItemSize(size: total_size, text_baseline_y: 0.0f); |
392 | const ImRect bb(pos, pos + total_size); |
393 | if (!ItemAdd(bb, id: 0)) |
394 | return; |
395 | |
396 | // Render |
397 | ImU32 text_col = GetColorU32(idx: ImGuiCol_Text); |
398 | RenderBullet(draw_list: window->DrawList, pos: bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, g.FontSize * 0.5f), col: text_col); |
399 | RenderText(pos: bb.Min + ImVec2(g.FontSize + style.FramePadding.x * 2, 0.0f), text: text_begin, text_end, hide_text_after_hash: false); |
400 | } |
401 | |
402 | //------------------------------------------------------------------------- |
403 | // [SECTION] Widgets: Main |
404 | //------------------------------------------------------------------------- |
405 | // - ButtonBehavior() [Internal] |
406 | // - Button() |
407 | // - SmallButton() |
408 | // - InvisibleButton() |
409 | // - ArrowButton() |
410 | // - CloseButton() [Internal] |
411 | // - CollapseButton() [Internal] |
412 | // - GetWindowScrollbarID() [Internal] |
413 | // - GetWindowScrollbarRect() [Internal] |
414 | // - Scrollbar() [Internal] |
415 | // - ScrollbarEx() [Internal] |
416 | // - Image() |
417 | // - ImageButton() |
418 | // - Checkbox() |
419 | // - CheckboxFlagsT() [Internal] |
420 | // - CheckboxFlags() |
421 | // - RadioButton() |
422 | // - ProgressBar() |
423 | // - Bullet() |
424 | //------------------------------------------------------------------------- |
425 | |
426 | // The ButtonBehavior() function is key to many interactions and used by many/most widgets. |
427 | // Because we handle so many cases (keyboard/gamepad navigation, drag and drop) and many specific behavior (via ImGuiButtonFlags_), |
428 | // this code is a little complex. |
429 | // By far the most common path is interacting with the Mouse using the default ImGuiButtonFlags_PressedOnClickRelease button behavior. |
430 | // See the series of events below and the corresponding state reported by dear imgui: |
431 | //------------------------------------------------------------------------------------------------------------------------------------------------ |
432 | // with PressedOnClickRelease: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked() |
433 | // Frame N+0 (mouse is outside bb) - - - - - - |
434 | // Frame N+1 (mouse moves inside bb) - true - - - - |
435 | // Frame N+2 (mouse button is down) - true true true - true |
436 | // Frame N+3 (mouse button is down) - true true - - - |
437 | // Frame N+4 (mouse moves outside bb) - - true - - - |
438 | // Frame N+5 (mouse moves inside bb) - true true - - - |
439 | // Frame N+6 (mouse button is released) true true - - true - |
440 | // Frame N+7 (mouse button is released) - true - - - - |
441 | // Frame N+8 (mouse moves outside bb) - - - - - - |
442 | //------------------------------------------------------------------------------------------------------------------------------------------------ |
443 | // with PressedOnClick: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked() |
444 | // Frame N+2 (mouse button is down) true true true true - true |
445 | // Frame N+3 (mouse button is down) - true true - - - |
446 | // Frame N+6 (mouse button is released) - true - - true - |
447 | // Frame N+7 (mouse button is released) - true - - - - |
448 | //------------------------------------------------------------------------------------------------------------------------------------------------ |
449 | // with PressedOnRelease: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked() |
450 | // Frame N+2 (mouse button is down) - true - - - true |
451 | // Frame N+3 (mouse button is down) - true - - - - |
452 | // Frame N+6 (mouse button is released) true true - - - - |
453 | // Frame N+7 (mouse button is released) - true - - - - |
454 | //------------------------------------------------------------------------------------------------------------------------------------------------ |
455 | // with PressedOnDoubleClick: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked() |
456 | // Frame N+0 (mouse button is down) - true - - - true |
457 | // Frame N+1 (mouse button is down) - true - - - - |
458 | // Frame N+2 (mouse button is released) - true - - - - |
459 | // Frame N+3 (mouse button is released) - true - - - - |
460 | // Frame N+4 (mouse button is down) true true true true - true |
461 | // Frame N+5 (mouse button is down) - true true - - - |
462 | // Frame N+6 (mouse button is released) - true - - true - |
463 | // Frame N+7 (mouse button is released) - true - - - - |
464 | //------------------------------------------------------------------------------------------------------------------------------------------------ |
465 | // Note that some combinations are supported, |
466 | // - PressedOnDragDropHold can generally be associated with any flag. |
467 | // - PressedOnDoubleClick can be associated by PressedOnClickRelease/PressedOnRelease, in which case the second release event won't be reported. |
468 | //------------------------------------------------------------------------------------------------------------------------------------------------ |
469 | // The behavior of the return-value changes when ImGuiButtonFlags_Repeat is set: |
470 | // Repeat+ Repeat+ Repeat+ Repeat+ |
471 | // PressedOnClickRelease PressedOnClick PressedOnRelease PressedOnDoubleClick |
472 | //------------------------------------------------------------------------------------------------------------------------------------------------- |
473 | // Frame N+0 (mouse button is down) - true - true |
474 | // ... - - - - |
475 | // Frame N + RepeatDelay true true - true |
476 | // ... - - - - |
477 | // Frame N + RepeatDelay + RepeatRate*N true true - true |
478 | //------------------------------------------------------------------------------------------------------------------------------------------------- |
479 | |
480 | // FIXME: For refactor we could output flags, incl mouse hovered vs nav keyboard vs nav triggered etc. |
481 | // And better standardize how widgets use 'GetColor32((held && hovered) ? ... : hovered ? ...)' vs 'GetColor32(held ? ... : hovered ? ...);' |
482 | // For mouse feedback we typically prefer the 'held && hovered' test, but for nav feedback not always. Outputting hovered=true on Activation may be misleading. |
483 | bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags) |
484 | { |
485 | ImGuiContext& g = *GImGui; |
486 | ImGuiWindow* window = GetCurrentWindow(); |
487 | |
488 | // Default only reacts to left mouse button |
489 | if ((flags & ImGuiButtonFlags_MouseButtonMask_) == 0) |
490 | flags |= ImGuiButtonFlags_MouseButtonDefault_; |
491 | |
492 | // Default behavior requires click + release inside bounding box |
493 | if ((flags & ImGuiButtonFlags_PressedOnMask_) == 0) |
494 | flags |= ImGuiButtonFlags_PressedOnDefault_; |
495 | |
496 | // Default behavior inherited from item flags |
497 | // Note that _both_ ButtonFlags and ItemFlags are valid sources, so copy one into the item_flags and only check that. |
498 | ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.InFlags : g.CurrentItemFlags); |
499 | if (flags & ImGuiButtonFlags_AllowOverlap) |
500 | item_flags |= ImGuiItemFlags_AllowOverlap; |
501 | if (flags & ImGuiButtonFlags_Repeat) |
502 | item_flags |= ImGuiItemFlags_ButtonRepeat; |
503 | |
504 | ImGuiWindow* backup_hovered_window = g.HoveredWindow; |
505 | const bool flatten_hovered_children = (flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredWindow && g.HoveredWindow->RootWindowDockTree == window->RootWindowDockTree; |
506 | if (flatten_hovered_children) |
507 | g.HoveredWindow = window; |
508 | |
509 | #ifdef IMGUI_ENABLE_TEST_ENGINE |
510 | // Alternate registration spot, for when caller didn't use ItemAdd() |
511 | if (id != 0 && g.LastItemData.ID != id) |
512 | IMGUI_TEST_ENGINE_ITEM_ADD(id, bb, NULL); |
513 | #endif |
514 | |
515 | bool pressed = false; |
516 | bool hovered = ItemHoverable(bb, id, item_flags); |
517 | |
518 | // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button |
519 | if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers)) |
520 | if (IsItemHovered(flags: ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) |
521 | { |
522 | hovered = true; |
523 | SetHoveredID(id); |
524 | if (g.HoveredIdTimer - g.IO.DeltaTime <= DRAGDROP_HOLD_TO_OPEN_TIMER && g.HoveredIdTimer >= DRAGDROP_HOLD_TO_OPEN_TIMER) |
525 | { |
526 | pressed = true; |
527 | g.DragDropHoldJustPressedId = id; |
528 | FocusWindow(window); |
529 | } |
530 | } |
531 | |
532 | if (flatten_hovered_children) |
533 | g.HoveredWindow = backup_hovered_window; |
534 | |
535 | // Mouse handling |
536 | const ImGuiID test_owner_id = (flags & ImGuiButtonFlags_NoTestKeyOwner) ? ImGuiKeyOwner_Any : id; |
537 | if (hovered) |
538 | { |
539 | // Poll mouse buttons |
540 | // - 'mouse_button_clicked' is generally carried into ActiveIdMouseButton when setting ActiveId. |
541 | // - Technically we only need some values in one code path, but since this is gated by hovered test this is fine. |
542 | int mouse_button_clicked = -1; |
543 | int mouse_button_released = -1; |
544 | for (int button = 0; button < 3; button++) |
545 | if (flags & (ImGuiButtonFlags_MouseButtonLeft << button)) // Handle ImGuiButtonFlags_MouseButtonRight and ImGuiButtonFlags_MouseButtonMiddle here. |
546 | { |
547 | if (IsMouseClicked(button, owner_id: test_owner_id) && mouse_button_clicked == -1) { mouse_button_clicked = button; } |
548 | if (IsMouseReleased(button, owner_id: test_owner_id) && mouse_button_released == -1) { mouse_button_released = button; } |
549 | } |
550 | |
551 | // Process initial action |
552 | if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt)) |
553 | { |
554 | if (mouse_button_clicked != -1 && g.ActiveId != id) |
555 | { |
556 | if (!(flags & ImGuiButtonFlags_NoSetKeyOwner)) |
557 | SetKeyOwner(key: MouseButtonToKey(button: mouse_button_clicked), owner_id: id); |
558 | if (flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere)) |
559 | { |
560 | SetActiveID(id, window); |
561 | g.ActiveIdMouseButton = mouse_button_clicked; |
562 | if (!(flags & ImGuiButtonFlags_NoNavFocus)) |
563 | SetFocusID(id, window); |
564 | FocusWindow(window); |
565 | } |
566 | if ((flags & ImGuiButtonFlags_PressedOnClick) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseClickedCount[mouse_button_clicked] == 2)) |
567 | { |
568 | pressed = true; |
569 | if (flags & ImGuiButtonFlags_NoHoldingActiveId) |
570 | ClearActiveID(); |
571 | else |
572 | SetActiveID(id, window); // Hold on ID |
573 | if (!(flags & ImGuiButtonFlags_NoNavFocus)) |
574 | SetFocusID(id, window); |
575 | g.ActiveIdMouseButton = mouse_button_clicked; |
576 | FocusWindow(window); |
577 | } |
578 | } |
579 | if (flags & ImGuiButtonFlags_PressedOnRelease) |
580 | { |
581 | if (mouse_button_released != -1) |
582 | { |
583 | const bool has_repeated_at_least_once = (item_flags & ImGuiItemFlags_ButtonRepeat) && g.IO.MouseDownDurationPrev[mouse_button_released] >= g.IO.KeyRepeatDelay; // Repeat mode trumps on release behavior |
584 | if (!has_repeated_at_least_once) |
585 | pressed = true; |
586 | if (!(flags & ImGuiButtonFlags_NoNavFocus)) |
587 | SetFocusID(id, window); |
588 | ClearActiveID(); |
589 | } |
590 | } |
591 | |
592 | // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above). |
593 | // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings. |
594 | if (g.ActiveId == id && (item_flags & ImGuiItemFlags_ButtonRepeat)) |
595 | if (g.IO.MouseDownDuration[g.ActiveIdMouseButton] > 0.0f && IsMouseClicked(button: g.ActiveIdMouseButton, owner_id: test_owner_id, flags: ImGuiInputFlags_Repeat)) |
596 | pressed = true; |
597 | } |
598 | |
599 | if (pressed) |
600 | g.NavDisableHighlight = true; |
601 | } |
602 | |
603 | // Gamepad/Keyboard handling |
604 | // We report navigated and navigation-activated items as hovered but we don't set g.HoveredId to not interfere with mouse. |
605 | if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover) |
606 | if (!(flags & ImGuiButtonFlags_NoHoveredOnFocus)) |
607 | hovered = true; |
608 | if (g.NavActivateDownId == id) |
609 | { |
610 | bool nav_activated_by_code = (g.NavActivateId == id); |
611 | bool nav_activated_by_inputs = (g.NavActivatePressedId == id); |
612 | if (!nav_activated_by_inputs && (item_flags & ImGuiItemFlags_ButtonRepeat)) |
613 | { |
614 | // Avoid pressing multiple keys from triggering excessive amount of repeat events |
615 | const ImGuiKeyData* key1 = GetKeyData(key: ImGuiKey_Space); |
616 | const ImGuiKeyData* key2 = GetKeyData(key: ImGuiKey_Enter); |
617 | const ImGuiKeyData* key3 = GetKeyData(ImGuiKey_NavGamepadActivate); |
618 | const float t1 = ImMax(lhs: ImMax(lhs: key1->DownDuration, rhs: key2->DownDuration), rhs: key3->DownDuration); |
619 | nav_activated_by_inputs = CalcTypematicRepeatAmount(t0: t1 - g.IO.DeltaTime, t1, repeat_delay: g.IO.KeyRepeatDelay, repeat_rate: g.IO.KeyRepeatRate) > 0; |
620 | } |
621 | if (nav_activated_by_code || nav_activated_by_inputs) |
622 | { |
623 | // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button. |
624 | pressed = true; |
625 | SetActiveID(id, window); |
626 | g.ActiveIdSource = g.NavInputSource; |
627 | if (!(flags & ImGuiButtonFlags_NoNavFocus) && !(g.NavActivateFlags & ImGuiActivateFlags_FromShortcut)) |
628 | SetFocusID(id, window); |
629 | if (g.NavActivateFlags & ImGuiActivateFlags_FromShortcut) |
630 | g.ActiveIdFromShortcut = true; |
631 | } |
632 | } |
633 | |
634 | // Process while held |
635 | bool held = false; |
636 | if (g.ActiveId == id) |
637 | { |
638 | if (g.ActiveIdSource == ImGuiInputSource_Mouse) |
639 | { |
640 | if (g.ActiveIdIsJustActivated) |
641 | g.ActiveIdClickOffset = g.IO.MousePos - bb.Min; |
642 | |
643 | const int mouse_button = g.ActiveIdMouseButton; |
644 | if (mouse_button == -1) |
645 | { |
646 | // Fallback for the rare situation were g.ActiveId was set programmatically or from another widget (e.g. #6304). |
647 | ClearActiveID(); |
648 | } |
649 | else if (IsMouseDown(button: mouse_button, owner_id: test_owner_id)) |
650 | { |
651 | held = true; |
652 | } |
653 | else |
654 | { |
655 | bool release_in = hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease) != 0; |
656 | bool release_anywhere = (flags & ImGuiButtonFlags_PressedOnClickReleaseAnywhere) != 0; |
657 | if ((release_in || release_anywhere) && !g.DragDropActive) |
658 | { |
659 | // Report as pressed when releasing the mouse (this is the most common path) |
660 | bool is_double_click_release = (flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseReleased[mouse_button] && g.IO.MouseClickedLastCount[mouse_button] == 2; |
661 | bool is_repeating_already = (item_flags & ImGuiItemFlags_ButtonRepeat) && g.IO.MouseDownDurationPrev[mouse_button] >= g.IO.KeyRepeatDelay; // Repeat mode trumps <on release> |
662 | bool is_button_avail_or_owned = TestKeyOwner(key: MouseButtonToKey(button: mouse_button), owner_id: test_owner_id); |
663 | if (!is_double_click_release && !is_repeating_already && is_button_avail_or_owned) |
664 | pressed = true; |
665 | } |
666 | ClearActiveID(); |
667 | } |
668 | if (!(flags & ImGuiButtonFlags_NoNavFocus)) |
669 | g.NavDisableHighlight = true; |
670 | } |
671 | else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) |
672 | { |
673 | // When activated using Nav, we hold on the ActiveID until activation button is released |
674 | if (g.NavActivateDownId == id) |
675 | held = true; // hovered == true not true as we are already likely hovered on direct activation. |
676 | else |
677 | ClearActiveID(); |
678 | } |
679 | if (pressed) |
680 | g.ActiveIdHasBeenPressedBefore = true; |
681 | } |
682 | |
683 | // Activation highlight (this may be a remote activation) |
684 | if (g.NavHighlightActivatedId == id) |
685 | hovered = true; |
686 | |
687 | if (out_hovered) *out_hovered = hovered; |
688 | if (out_held) *out_held = held; |
689 | |
690 | return pressed; |
691 | } |
692 | |
693 | bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags) |
694 | { |
695 | ImGuiWindow* window = GetCurrentWindow(); |
696 | if (window->SkipItems) |
697 | return false; |
698 | |
699 | ImGuiContext& g = *GImGui; |
700 | const ImGuiStyle& style = g.Style; |
701 | const ImGuiID id = window->GetID(str: label); |
702 | const ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: true); |
703 | |
704 | ImVec2 pos = window->DC.CursorPos; |
705 | if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrLineTextBaseOffset) // 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) |
706 | pos.y += window->DC.CurrLineTextBaseOffset - style.FramePadding.y; |
707 | ImVec2 size = CalcItemSize(size: size_arg, default_w: label_size.x + style.FramePadding.x * 2.0f, default_h: label_size.y + style.FramePadding.y * 2.0f); |
708 | |
709 | const ImRect bb(pos, pos + size); |
710 | ItemSize(size, text_baseline_y: style.FramePadding.y); |
711 | if (!ItemAdd(bb, id)) |
712 | return false; |
713 | |
714 | bool hovered, held; |
715 | bool pressed = ButtonBehavior(bb, id, out_hovered: &hovered, out_held: &held, flags); |
716 | |
717 | // Render |
718 | const ImU32 col = GetColorU32(idx: (held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); |
719 | RenderNavHighlight(bb, id); |
720 | RenderFrame(p_min: bb.Min, p_max: bb.Max, fill_col: col, border: true, rounding: style.FrameRounding); |
721 | |
722 | if (g.LogEnabled) |
723 | LogSetNextTextDecoration(prefix: "[" , suffix: "]" ); |
724 | RenderTextClipped(pos_min: bb.Min + style.FramePadding, pos_max: bb.Max - style.FramePadding, text: label, NULL, text_size_if_known: &label_size, align: style.ButtonTextAlign, clip_rect: &bb); |
725 | |
726 | // Automatically close popups |
727 | //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup)) |
728 | // CloseCurrentPopup(); |
729 | |
730 | IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); |
731 | return pressed; |
732 | } |
733 | |
734 | bool ImGui::Button(const char* label, const ImVec2& size_arg) |
735 | { |
736 | return ButtonEx(label, size_arg, flags: ImGuiButtonFlags_None); |
737 | } |
738 | |
739 | // Small buttons fits within text without additional vertical spacing. |
740 | bool ImGui::SmallButton(const char* label) |
741 | { |
742 | ImGuiContext& g = *GImGui; |
743 | float backup_padding_y = g.Style.FramePadding.y; |
744 | g.Style.FramePadding.y = 0.0f; |
745 | bool pressed = ButtonEx(label, size_arg: ImVec2(0, 0), flags: ImGuiButtonFlags_AlignTextBaseLine); |
746 | g.Style.FramePadding.y = backup_padding_y; |
747 | return pressed; |
748 | } |
749 | |
750 | // Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack. |
751 | // 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) |
752 | bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg, ImGuiButtonFlags flags) |
753 | { |
754 | ImGuiContext& g = *GImGui; |
755 | ImGuiWindow* window = GetCurrentWindow(); |
756 | if (window->SkipItems) |
757 | return false; |
758 | |
759 | // Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size. |
760 | IM_ASSERT(size_arg.x != 0.0f && size_arg.y != 0.0f); |
761 | |
762 | const ImGuiID id = window->GetID(str: str_id); |
763 | ImVec2 size = CalcItemSize(size: size_arg, default_w: 0.0f, default_h: 0.0f); |
764 | const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); |
765 | ItemSize(size); |
766 | if (!ItemAdd(bb, id)) |
767 | return false; |
768 | |
769 | bool hovered, held; |
770 | bool pressed = ButtonBehavior(bb, id, out_hovered: &hovered, out_held: &held, flags); |
771 | |
772 | IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags); |
773 | return pressed; |
774 | } |
775 | |
776 | bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags) |
777 | { |
778 | ImGuiContext& g = *GImGui; |
779 | ImGuiWindow* window = GetCurrentWindow(); |
780 | if (window->SkipItems) |
781 | return false; |
782 | |
783 | const ImGuiID id = window->GetID(str: str_id); |
784 | const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); |
785 | const float default_size = GetFrameHeight(); |
786 | ItemSize(size, text_baseline_y: (size.y >= default_size) ? g.Style.FramePadding.y : -1.0f); |
787 | if (!ItemAdd(bb, id)) |
788 | return false; |
789 | |
790 | bool hovered, held; |
791 | bool pressed = ButtonBehavior(bb, id, out_hovered: &hovered, out_held: &held, flags); |
792 | |
793 | // Render |
794 | const ImU32 bg_col = GetColorU32(idx: (held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); |
795 | const ImU32 text_col = GetColorU32(idx: ImGuiCol_Text); |
796 | RenderNavHighlight(bb, id); |
797 | RenderFrame(p_min: bb.Min, p_max: bb.Max, fill_col: bg_col, border: true, rounding: g.Style.FrameRounding); |
798 | RenderArrow(draw_list: window->DrawList, pos: bb.Min + ImVec2(ImMax(lhs: 0.0f, rhs: (size.x - g.FontSize) * 0.5f), ImMax(lhs: 0.0f, rhs: (size.y - g.FontSize) * 0.5f)), col: text_col, dir); |
799 | |
800 | IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags); |
801 | return pressed; |
802 | } |
803 | |
804 | bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir) |
805 | { |
806 | float sz = GetFrameHeight(); |
807 | return ArrowButtonEx(str_id, dir, size: ImVec2(sz, sz), flags: ImGuiButtonFlags_None); |
808 | } |
809 | |
810 | // Button to close a window |
811 | bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos) |
812 | { |
813 | ImGuiContext& g = *GImGui; |
814 | ImGuiWindow* window = g.CurrentWindow; |
815 | |
816 | // Tweak 1: Shrink hit-testing area if button covers an abnormally large proportion of the visible region. That's in order to facilitate moving the window away. (#3825) |
817 | // This may better be applied as a general hit-rect reduction mechanism for all widgets to ensure the area to move window is always accessible? |
818 | const ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize)); |
819 | ImRect bb_interact = bb; |
820 | const float area_to_visible_ratio = window->OuterRectClipped.GetArea() / bb.GetArea(); |
821 | if (area_to_visible_ratio < 1.5f) |
822 | bb_interact.Expand(amount: ImTrunc(v: bb_interact.GetSize() * -0.25f)); |
823 | |
824 | // Tweak 2: We intentionally allow interaction when clipped so that a mechanical Alt,Right,Activate sequence can always close a window. |
825 | // (this isn't the common behavior of buttons, but it doesn't affect the user because navigation tends to keep items visible in scrolling layer). |
826 | bool is_clipped = !ItemAdd(bb: bb_interact, id); |
827 | |
828 | bool hovered, held; |
829 | bool pressed = ButtonBehavior(bb: bb_interact, id, out_hovered: &hovered, out_held: &held); |
830 | if (is_clipped) |
831 | return pressed; |
832 | |
833 | // Render |
834 | // FIXME: Clarify this mess |
835 | ImU32 col = GetColorU32(idx: held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered); |
836 | ImVec2 center = bb.GetCenter(); |
837 | if (hovered) |
838 | window->DrawList->AddCircleFilled(center, radius: ImMax(lhs: 2.0f, rhs: g.FontSize * 0.5f + 1.0f), col); |
839 | |
840 | float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f; |
841 | ImU32 cross_col = GetColorU32(idx: ImGuiCol_Text); |
842 | center -= ImVec2(0.5f, 0.5f); |
843 | window->DrawList->AddLine(p1: center + ImVec2(+cross_extent, +cross_extent), p2: center + ImVec2(-cross_extent, -cross_extent), col: cross_col, thickness: 1.0f); |
844 | window->DrawList->AddLine(p1: center + ImVec2(+cross_extent, -cross_extent), p2: center + ImVec2(-cross_extent, +cross_extent), col: cross_col, thickness: 1.0f); |
845 | |
846 | return pressed; |
847 | } |
848 | |
849 | // The Collapse button also functions as a Dock Menu button. |
850 | bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos, ImGuiDockNode* dock_node) |
851 | { |
852 | ImGuiContext& g = *GImGui; |
853 | ImGuiWindow* window = g.CurrentWindow; |
854 | |
855 | ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize)); |
856 | bool is_clipped = !ItemAdd(bb, id); |
857 | bool hovered, held; |
858 | bool pressed = ButtonBehavior(bb, id, out_hovered: &hovered, out_held: &held, flags: ImGuiButtonFlags_None); |
859 | if (is_clipped) |
860 | return pressed; |
861 | |
862 | // Render |
863 | //bool is_dock_menu = (window->DockNodeAsHost && !window->Collapsed); |
864 | ImU32 bg_col = GetColorU32(idx: (held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); |
865 | ImU32 text_col = GetColorU32(idx: ImGuiCol_Text); |
866 | if (hovered || held) |
867 | window->DrawList->AddCircleFilled(center: bb.GetCenter() + ImVec2(0.0f, -0.5f), radius: g.FontSize * 0.5f + 1.0f, col: bg_col); |
868 | |
869 | if (dock_node) |
870 | RenderArrowDockMenu(draw_list: window->DrawList, p_min: bb.Min, sz: g.FontSize, col: text_col); |
871 | else |
872 | RenderArrow(draw_list: window->DrawList, pos: bb.Min, col: text_col, dir: window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, scale: 1.0f); |
873 | |
874 | // Switch to moving the window after mouse is moved beyond the initial drag threshold |
875 | if (IsItemActive() && IsMouseDragging(button: 0)) |
876 | StartMouseMovingWindowOrNode(window, node: dock_node, undock: true); // Undock from window/collapse menu button |
877 | |
878 | return pressed; |
879 | } |
880 | |
881 | ImGuiID ImGui::GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis) |
882 | { |
883 | return window->GetID(str: axis == ImGuiAxis_X ? "#SCROLLX" : "#SCROLLY" ); |
884 | } |
885 | |
886 | // Return scrollbar rectangle, must only be called for corresponding axis if window->ScrollbarX/Y is set. |
887 | ImRect ImGui::GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis) |
888 | { |
889 | const ImRect outer_rect = window->Rect(); |
890 | const ImRect inner_rect = window->InnerRect; |
891 | const float border_size = window->WindowBorderSize; |
892 | const float scrollbar_size = window->ScrollbarSizes[axis ^ 1]; // (ScrollbarSizes.x = width of Y scrollbar; ScrollbarSizes.y = height of X scrollbar) |
893 | IM_ASSERT(scrollbar_size > 0.0f); |
894 | if (axis == ImGuiAxis_X) |
895 | return ImRect(inner_rect.Min.x, ImMax(lhs: outer_rect.Min.y, rhs: outer_rect.Max.y - border_size - scrollbar_size), inner_rect.Max.x - border_size, outer_rect.Max.y - border_size); |
896 | else |
897 | return ImRect(ImMax(lhs: outer_rect.Min.x, rhs: outer_rect.Max.x - border_size - scrollbar_size), inner_rect.Min.y, outer_rect.Max.x - border_size, inner_rect.Max.y - border_size); |
898 | } |
899 | |
900 | void ImGui::Scrollbar(ImGuiAxis axis) |
901 | { |
902 | ImGuiContext& g = *GImGui; |
903 | ImGuiWindow* window = g.CurrentWindow; |
904 | const ImGuiID id = GetWindowScrollbarID(window, axis); |
905 | |
906 | // Calculate scrollbar bounding box |
907 | ImRect bb = GetWindowScrollbarRect(window, axis); |
908 | ImDrawFlags rounding_corners = ImDrawFlags_RoundCornersNone; |
909 | if (axis == ImGuiAxis_X) |
910 | { |
911 | rounding_corners |= ImDrawFlags_RoundCornersBottomLeft; |
912 | if (!window->ScrollbarY) |
913 | rounding_corners |= ImDrawFlags_RoundCornersBottomRight; |
914 | } |
915 | else |
916 | { |
917 | if ((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) |
918 | rounding_corners |= ImDrawFlags_RoundCornersTopRight; |
919 | if (!window->ScrollbarX) |
920 | rounding_corners |= ImDrawFlags_RoundCornersBottomRight; |
921 | } |
922 | float size_avail = window->InnerRect.Max[axis] - window->InnerRect.Min[axis]; |
923 | float size_contents = window->ContentSize[axis] + window->WindowPadding[axis] * 2.0f; |
924 | ImS64 scroll = (ImS64)window->Scroll[axis]; |
925 | ScrollbarEx(bb, id, axis, p_scroll_v: &scroll, avail_v: (ImS64)size_avail, contents_v: (ImS64)size_contents, flags: rounding_corners); |
926 | window->Scroll[axis] = (float)scroll; |
927 | } |
928 | |
929 | // Vertical/Horizontal scrollbar |
930 | // The entire piece of code below is rather confusing because: |
931 | // - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab) |
932 | // - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar |
933 | // - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal. |
934 | // Still, the code should probably be made simpler.. |
935 | bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS64* p_scroll_v, ImS64 size_avail_v, ImS64 size_contents_v, ImDrawFlags flags) |
936 | { |
937 | ImGuiContext& g = *GImGui; |
938 | ImGuiWindow* window = g.CurrentWindow; |
939 | if (window->SkipItems) |
940 | return false; |
941 | |
942 | const float bb_frame_width = bb_frame.GetWidth(); |
943 | const float bb_frame_height = bb_frame.GetHeight(); |
944 | if (bb_frame_width <= 0.0f || bb_frame_height <= 0.0f) |
945 | return false; |
946 | |
947 | // When we are too small, start hiding and disabling the grab (this reduce visual noise on very small window and facilitate using the window resize grab) |
948 | float alpha = 1.0f; |
949 | if ((axis == ImGuiAxis_Y) && bb_frame_height < g.FontSize + g.Style.FramePadding.y * 2.0f) |
950 | alpha = ImSaturate(f: (bb_frame_height - g.FontSize) / (g.Style.FramePadding.y * 2.0f)); |
951 | if (alpha <= 0.0f) |
952 | return false; |
953 | |
954 | const ImGuiStyle& style = g.Style; |
955 | const bool allow_interaction = (alpha >= 1.0f); |
956 | |
957 | ImRect bb = bb_frame; |
958 | bb.Expand(amount: ImVec2(-ImClamp(IM_TRUNC((bb_frame_width - 2.0f) * 0.5f), mn: 0.0f, mx: 3.0f), -ImClamp(IM_TRUNC((bb_frame_height - 2.0f) * 0.5f), mn: 0.0f, mx: 3.0f))); |
959 | |
960 | // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar) |
961 | const float scrollbar_size_v = (axis == ImGuiAxis_X) ? bb.GetWidth() : bb.GetHeight(); |
962 | |
963 | // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount) |
964 | // But we maintain a minimum size in pixel to allow for the user to still aim inside. |
965 | IM_ASSERT(ImMax(size_contents_v, 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. |
966 | const ImS64 win_size_v = ImMax(lhs: ImMax(lhs: size_contents_v, rhs: size_avail_v), rhs: (ImS64)1); |
967 | const float grab_h_pixels = ImClamp(v: scrollbar_size_v * ((float)size_avail_v / (float)win_size_v), mn: style.GrabMinSize, mx: scrollbar_size_v); |
968 | const float grab_h_norm = grab_h_pixels / scrollbar_size_v; |
969 | |
970 | // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar(). |
971 | bool held = false; |
972 | bool hovered = false; |
973 | ItemAdd(bb: bb_frame, id, NULL, extra_flags: ImGuiItemFlags_NoNav); |
974 | ButtonBehavior(bb, id, out_hovered: &hovered, out_held: &held, flags: ImGuiButtonFlags_NoNavFocus); |
975 | |
976 | const ImS64 scroll_max = ImMax(lhs: (ImS64)1, rhs: size_contents_v - size_avail_v); |
977 | float scroll_ratio = ImSaturate(f: (float)*p_scroll_v / (float)scroll_max); |
978 | float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; // Grab position in normalized space |
979 | if (held && allow_interaction && grab_h_norm < 1.0f) |
980 | { |
981 | const float scrollbar_pos_v = bb.Min[axis]; |
982 | const float mouse_pos_v = g.IO.MousePos[axis]; |
983 | |
984 | // Click position in scrollbar normalized space (0.0f->1.0f) |
985 | const float clicked_v_norm = ImSaturate(f: (mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v); |
986 | |
987 | bool seek_absolute = false; |
988 | if (g.ActiveIdIsJustActivated) |
989 | { |
990 | // On initial click calculate the distance between mouse and the center of the grab |
991 | seek_absolute = (clicked_v_norm < grab_v_norm || clicked_v_norm > grab_v_norm + grab_h_norm); |
992 | if (seek_absolute) |
993 | g.ScrollbarClickDeltaToGrabCenter = 0.0f; |
994 | else |
995 | g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f; |
996 | } |
997 | |
998 | // Apply scroll (p_scroll_v will generally point on one member of window->Scroll) |
999 | // It is ok to modify Scroll here because we are being called in Begin() after the calculation of ContentSize and before setting up our starting position |
1000 | const float scroll_v_norm = ImSaturate(f: (clicked_v_norm - g.ScrollbarClickDeltaToGrabCenter - grab_h_norm * 0.5f) / (1.0f - grab_h_norm)); |
1001 | *p_scroll_v = (ImS64)(scroll_v_norm * scroll_max); |
1002 | |
1003 | // Update values for rendering |
1004 | scroll_ratio = ImSaturate(f: (float)*p_scroll_v / (float)scroll_max); |
1005 | grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; |
1006 | |
1007 | // Update distance to grab now that we have seeked and saturated |
1008 | if (seek_absolute) |
1009 | g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f; |
1010 | } |
1011 | |
1012 | // Render |
1013 | const ImU32 bg_col = GetColorU32(idx: ImGuiCol_ScrollbarBg); |
1014 | const ImU32 grab_col = GetColorU32(idx: held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab, alpha_mul: alpha); |
1015 | window->DrawList->AddRectFilled(p_min: bb_frame.Min, p_max: bb_frame.Max, col: bg_col, rounding: window->WindowRounding, flags); |
1016 | ImRect grab_rect; |
1017 | if (axis == ImGuiAxis_X) |
1018 | grab_rect = ImRect(ImLerp(a: bb.Min.x, b: bb.Max.x, t: grab_v_norm), bb.Min.y, ImLerp(a: bb.Min.x, b: bb.Max.x, t: grab_v_norm) + grab_h_pixels, bb.Max.y); |
1019 | else |
1020 | grab_rect = ImRect(bb.Min.x, ImLerp(a: bb.Min.y, b: bb.Max.y, t: grab_v_norm), bb.Max.x, ImLerp(a: bb.Min.y, b: bb.Max.y, t: grab_v_norm) + grab_h_pixels); |
1021 | window->DrawList->AddRectFilled(p_min: grab_rect.Min, p_max: grab_rect.Max, col: grab_col, rounding: style.ScrollbarRounding); |
1022 | |
1023 | return held; |
1024 | } |
1025 | |
1026 | // - Read about ImTextureID here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples |
1027 | // - 'uv0' and 'uv1' are texture coordinates. Read about them from the same link above. |
1028 | void ImGui::Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col) |
1029 | { |
1030 | ImGuiWindow* window = GetCurrentWindow(); |
1031 | if (window->SkipItems) |
1032 | return; |
1033 | |
1034 | const float border_size = (border_col.w > 0.0f) ? 1.0f : 0.0f; |
1035 | const ImVec2 padding(border_size, border_size); |
1036 | const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + image_size + padding * 2.0f); |
1037 | ItemSize(bb); |
1038 | if (!ItemAdd(bb, id: 0)) |
1039 | return; |
1040 | |
1041 | // Render |
1042 | if (border_size > 0.0f) |
1043 | window->DrawList->AddRect(p_min: bb.Min, p_max: bb.Max, col: GetColorU32(col: border_col), rounding: 0.0f, flags: ImDrawFlags_None, thickness: border_size); |
1044 | window->DrawList->AddImage(user_texture_id, p_min: bb.Min + padding, p_max: bb.Max - padding, uv_min: uv0, uv_max: uv1, col: GetColorU32(col: tint_col)); |
1045 | } |
1046 | |
1047 | // ImageButton() is flawed as 'id' is always derived from 'texture_id' (see #2464 #1390) |
1048 | // We provide this internal helper to write your own variant while we figure out how to redesign the public ImageButton() API. |
1049 | bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags) |
1050 | { |
1051 | ImGuiContext& g = *GImGui; |
1052 | ImGuiWindow* window = GetCurrentWindow(); |
1053 | if (window->SkipItems) |
1054 | return false; |
1055 | |
1056 | const ImVec2 padding = g.Style.FramePadding; |
1057 | const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + image_size + padding * 2.0f); |
1058 | ItemSize(bb); |
1059 | if (!ItemAdd(bb, id)) |
1060 | return false; |
1061 | |
1062 | bool hovered, held; |
1063 | bool pressed = ButtonBehavior(bb, id, out_hovered: &hovered, out_held: &held, flags); |
1064 | |
1065 | // Render |
1066 | const ImU32 col = GetColorU32(idx: (held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); |
1067 | RenderNavHighlight(bb, id); |
1068 | RenderFrame(p_min: bb.Min, p_max: bb.Max, fill_col: col, border: true, rounding: ImClamp(v: (float)ImMin(lhs: padding.x, rhs: padding.y), mn: 0.0f, mx: g.Style.FrameRounding)); |
1069 | if (bg_col.w > 0.0f) |
1070 | window->DrawList->AddRectFilled(p_min: bb.Min + padding, p_max: bb.Max - padding, col: GetColorU32(col: bg_col)); |
1071 | window->DrawList->AddImage(user_texture_id: texture_id, p_min: bb.Min + padding, p_max: bb.Max - padding, uv_min: uv0, uv_max: uv1, col: GetColorU32(col: tint_col)); |
1072 | |
1073 | return pressed; |
1074 | } |
1075 | |
1076 | // Note that ImageButton() adds style.FramePadding*2.0f to provided size. This is in order to facilitate fitting an image in a button. |
1077 | bool ImGui::ImageButton(const char* str_id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col) |
1078 | { |
1079 | ImGuiContext& g = *GImGui; |
1080 | ImGuiWindow* window = g.CurrentWindow; |
1081 | if (window->SkipItems) |
1082 | return false; |
1083 | |
1084 | return ImageButtonEx(id: window->GetID(str: str_id), texture_id: user_texture_id, image_size, uv0, uv1, bg_col, tint_col); |
1085 | } |
1086 | |
1087 | #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS |
1088 | // Legacy API obsoleted in 1.89. Two differences with new ImageButton() |
1089 | // - new ImageButton() requires an explicit 'const char* str_id' Old ImageButton() used opaque imTextureId (created issue with: multiple buttons with same image, transient texture id values, opaque computation of ID) |
1090 | // - new ImageButton() always use style.FramePadding Old ImageButton() had an override argument. |
1091 | // If you need to change padding with new ImageButton() you can use PushStyleVar(ImGuiStyleVar_FramePadding, value), consistent with other Button functions. |
1092 | 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) |
1093 | { |
1094 | ImGuiContext& g = *GImGui; |
1095 | ImGuiWindow* window = g.CurrentWindow; |
1096 | if (window->SkipItems) |
1097 | return false; |
1098 | |
1099 | // Default to using texture ID as ID. User can still push string/integer prefixes. |
1100 | PushID(ptr_id: (void*)(intptr_t)user_texture_id); |
1101 | const ImGuiID id = window->GetID(str: "#image" ); |
1102 | PopID(); |
1103 | |
1104 | if (frame_padding >= 0) |
1105 | PushStyleVar(idx: ImGuiStyleVar_FramePadding, val: ImVec2((float)frame_padding, (float)frame_padding)); |
1106 | bool ret = ImageButtonEx(id, texture_id: user_texture_id, image_size: size, uv0, uv1, bg_col, tint_col); |
1107 | if (frame_padding >= 0) |
1108 | PopStyleVar(); |
1109 | return ret; |
1110 | } |
1111 | #endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS |
1112 | |
1113 | bool ImGui::Checkbox(const char* label, bool* v) |
1114 | { |
1115 | ImGuiWindow* window = GetCurrentWindow(); |
1116 | if (window->SkipItems) |
1117 | return false; |
1118 | |
1119 | ImGuiContext& g = *GImGui; |
1120 | const ImGuiStyle& style = g.Style; |
1121 | const ImGuiID id = window->GetID(str: label); |
1122 | const ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: true); |
1123 | |
1124 | const float square_sz = GetFrameHeight(); |
1125 | const ImVec2 pos = window->DC.CursorPos; |
1126 | const ImRect total_bb(pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), label_size.y + style.FramePadding.y * 2.0f)); |
1127 | ItemSize(bb: total_bb, text_baseline_y: style.FramePadding.y); |
1128 | if (!ItemAdd(bb: total_bb, id)) |
1129 | { |
1130 | IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); |
1131 | return false; |
1132 | } |
1133 | |
1134 | bool hovered, held; |
1135 | bool pressed = ButtonBehavior(bb: total_bb, id, out_hovered: &hovered, out_held: &held); |
1136 | if (pressed) |
1137 | { |
1138 | *v = !(*v); |
1139 | MarkItemEdited(id); |
1140 | } |
1141 | |
1142 | const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz)); |
1143 | RenderNavHighlight(bb: total_bb, id); |
1144 | RenderFrame(p_min: check_bb.Min, p_max: check_bb.Max, fill_col: GetColorU32(idx: (held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), border: true, rounding: style.FrameRounding); |
1145 | ImU32 check_col = GetColorU32(idx: ImGuiCol_CheckMark); |
1146 | bool mixed_value = (g.LastItemData.InFlags & ImGuiItemFlags_MixedValue) != 0; |
1147 | if (mixed_value) |
1148 | { |
1149 | // Undocumented tristate/mixed/indeterminate checkbox (#2644) |
1150 | // This may seem awkwardly designed because the aim is to make ImGuiItemFlags_MixedValue supported by all widgets (not just checkbox) |
1151 | ImVec2 pad(ImMax(lhs: 1.0f, IM_TRUNC(square_sz / 3.6f)), ImMax(lhs: 1.0f, IM_TRUNC(square_sz / 3.6f))); |
1152 | window->DrawList->AddRectFilled(p_min: check_bb.Min + pad, p_max: check_bb.Max - pad, col: check_col, rounding: style.FrameRounding); |
1153 | } |
1154 | else if (*v) |
1155 | { |
1156 | const float pad = ImMax(lhs: 1.0f, IM_TRUNC(square_sz / 6.0f)); |
1157 | RenderCheckMark(draw_list: window->DrawList, pos: check_bb.Min + ImVec2(pad, pad), col: check_col, sz: square_sz - pad * 2.0f); |
1158 | } |
1159 | |
1160 | ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y); |
1161 | if (g.LogEnabled) |
1162 | LogRenderedText(ref_pos: &label_pos, text: mixed_value ? "[~]" : *v ? "[x]" : "[ ]" ); |
1163 | if (label_size.x > 0.0f) |
1164 | RenderText(pos: label_pos, text: label); |
1165 | |
1166 | IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); |
1167 | return pressed; |
1168 | } |
1169 | |
1170 | template<typename T> |
1171 | bool ImGui::CheckboxFlagsT(const char* label, T* flags, T flags_value) |
1172 | { |
1173 | bool all_on = (*flags & flags_value) == flags_value; |
1174 | bool any_on = (*flags & flags_value) != 0; |
1175 | bool pressed; |
1176 | if (!all_on && any_on) |
1177 | { |
1178 | ImGuiContext& g = *GImGui; |
1179 | g.NextItemData.ItemFlags |= ImGuiItemFlags_MixedValue; |
1180 | pressed = Checkbox(label, v: &all_on); |
1181 | } |
1182 | else |
1183 | { |
1184 | pressed = Checkbox(label, v: &all_on); |
1185 | |
1186 | } |
1187 | if (pressed) |
1188 | { |
1189 | if (all_on) |
1190 | *flags |= flags_value; |
1191 | else |
1192 | *flags &= ~flags_value; |
1193 | } |
1194 | return pressed; |
1195 | } |
1196 | |
1197 | bool ImGui::CheckboxFlags(const char* label, int* flags, int flags_value) |
1198 | { |
1199 | return CheckboxFlagsT(label, flags, flags_value); |
1200 | } |
1201 | |
1202 | bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value) |
1203 | { |
1204 | return CheckboxFlagsT(label, flags, flags_value); |
1205 | } |
1206 | |
1207 | bool ImGui::CheckboxFlags(const char* label, ImS64* flags, ImS64 flags_value) |
1208 | { |
1209 | return CheckboxFlagsT(label, flags, flags_value); |
1210 | } |
1211 | |
1212 | bool ImGui::CheckboxFlags(const char* label, ImU64* flags, ImU64 flags_value) |
1213 | { |
1214 | return CheckboxFlagsT(label, flags, flags_value); |
1215 | } |
1216 | |
1217 | bool ImGui::RadioButton(const char* label, bool active) |
1218 | { |
1219 | ImGuiWindow* window = GetCurrentWindow(); |
1220 | if (window->SkipItems) |
1221 | return false; |
1222 | |
1223 | ImGuiContext& g = *GImGui; |
1224 | const ImGuiStyle& style = g.Style; |
1225 | const ImGuiID id = window->GetID(str: label); |
1226 | const ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: true); |
1227 | |
1228 | const float square_sz = GetFrameHeight(); |
1229 | const ImVec2 pos = window->DC.CursorPos; |
1230 | const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz)); |
1231 | const ImRect total_bb(pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), label_size.y + style.FramePadding.y * 2.0f)); |
1232 | ItemSize(bb: total_bb, text_baseline_y: style.FramePadding.y); |
1233 | if (!ItemAdd(bb: total_bb, id)) |
1234 | return false; |
1235 | |
1236 | ImVec2 center = check_bb.GetCenter(); |
1237 | center.x = IM_ROUND(center.x); |
1238 | center.y = IM_ROUND(center.y); |
1239 | const float radius = (square_sz - 1.0f) * 0.5f; |
1240 | |
1241 | bool hovered, held; |
1242 | bool pressed = ButtonBehavior(bb: total_bb, id, out_hovered: &hovered, out_held: &held); |
1243 | if (pressed) |
1244 | MarkItemEdited(id); |
1245 | |
1246 | RenderNavHighlight(bb: total_bb, id); |
1247 | const int num_segment = window->DrawList->_CalcCircleAutoSegmentCount(radius); |
1248 | window->DrawList->AddCircleFilled(center, radius, col: GetColorU32(idx: (held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), num_segments: num_segment); |
1249 | if (active) |
1250 | { |
1251 | const float pad = ImMax(lhs: 1.0f, IM_TRUNC(square_sz / 6.0f)); |
1252 | window->DrawList->AddCircleFilled(center, radius: radius - pad, col: GetColorU32(idx: ImGuiCol_CheckMark)); |
1253 | } |
1254 | |
1255 | if (style.FrameBorderSize > 0.0f) |
1256 | { |
1257 | window->DrawList->AddCircle(center: center + ImVec2(1, 1), radius, col: GetColorU32(idx: ImGuiCol_BorderShadow), num_segments: num_segment, thickness: style.FrameBorderSize); |
1258 | window->DrawList->AddCircle(center, radius, col: GetColorU32(idx: ImGuiCol_Border), num_segments: num_segment, thickness: style.FrameBorderSize); |
1259 | } |
1260 | |
1261 | ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y); |
1262 | if (g.LogEnabled) |
1263 | LogRenderedText(ref_pos: &label_pos, text: active ? "(x)" : "( )" ); |
1264 | if (label_size.x > 0.0f) |
1265 | RenderText(pos: label_pos, text: label); |
1266 | |
1267 | IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); |
1268 | return pressed; |
1269 | } |
1270 | |
1271 | // FIXME: This would work nicely if it was a public template, e.g. 'template<T> RadioButton(const char* label, T* v, T v_button)', but I'm not sure how we would expose it.. |
1272 | bool ImGui::RadioButton(const char* label, int* v, int v_button) |
1273 | { |
1274 | const bool pressed = RadioButton(label, active: *v == v_button); |
1275 | if (pressed) |
1276 | *v = v_button; |
1277 | return pressed; |
1278 | } |
1279 | |
1280 | // size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size |
1281 | void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay) |
1282 | { |
1283 | ImGuiWindow* window = GetCurrentWindow(); |
1284 | if (window->SkipItems) |
1285 | return; |
1286 | |
1287 | ImGuiContext& g = *GImGui; |
1288 | const ImGuiStyle& style = g.Style; |
1289 | |
1290 | ImVec2 pos = window->DC.CursorPos; |
1291 | ImVec2 size = CalcItemSize(size: size_arg, default_w: CalcItemWidth(), default_h: g.FontSize + style.FramePadding.y * 2.0f); |
1292 | ImRect bb(pos, pos + size); |
1293 | ItemSize(size, text_baseline_y: style.FramePadding.y); |
1294 | if (!ItemAdd(bb, id: 0)) |
1295 | return; |
1296 | |
1297 | // Out of courtesy we accept a NaN fraction without crashing |
1298 | fraction = ImSaturate(f: fraction); |
1299 | const float fraction_not_nan = (fraction == fraction) ? fraction : 0.0f; |
1300 | |
1301 | // Render |
1302 | RenderFrame(p_min: bb.Min, p_max: bb.Max, fill_col: GetColorU32(idx: ImGuiCol_FrameBg), border: true, rounding: style.FrameRounding); |
1303 | bb.Expand(amount: ImVec2(-style.FrameBorderSize, -style.FrameBorderSize)); |
1304 | const ImVec2 fill_br = ImVec2(ImLerp(a: bb.Min.x, b: bb.Max.x, t: fraction_not_nan), bb.Max.y); |
1305 | RenderRectFilledRangeH(draw_list: window->DrawList, rect: bb, col: GetColorU32(idx: ImGuiCol_PlotHistogram), x_start_norm: 0.0f, x_end_norm: fraction_not_nan, rounding: style.FrameRounding); |
1306 | |
1307 | // Default displaying the fraction as percentage string, but user can override it |
1308 | char overlay_buf[32]; |
1309 | if (!overlay) |
1310 | { |
1311 | ImFormatString(buf: overlay_buf, IM_ARRAYSIZE(overlay_buf), fmt: "%.0f%%" , fraction * 100 + 0.01f); |
1312 | overlay = overlay_buf; |
1313 | } |
1314 | |
1315 | ImVec2 overlay_size = CalcTextSize(text: overlay, NULL); |
1316 | if (overlay_size.x > 0.0f) |
1317 | RenderTextClipped(pos_min: ImVec2(ImClamp(v: fill_br.x + style.ItemSpacing.x, mn: bb.Min.x, mx: bb.Max.x - overlay_size.x - style.ItemInnerSpacing.x), bb.Min.y), pos_max: bb.Max, text: overlay, NULL, text_size_if_known: &overlay_size, align: ImVec2(0.0f, 0.5f), clip_rect: &bb); |
1318 | } |
1319 | |
1320 | void ImGui::Bullet() |
1321 | { |
1322 | ImGuiWindow* window = GetCurrentWindow(); |
1323 | if (window->SkipItems) |
1324 | return; |
1325 | |
1326 | ImGuiContext& g = *GImGui; |
1327 | const ImGuiStyle& style = g.Style; |
1328 | const float line_height = ImMax(lhs: ImMin(lhs: window->DC.CurrLineSize.y, rhs: g.FontSize + style.FramePadding.y * 2), rhs: g.FontSize); |
1329 | const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height)); |
1330 | ItemSize(bb); |
1331 | if (!ItemAdd(bb, id: 0)) |
1332 | { |
1333 | SameLine(offset_from_start_x: 0, spacing: style.FramePadding.x * 2); |
1334 | return; |
1335 | } |
1336 | |
1337 | // Render and stay on same line |
1338 | ImU32 text_col = GetColorU32(idx: ImGuiCol_Text); |
1339 | RenderBullet(draw_list: window->DrawList, pos: bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, line_height * 0.5f), col: text_col); |
1340 | SameLine(offset_from_start_x: 0, spacing: style.FramePadding.x * 2.0f); |
1341 | } |
1342 | |
1343 | //------------------------------------------------------------------------- |
1344 | // [SECTION] Widgets: Low-level Layout helpers |
1345 | //------------------------------------------------------------------------- |
1346 | // - Spacing() |
1347 | // - Dummy() |
1348 | // - NewLine() |
1349 | // - AlignTextToFramePadding() |
1350 | // - SeparatorEx() [Internal] |
1351 | // - Separator() |
1352 | // - SplitterBehavior() [Internal] |
1353 | // - ShrinkWidths() [Internal] |
1354 | //------------------------------------------------------------------------- |
1355 | |
1356 | void ImGui::Spacing() |
1357 | { |
1358 | ImGuiWindow* window = GetCurrentWindow(); |
1359 | if (window->SkipItems) |
1360 | return; |
1361 | ItemSize(size: ImVec2(0, 0)); |
1362 | } |
1363 | |
1364 | void ImGui::Dummy(const ImVec2& size) |
1365 | { |
1366 | ImGuiWindow* window = GetCurrentWindow(); |
1367 | if (window->SkipItems) |
1368 | return; |
1369 | |
1370 | const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); |
1371 | ItemSize(size); |
1372 | ItemAdd(bb, id: 0); |
1373 | } |
1374 | |
1375 | void ImGui::NewLine() |
1376 | { |
1377 | ImGuiWindow* window = GetCurrentWindow(); |
1378 | if (window->SkipItems) |
1379 | return; |
1380 | |
1381 | ImGuiContext& g = *GImGui; |
1382 | const ImGuiLayoutType backup_layout_type = window->DC.LayoutType; |
1383 | window->DC.LayoutType = ImGuiLayoutType_Vertical; |
1384 | window->DC.IsSameLine = false; |
1385 | if (window->DC.CurrLineSize.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. |
1386 | ItemSize(size: ImVec2(0, 0)); |
1387 | else |
1388 | ItemSize(size: ImVec2(0.0f, g.FontSize)); |
1389 | window->DC.LayoutType = backup_layout_type; |
1390 | } |
1391 | |
1392 | void ImGui::AlignTextToFramePadding() |
1393 | { |
1394 | ImGuiWindow* window = GetCurrentWindow(); |
1395 | if (window->SkipItems) |
1396 | return; |
1397 | |
1398 | ImGuiContext& g = *GImGui; |
1399 | window->DC.CurrLineSize.y = ImMax(lhs: window->DC.CurrLineSize.y, rhs: g.FontSize + g.Style.FramePadding.y * 2); |
1400 | window->DC.CurrLineTextBaseOffset = ImMax(lhs: window->DC.CurrLineTextBaseOffset, rhs: g.Style.FramePadding.y); |
1401 | } |
1402 | |
1403 | // Horizontal/vertical separating line |
1404 | // FIXME: Surprisingly, this seemingly trivial widget is a victim of many different legacy/tricky layout issues. |
1405 | // Note how thickness == 1.0f is handled specifically as not moving CursorPos by 'thickness', but other values are. |
1406 | void ImGui::SeparatorEx(ImGuiSeparatorFlags flags, float thickness) |
1407 | { |
1408 | ImGuiWindow* window = GetCurrentWindow(); |
1409 | if (window->SkipItems) |
1410 | return; |
1411 | |
1412 | ImGuiContext& g = *GImGui; |
1413 | IM_ASSERT(ImIsPowerOfTwo(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical))); // Check that only 1 option is selected |
1414 | IM_ASSERT(thickness > 0.0f); |
1415 | |
1416 | if (flags & ImGuiSeparatorFlags_Vertical) |
1417 | { |
1418 | // Vertical separator, for menu bars (use current line height). |
1419 | float y1 = window->DC.CursorPos.y; |
1420 | float y2 = window->DC.CursorPos.y + window->DC.CurrLineSize.y; |
1421 | const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + thickness, y2)); |
1422 | ItemSize(size: ImVec2(thickness, 0.0f)); |
1423 | if (!ItemAdd(bb, id: 0)) |
1424 | return; |
1425 | |
1426 | // Draw |
1427 | window->DrawList->AddRectFilled(p_min: bb.Min, p_max: bb.Max, col: GetColorU32(idx: ImGuiCol_Separator)); |
1428 | if (g.LogEnabled) |
1429 | LogText(fmt: " |" ); |
1430 | } |
1431 | else if (flags & ImGuiSeparatorFlags_Horizontal) |
1432 | { |
1433 | // Horizontal Separator |
1434 | float x1 = window->DC.CursorPos.x; |
1435 | float x2 = window->WorkRect.Max.x; |
1436 | |
1437 | // Preserve legacy behavior inside Columns() |
1438 | // Before Tables API happened, we relied on Separator() to span all columns of a Columns() set. |
1439 | // We currently don't need to provide the same feature for tables because tables naturally have border features. |
1440 | ImGuiOldColumns* columns = (flags & ImGuiSeparatorFlags_SpanAllColumns) ? window->DC.CurrentColumns : NULL; |
1441 | if (columns) |
1442 | { |
1443 | x1 = window->Pos.x + window->DC.Indent.x; // Used to be Pos.x before 2023/10/03 |
1444 | x2 = window->Pos.x + window->Size.x; |
1445 | PushColumnsBackground(); |
1446 | } |
1447 | |
1448 | // We don't provide our width to the layout so that it doesn't get feed back into AutoFit |
1449 | // FIXME: This prevents ->CursorMaxPos based bounding box evaluation from working (e.g. TableEndCell) |
1450 | const float thickness_for_layout = (thickness == 1.0f) ? 0.0f : thickness; // FIXME: See 1.70/1.71 Separator() change: makes legacy 1-px separator not affect layout yet. Should change. |
1451 | const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y + thickness)); |
1452 | ItemSize(size: ImVec2(0.0f, thickness_for_layout)); |
1453 | |
1454 | if (ItemAdd(bb, id: 0)) |
1455 | { |
1456 | // Draw |
1457 | window->DrawList->AddRectFilled(p_min: bb.Min, p_max: bb.Max, col: GetColorU32(idx: ImGuiCol_Separator)); |
1458 | if (g.LogEnabled) |
1459 | LogRenderedText(ref_pos: &bb.Min, text: "--------------------------------\n" ); |
1460 | |
1461 | } |
1462 | if (columns) |
1463 | { |
1464 | PopColumnsBackground(); |
1465 | columns->LineMinY = window->DC.CursorPos.y; |
1466 | } |
1467 | } |
1468 | } |
1469 | |
1470 | void ImGui::Separator() |
1471 | { |
1472 | ImGuiContext& g = *GImGui; |
1473 | ImGuiWindow* window = g.CurrentWindow; |
1474 | if (window->SkipItems) |
1475 | return; |
1476 | |
1477 | // Those flags should eventually be configurable by the user |
1478 | // FIXME: We cannot g.Style.SeparatorTextBorderSize for thickness as it relates to SeparatorText() which is a decorated separator, not defaulting to 1.0f. |
1479 | ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal; |
1480 | |
1481 | // Only applies to legacy Columns() api as they relied on Separator() a lot. |
1482 | if (window->DC.CurrentColumns) |
1483 | flags |= ImGuiSeparatorFlags_SpanAllColumns; |
1484 | |
1485 | SeparatorEx(flags, thickness: 1.0f); |
1486 | } |
1487 | |
1488 | void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end, float ) |
1489 | { |
1490 | ImGuiContext& g = *GImGui; |
1491 | ImGuiWindow* window = g.CurrentWindow; |
1492 | ImGuiStyle& style = g.Style; |
1493 | |
1494 | const ImVec2 label_size = CalcTextSize(text: label, text_end: label_end, hide_text_after_double_hash: false); |
1495 | const ImVec2 pos = window->DC.CursorPos; |
1496 | const ImVec2 padding = style.SeparatorTextPadding; |
1497 | |
1498 | const float separator_thickness = style.SeparatorTextBorderSize; |
1499 | const ImVec2 min_size(label_size.x + extra_w + padding.x * 2.0f, ImMax(lhs: label_size.y + padding.y * 2.0f, rhs: separator_thickness)); |
1500 | const ImRect bb(pos, ImVec2(window->WorkRect.Max.x, pos.y + min_size.y)); |
1501 | const float text_baseline_y = ImTrunc(f: (bb.GetHeight() - label_size.y) * style.SeparatorTextAlign.y + 0.99999f); //ImMax(padding.y, ImTrunc((style.SeparatorTextSize - label_size.y) * 0.5f)); |
1502 | ItemSize(size: min_size, text_baseline_y); |
1503 | if (!ItemAdd(bb, id)) |
1504 | return; |
1505 | |
1506 | const float sep1_x1 = pos.x; |
1507 | const float sep2_x2 = bb.Max.x; |
1508 | const float seps_y = ImTrunc(f: (bb.Min.y + bb.Max.y) * 0.5f + 0.99999f); |
1509 | |
1510 | const float label_avail_w = ImMax(lhs: 0.0f, rhs: sep2_x2 - sep1_x1 - padding.x * 2.0f); |
1511 | const ImVec2 label_pos(pos.x + padding.x + ImMax(lhs: 0.0f, rhs: (label_avail_w - label_size.x - extra_w) * style.SeparatorTextAlign.x), pos.y + text_baseline_y); // FIXME-ALIGN |
1512 | |
1513 | // This allows using SameLine() to position something in the 'extra_w' |
1514 | window->DC.CursorPosPrevLine.x = label_pos.x + label_size.x; |
1515 | |
1516 | const ImU32 separator_col = GetColorU32(idx: ImGuiCol_Separator); |
1517 | if (label_size.x > 0.0f) |
1518 | { |
1519 | const float sep1_x2 = label_pos.x - style.ItemSpacing.x; |
1520 | const float sep2_x1 = label_pos.x + label_size.x + extra_w + style.ItemSpacing.x; |
1521 | if (sep1_x2 > sep1_x1 && separator_thickness > 0.0f) |
1522 | window->DrawList->AddLine(p1: ImVec2(sep1_x1, seps_y), p2: ImVec2(sep1_x2, seps_y), col: separator_col, thickness: separator_thickness); |
1523 | if (sep2_x2 > sep2_x1 && separator_thickness > 0.0f) |
1524 | window->DrawList->AddLine(p1: ImVec2(sep2_x1, seps_y), p2: ImVec2(sep2_x2, seps_y), col: separator_col, thickness: separator_thickness); |
1525 | if (g.LogEnabled) |
1526 | LogSetNextTextDecoration(prefix: "---" , NULL); |
1527 | RenderTextEllipsis(draw_list: window->DrawList, pos_min: label_pos, pos_max: ImVec2(bb.Max.x, bb.Max.y + style.ItemSpacing.y), clip_max_x: bb.Max.x, ellipsis_max_x: bb.Max.x, text: label, text_end: label_end, text_size_if_known: &label_size); |
1528 | } |
1529 | else |
1530 | { |
1531 | if (g.LogEnabled) |
1532 | LogText(fmt: "---" ); |
1533 | if (separator_thickness > 0.0f) |
1534 | window->DrawList->AddLine(p1: ImVec2(sep1_x1, seps_y), p2: ImVec2(sep2_x2, seps_y), col: separator_col, thickness: separator_thickness); |
1535 | } |
1536 | } |
1537 | |
1538 | void ImGui::SeparatorText(const char* label) |
1539 | { |
1540 | ImGuiWindow* window = GetCurrentWindow(); |
1541 | if (window->SkipItems) |
1542 | return; |
1543 | |
1544 | // The SeparatorText() vs SeparatorTextEx() distinction is designed to be considerate that we may want: |
1545 | // - allow separator-text to be draggable items (would require a stable ID + a noticeable highlight) |
1546 | // - this high-level entry point to allow formatting? (which in turns may require ID separate from formatted string) |
1547 | // - because of this we probably can't turn 'const char* label' into 'const char* fmt, ...' |
1548 | // Otherwise, we can decide that users wanting to drag this would layout a dedicated drag-item, |
1549 | // and then we can turn this into a format function. |
1550 | SeparatorTextEx(id: 0, label, label_end: FindRenderedTextEnd(text: label), extra_w: 0.0f); |
1551 | } |
1552 | |
1553 | // 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. |
1554 | 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, ImU32 bg_col) |
1555 | { |
1556 | ImGuiContext& g = *GImGui; |
1557 | ImGuiWindow* window = g.CurrentWindow; |
1558 | |
1559 | if (!ItemAdd(bb, id, NULL, extra_flags: ImGuiItemFlags_NoNav)) |
1560 | return false; |
1561 | |
1562 | // FIXME: AFAIK the only leftover reason for passing ImGuiButtonFlags_AllowOverlap here is |
1563 | // to allow caller of SplitterBehavior() to call SetItemAllowOverlap() after the item. |
1564 | // Nowadays we would instead want to use SetNextItemAllowOverlap() before the item. |
1565 | ImGuiButtonFlags button_flags = ImGuiButtonFlags_FlattenChildren; |
1566 | #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS |
1567 | button_flags |= ImGuiButtonFlags_AllowOverlap; |
1568 | #endif |
1569 | |
1570 | bool hovered, held; |
1571 | ImRect bb_interact = bb; |
1572 | bb_interact.Expand(amount: axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f)); |
1573 | ButtonBehavior(bb: bb_interact, id, out_hovered: &hovered, out_held: &held, flags: button_flags); |
1574 | if (hovered) |
1575 | g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect; // for IsItemHovered(), because bb_interact is larger than bb |
1576 | |
1577 | if (held || (hovered && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay)) |
1578 | SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW); |
1579 | |
1580 | ImRect bb_render = bb; |
1581 | if (held) |
1582 | { |
1583 | float mouse_delta = (g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min)[axis]; |
1584 | |
1585 | // Minimum pane size |
1586 | float size_1_maximum_delta = ImMax(lhs: 0.0f, rhs: *size1 - min_size1); |
1587 | float size_2_maximum_delta = ImMax(lhs: 0.0f, rhs: *size2 - min_size2); |
1588 | if (mouse_delta < -size_1_maximum_delta) |
1589 | mouse_delta = -size_1_maximum_delta; |
1590 | if (mouse_delta > size_2_maximum_delta) |
1591 | mouse_delta = size_2_maximum_delta; |
1592 | |
1593 | // Apply resize |
1594 | if (mouse_delta != 0.0f) |
1595 | { |
1596 | *size1 = ImMax(lhs: *size1 + mouse_delta, rhs: min_size1); |
1597 | *size2 = ImMax(lhs: *size2 - mouse_delta, rhs: min_size2); |
1598 | bb_render.Translate(d: (axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta)); |
1599 | MarkItemEdited(id); |
1600 | } |
1601 | } |
1602 | |
1603 | // Render at new position |
1604 | if (bg_col & IM_COL32_A_MASK) |
1605 | window->DrawList->AddRectFilled(p_min: bb_render.Min, p_max: bb_render.Max, col: bg_col, rounding: 0.0f); |
1606 | const ImU32 col = GetColorU32(idx: held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator); |
1607 | window->DrawList->AddRectFilled(p_min: bb_render.Min, p_max: bb_render.Max, col, rounding: 0.0f); |
1608 | |
1609 | return held; |
1610 | } |
1611 | |
1612 | static int IMGUI_CDECL ShrinkWidthItemComparer(const void* lhs, const void* rhs) |
1613 | { |
1614 | const ImGuiShrinkWidthItem* a = (const ImGuiShrinkWidthItem*)lhs; |
1615 | const ImGuiShrinkWidthItem* b = (const ImGuiShrinkWidthItem*)rhs; |
1616 | if (int d = (int)(b->Width - a->Width)) |
1617 | return d; |
1618 | return (b->Index - a->Index); |
1619 | } |
1620 | |
1621 | // Shrink excess width from a set of item, by removing width from the larger items first. |
1622 | // Set items Width to -1.0f to disable shrinking this item. |
1623 | void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess) |
1624 | { |
1625 | if (count == 1) |
1626 | { |
1627 | if (items[0].Width >= 0.0f) |
1628 | items[0].Width = ImMax(lhs: items[0].Width - width_excess, rhs: 1.0f); |
1629 | return; |
1630 | } |
1631 | ImQsort(base: items, count: (size_t)count, size_of_element: sizeof(ImGuiShrinkWidthItem), compare_func: ShrinkWidthItemComparer); |
1632 | int count_same_width = 1; |
1633 | while (width_excess > 0.0f && count_same_width < count) |
1634 | { |
1635 | while (count_same_width < count && items[0].Width <= items[count_same_width].Width) |
1636 | count_same_width++; |
1637 | float max_width_to_remove_per_item = (count_same_width < count && items[count_same_width].Width >= 0.0f) ? (items[0].Width - items[count_same_width].Width) : (items[0].Width - 1.0f); |
1638 | if (max_width_to_remove_per_item <= 0.0f) |
1639 | break; |
1640 | float width_to_remove_per_item = ImMin(lhs: width_excess / count_same_width, rhs: max_width_to_remove_per_item); |
1641 | for (int item_n = 0; item_n < count_same_width; item_n++) |
1642 | items[item_n].Width -= width_to_remove_per_item; |
1643 | width_excess -= width_to_remove_per_item * count_same_width; |
1644 | } |
1645 | |
1646 | // Round width and redistribute remainder |
1647 | // Ensure that e.g. the right-most tab of a shrunk tab-bar always reaches exactly at the same distance from the right-most edge of the tab bar separator. |
1648 | width_excess = 0.0f; |
1649 | for (int n = 0; n < count; n++) |
1650 | { |
1651 | float width_rounded = ImTrunc(f: items[n].Width); |
1652 | width_excess += items[n].Width - width_rounded; |
1653 | items[n].Width = width_rounded; |
1654 | } |
1655 | while (width_excess > 0.0f) |
1656 | for (int n = 0; n < count && width_excess > 0.0f; n++) |
1657 | { |
1658 | float width_to_add = ImMin(lhs: items[n].InitialWidth - items[n].Width, rhs: 1.0f); |
1659 | items[n].Width += width_to_add; |
1660 | width_excess -= width_to_add; |
1661 | } |
1662 | } |
1663 | |
1664 | //------------------------------------------------------------------------- |
1665 | // [SECTION] Widgets: ComboBox |
1666 | //------------------------------------------------------------------------- |
1667 | // - CalcMaxPopupHeightFromItemCount() [Internal] |
1668 | // - BeginCombo() |
1669 | // - BeginComboPopup() [Internal] |
1670 | // - EndCombo() |
1671 | // - BeginComboPreview() [Internal] |
1672 | // - EndComboPreview() [Internal] |
1673 | // - Combo() |
1674 | //------------------------------------------------------------------------- |
1675 | |
1676 | static float (int items_count) |
1677 | { |
1678 | ImGuiContext& g = *GImGui; |
1679 | if (items_count <= 0) |
1680 | return FLT_MAX; |
1681 | return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2); |
1682 | } |
1683 | |
1684 | bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags) |
1685 | { |
1686 | ImGuiContext& g = *GImGui; |
1687 | ImGuiWindow* window = GetCurrentWindow(); |
1688 | |
1689 | ImGuiNextWindowDataFlags backup_next_window_data_flags = g.NextWindowData.Flags; |
1690 | g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values |
1691 | if (window->SkipItems) |
1692 | return false; |
1693 | |
1694 | const ImGuiStyle& style = g.Style; |
1695 | const ImGuiID id = window->GetID(str: label); |
1696 | IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together |
1697 | if (flags & ImGuiComboFlags_WidthFitPreview) |
1698 | IM_ASSERT((flags & (ImGuiComboFlags_NoPreview | (ImGuiComboFlags)ImGuiComboFlags_CustomPreview)) == 0); |
1699 | |
1700 | const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight(); |
1701 | const ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: true); |
1702 | const float preview_width = ((flags & ImGuiComboFlags_WidthFitPreview) && (preview_value != NULL)) ? CalcTextSize(text: preview_value, NULL, hide_text_after_double_hash: true).x : 0.0f; |
1703 | const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : ((flags & ImGuiComboFlags_WidthFitPreview) ? (arrow_size + preview_width + style.FramePadding.x * 2.0f) : CalcItemWidth()); |
1704 | const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); |
1705 | const ImRect total_bb(bb.Min, bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); |
1706 | ItemSize(bb: total_bb, text_baseline_y: style.FramePadding.y); |
1707 | if (!ItemAdd(bb: total_bb, id, nav_bb: &bb)) |
1708 | return false; |
1709 | |
1710 | // Open on click |
1711 | bool hovered, held; |
1712 | bool pressed = ButtonBehavior(bb, id, out_hovered: &hovered, out_held: &held); |
1713 | const ImGuiID = ImHashStr(data: "##ComboPopup" , data_size: 0, seed: id); |
1714 | bool = IsPopupOpen(id: popup_id, popup_flags: ImGuiPopupFlags_None); |
1715 | if (pressed && !popup_open) |
1716 | { |
1717 | OpenPopupEx(id: popup_id, popup_flags: ImGuiPopupFlags_None); |
1718 | popup_open = true; |
1719 | } |
1720 | |
1721 | // Render shape |
1722 | const ImU32 frame_col = GetColorU32(idx: hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); |
1723 | const float value_x2 = ImMax(lhs: bb.Min.x, rhs: bb.Max.x - arrow_size); |
1724 | RenderNavHighlight(bb, id); |
1725 | if (!(flags & ImGuiComboFlags_NoPreview)) |
1726 | window->DrawList->AddRectFilled(p_min: bb.Min, p_max: ImVec2(value_x2, bb.Max.y), col: frame_col, rounding: style.FrameRounding, flags: (flags & ImGuiComboFlags_NoArrowButton) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersLeft); |
1727 | if (!(flags & ImGuiComboFlags_NoArrowButton)) |
1728 | { |
1729 | ImU32 bg_col = GetColorU32(idx: (popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button); |
1730 | ImU32 text_col = GetColorU32(idx: ImGuiCol_Text); |
1731 | window->DrawList->AddRectFilled(p_min: ImVec2(value_x2, bb.Min.y), p_max: bb.Max, col: bg_col, rounding: style.FrameRounding, flags: (w <= arrow_size) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersRight); |
1732 | if (value_x2 + arrow_size - style.FramePadding.x <= bb.Max.x) |
1733 | RenderArrow(draw_list: window->DrawList, pos: ImVec2(value_x2 + style.FramePadding.y, bb.Min.y + style.FramePadding.y), col: text_col, dir: ImGuiDir_Down, scale: 1.0f); |
1734 | } |
1735 | RenderFrameBorder(p_min: bb.Min, p_max: bb.Max, rounding: style.FrameRounding); |
1736 | |
1737 | // Custom preview |
1738 | if (flags & ImGuiComboFlags_CustomPreview) |
1739 | { |
1740 | g.ComboPreviewData.PreviewRect = ImRect(bb.Min.x, bb.Min.y, value_x2, bb.Max.y); |
1741 | IM_ASSERT(preview_value == NULL || preview_value[0] == 0); |
1742 | preview_value = NULL; |
1743 | } |
1744 | |
1745 | // Render preview and label |
1746 | if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview)) |
1747 | { |
1748 | if (g.LogEnabled) |
1749 | LogSetNextTextDecoration(prefix: "{" , suffix: "}" ); |
1750 | RenderTextClipped(pos_min: bb.Min + style.FramePadding, pos_max: ImVec2(value_x2, bb.Max.y), text: preview_value, NULL, NULL); |
1751 | } |
1752 | if (label_size.x > 0) |
1753 | RenderText(pos: ImVec2(bb.Max.x + style.ItemInnerSpacing.x, bb.Min.y + style.FramePadding.y), text: label); |
1754 | |
1755 | if (!popup_open) |
1756 | return false; |
1757 | |
1758 | g.NextWindowData.Flags = backup_next_window_data_flags; |
1759 | return BeginComboPopup(popup_id, bb, flags); |
1760 | } |
1761 | |
1762 | bool ImGui::(ImGuiID , const ImRect& bb, ImGuiComboFlags flags) |
1763 | { |
1764 | ImGuiContext& g = *GImGui; |
1765 | if (!IsPopupOpen(id: popup_id, popup_flags: ImGuiPopupFlags_None)) |
1766 | { |
1767 | g.NextWindowData.ClearFlags(); |
1768 | return false; |
1769 | } |
1770 | |
1771 | // Set popup size |
1772 | float w = bb.GetWidth(); |
1773 | if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint) |
1774 | { |
1775 | g.NextWindowData.SizeConstraintRect.Min.x = ImMax(lhs: g.NextWindowData.SizeConstraintRect.Min.x, rhs: w); |
1776 | } |
1777 | else |
1778 | { |
1779 | if ((flags & ImGuiComboFlags_HeightMask_) == 0) |
1780 | flags |= ImGuiComboFlags_HeightRegular; |
1781 | IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one |
1782 | int = -1; |
1783 | if (flags & ImGuiComboFlags_HeightRegular) popup_max_height_in_items = 8; |
1784 | else if (flags & ImGuiComboFlags_HeightSmall) popup_max_height_in_items = 4; |
1785 | else if (flags & ImGuiComboFlags_HeightLarge) popup_max_height_in_items = 20; |
1786 | ImVec2 constraint_min(0.0f, 0.0f), constraint_max(FLT_MAX, FLT_MAX); |
1787 | if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.x <= 0.0f) // Don't apply constraints if user specified a size |
1788 | constraint_min.x = w; |
1789 | if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.y <= 0.0f) |
1790 | constraint_max.y = CalcMaxPopupHeightFromItemCount(items_count: popup_max_height_in_items); |
1791 | SetNextWindowSizeConstraints(size_min: constraint_min, size_max: constraint_max); |
1792 | } |
1793 | |
1794 | // This is essentially a specialized version of BeginPopupEx() |
1795 | char name[16]; |
1796 | ImFormatString(buf: name, IM_ARRAYSIZE(name), fmt: "##Combo_%02d" , g.BeginComboDepth); // Recycle windows based on depth |
1797 | |
1798 | // Set position given a custom constraint (peak into expected window size so we can position it) |
1799 | // FIXME: This might be easier to express with an hypothetical SetNextWindowPosConstraints() function? |
1800 | // FIXME: This might be moved to Begin() or at least around the same spot where Tooltips and other Popups are calling FindBestWindowPosForPopupEx()? |
1801 | if (ImGuiWindow* = FindWindowByName(name)) |
1802 | if (popup_window->WasActive) |
1803 | { |
1804 | // Always override 'AutoPosLastDirection' to not leave a chance for a past value to affect us. |
1805 | ImVec2 size_expected = CalcWindowNextAutoFitSize(window: popup_window); |
1806 | popup_window->AutoPosLastDirection = (flags & ImGuiComboFlags_PopupAlignLeft) ? ImGuiDir_Left : ImGuiDir_Down; // Left = "Below, Toward Left", Down = "Below, Toward Right (default)" |
1807 | ImRect r_outer = GetPopupAllowedExtentRect(window: popup_window); |
1808 | ImVec2 pos = FindBestWindowPosForPopupEx(ref_pos: bb.GetBL(), size: size_expected, last_dir: &popup_window->AutoPosLastDirection, r_outer, r_avoid: bb, policy: ImGuiPopupPositionPolicy_ComboBox); |
1809 | SetNextWindowPos(pos); |
1810 | } |
1811 | |
1812 | // We don't use BeginPopupEx() solely because we have a custom name string, which we could make an argument to BeginPopupEx() |
1813 | ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoMove; |
1814 | PushStyleVar(idx: ImGuiStyleVar_WindowPadding, val: ImVec2(g.Style.FramePadding.x, g.Style.WindowPadding.y)); // Horizontally align ourselves with the framed text |
1815 | bool ret = Begin(name, NULL, flags: window_flags); |
1816 | PopStyleVar(); |
1817 | if (!ret) |
1818 | { |
1819 | EndPopup(); |
1820 | IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above |
1821 | return false; |
1822 | } |
1823 | g.BeginComboDepth++; |
1824 | return true; |
1825 | } |
1826 | |
1827 | void ImGui::EndCombo() |
1828 | { |
1829 | ImGuiContext& g = *GImGui; |
1830 | EndPopup(); |
1831 | g.BeginComboDepth--; |
1832 | } |
1833 | |
1834 | // Call directly after the BeginCombo/EndCombo block. The preview is designed to only host non-interactive elements |
1835 | // (Experimental, see GitHub issues: #1658, #4168) |
1836 | bool ImGui::BeginComboPreview() |
1837 | { |
1838 | ImGuiContext& g = *GImGui; |
1839 | ImGuiWindow* window = g.CurrentWindow; |
1840 | ImGuiComboPreviewData* preview_data = &g.ComboPreviewData; |
1841 | |
1842 | if (window->SkipItems || !(g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible)) |
1843 | return false; |
1844 | IM_ASSERT(g.LastItemData.Rect.Min.x == preview_data->PreviewRect.Min.x && g.LastItemData.Rect.Min.y == preview_data->PreviewRect.Min.y); // Didn't call after BeginCombo/EndCombo block or forgot to pass ImGuiComboFlags_CustomPreview flag? |
1845 | if (!window->ClipRect.Overlaps(r: preview_data->PreviewRect)) // Narrower test (optional) |
1846 | return false; |
1847 | |
1848 | // FIXME: This could be contained in a PushWorkRect() api |
1849 | preview_data->BackupCursorPos = window->DC.CursorPos; |
1850 | preview_data->BackupCursorMaxPos = window->DC.CursorMaxPos; |
1851 | preview_data->BackupCursorPosPrevLine = window->DC.CursorPosPrevLine; |
1852 | preview_data->BackupPrevLineTextBaseOffset = window->DC.PrevLineTextBaseOffset; |
1853 | preview_data->BackupLayout = window->DC.LayoutType; |
1854 | window->DC.CursorPos = preview_data->PreviewRect.Min + g.Style.FramePadding; |
1855 | window->DC.CursorMaxPos = window->DC.CursorPos; |
1856 | window->DC.LayoutType = ImGuiLayoutType_Horizontal; |
1857 | window->DC.IsSameLine = false; |
1858 | PushClipRect(clip_rect_min: preview_data->PreviewRect.Min, clip_rect_max: preview_data->PreviewRect.Max, intersect_with_current_clip_rect: true); |
1859 | |
1860 | return true; |
1861 | } |
1862 | |
1863 | void ImGui::EndComboPreview() |
1864 | { |
1865 | ImGuiContext& g = *GImGui; |
1866 | ImGuiWindow* window = g.CurrentWindow; |
1867 | ImGuiComboPreviewData* preview_data = &g.ComboPreviewData; |
1868 | |
1869 | // FIXME: Using CursorMaxPos approximation instead of correct AABB which we will store in ImDrawCmd in the future |
1870 | ImDrawList* draw_list = window->DrawList; |
1871 | if (window->DC.CursorMaxPos.x < preview_data->PreviewRect.Max.x && window->DC.CursorMaxPos.y < preview_data->PreviewRect.Max.y) |
1872 | if (draw_list->CmdBuffer.Size > 1) // Unlikely case that the PushClipRect() didn't create a command |
1873 | { |
1874 | draw_list->_CmdHeader.ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 1].ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 2].ClipRect; |
1875 | draw_list->_TryMergeDrawCmds(); |
1876 | } |
1877 | PopClipRect(); |
1878 | window->DC.CursorPos = preview_data->BackupCursorPos; |
1879 | window->DC.CursorMaxPos = ImMax(lhs: window->DC.CursorMaxPos, rhs: preview_data->BackupCursorMaxPos); |
1880 | window->DC.CursorPosPrevLine = preview_data->BackupCursorPosPrevLine; |
1881 | window->DC.PrevLineTextBaseOffset = preview_data->BackupPrevLineTextBaseOffset; |
1882 | window->DC.LayoutType = preview_data->BackupLayout; |
1883 | window->DC.IsSameLine = false; |
1884 | preview_data->PreviewRect = ImRect(); |
1885 | } |
1886 | |
1887 | // Getter for the old Combo() API: const char*[] |
1888 | static const char* Items_ArrayGetter(void* data, int idx) |
1889 | { |
1890 | const char* const* items = (const char* const*)data; |
1891 | return items[idx]; |
1892 | } |
1893 | |
1894 | // Getter for the old Combo() API: "item1\0item2\0item3\0" |
1895 | static const char* Items_SingleStringGetter(void* data, int idx) |
1896 | { |
1897 | const char* items_separated_by_zeros = (const char*)data; |
1898 | int items_count = 0; |
1899 | const char* p = items_separated_by_zeros; |
1900 | while (*p) |
1901 | { |
1902 | if (idx == items_count) |
1903 | break; |
1904 | p += strlen(s: p) + 1; |
1905 | items_count++; |
1906 | } |
1907 | return *p ? p : NULL; |
1908 | } |
1909 | |
1910 | // Old API, prefer using BeginCombo() nowadays if you can. |
1911 | bool ImGui::Combo(const char* label, int* current_item, const char* (*getter)(void* user_data, int idx), void* user_data, int items_count, int ) |
1912 | { |
1913 | ImGuiContext& g = *GImGui; |
1914 | |
1915 | // Call the getter to obtain the preview string which is a parameter to BeginCombo() |
1916 | const char* preview_value = NULL; |
1917 | if (*current_item >= 0 && *current_item < items_count) |
1918 | preview_value = getter(user_data, *current_item); |
1919 | |
1920 | // 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. |
1921 | if (popup_max_height_in_items != -1 && !(g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint)) |
1922 | SetNextWindowSizeConstraints(size_min: ImVec2(0, 0), size_max: ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(items_count: popup_max_height_in_items))); |
1923 | |
1924 | if (!BeginCombo(label, preview_value, flags: ImGuiComboFlags_None)) |
1925 | return false; |
1926 | |
1927 | // Display items |
1928 | // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed) |
1929 | bool value_changed = false; |
1930 | for (int i = 0; i < items_count; i++) |
1931 | { |
1932 | const char* item_text = getter(user_data, i); |
1933 | if (item_text == NULL) |
1934 | item_text = "*Unknown item*" ; |
1935 | |
1936 | PushID(int_id: i); |
1937 | const bool item_selected = (i == *current_item); |
1938 | if (Selectable(label: item_text, selected: item_selected) && *current_item != i) |
1939 | { |
1940 | value_changed = true; |
1941 | *current_item = i; |
1942 | } |
1943 | if (item_selected) |
1944 | SetItemDefaultFocus(); |
1945 | PopID(); |
1946 | } |
1947 | |
1948 | EndCombo(); |
1949 | |
1950 | if (value_changed) |
1951 | MarkItemEdited(id: g.LastItemData.ID); |
1952 | |
1953 | return value_changed; |
1954 | } |
1955 | |
1956 | // Combo box helper allowing to pass an array of strings. |
1957 | bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items) |
1958 | { |
1959 | const bool value_changed = Combo(label, current_item, getter: Items_ArrayGetter, user_data: (void*)items, items_count, popup_max_height_in_items: height_in_items); |
1960 | return value_changed; |
1961 | } |
1962 | |
1963 | // Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0" |
1964 | bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items) |
1965 | { |
1966 | int items_count = 0; |
1967 | const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open |
1968 | while (*p) |
1969 | { |
1970 | p += strlen(s: p) + 1; |
1971 | items_count++; |
1972 | } |
1973 | bool value_changed = Combo(label, current_item, getter: Items_SingleStringGetter, user_data: (void*)items_separated_by_zeros, items_count, popup_max_height_in_items: height_in_items); |
1974 | return value_changed; |
1975 | } |
1976 | |
1977 | #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS |
1978 | |
1979 | struct ImGuiGetNameFromIndexOldToNewCallbackData { void* UserData; bool (*OldCallback)(void*, int, const char**); }; |
1980 | static const char* ImGuiGetNameFromIndexOldToNewCallback(void* user_data, int idx) |
1981 | { |
1982 | ImGuiGetNameFromIndexOldToNewCallbackData* data = (ImGuiGetNameFromIndexOldToNewCallbackData*)user_data; |
1983 | const char* s = NULL; |
1984 | data->OldCallback(data->UserData, idx, &s); |
1985 | return s; |
1986 | } |
1987 | |
1988 | bool ImGui::ListBox(const char* label, int* current_item, bool (*old_getter)(void*, int, const char**), void* user_data, int items_count, int height_in_items) |
1989 | { |
1990 | ImGuiGetNameFromIndexOldToNewCallbackData old_to_new_data = { .UserData: user_data, .OldCallback: old_getter }; |
1991 | return ListBox(label, current_item, getter: ImGuiGetNameFromIndexOldToNewCallback, user_data: &old_to_new_data, items_count, height_in_items); |
1992 | } |
1993 | bool ImGui::Combo(const char* label, int* current_item, bool (*old_getter)(void*, int, const char**), void* user_data, int items_count, int ) |
1994 | { |
1995 | ImGuiGetNameFromIndexOldToNewCallbackData old_to_new_data = { .UserData: user_data, .OldCallback: old_getter }; |
1996 | return Combo(label, current_item, getter: ImGuiGetNameFromIndexOldToNewCallback, user_data: &old_to_new_data, items_count, popup_max_height_in_items); |
1997 | } |
1998 | |
1999 | #endif |
2000 | |
2001 | //------------------------------------------------------------------------- |
2002 | // [SECTION] Data Type and Data Formatting Helpers [Internal] |
2003 | //------------------------------------------------------------------------- |
2004 | // - DataTypeGetInfo() |
2005 | // - DataTypeFormatString() |
2006 | // - DataTypeApplyOp() |
2007 | // - DataTypeApplyFromText() |
2008 | // - DataTypeCompare() |
2009 | // - DataTypeClamp() |
2010 | // - GetMinimumStepAtDecimalPrecision |
2011 | // - RoundScalarWithFormat<>() |
2012 | //------------------------------------------------------------------------- |
2013 | |
2014 | static const ImGuiDataTypeInfo GDataTypeInfo[] = |
2015 | { |
2016 | { .Size: sizeof(char), .Name: "S8" , .PrintFmt: "%d" , .ScanFmt: "%d" }, // ImGuiDataType_S8 |
2017 | { .Size: sizeof(unsigned char), .Name: "U8" , .PrintFmt: "%u" , .ScanFmt: "%u" }, |
2018 | { .Size: sizeof(short), .Name: "S16" , .PrintFmt: "%d" , .ScanFmt: "%d" }, // ImGuiDataType_S16 |
2019 | { .Size: sizeof(unsigned short), .Name: "U16" , .PrintFmt: "%u" , .ScanFmt: "%u" }, |
2020 | { .Size: sizeof(int), .Name: "S32" , .PrintFmt: "%d" , .ScanFmt: "%d" }, // ImGuiDataType_S32 |
2021 | { .Size: sizeof(unsigned int), .Name: "U32" , .PrintFmt: "%u" , .ScanFmt: "%u" }, |
2022 | #ifdef _MSC_VER |
2023 | { sizeof(ImS64), "S64" , "%I64d" ,"%I64d" }, // ImGuiDataType_S64 |
2024 | { sizeof(ImU64), "U64" , "%I64u" ,"%I64u" }, |
2025 | #else |
2026 | { .Size: sizeof(ImS64), .Name: "S64" , .PrintFmt: "%lld" , .ScanFmt: "%lld" }, // ImGuiDataType_S64 |
2027 | { .Size: sizeof(ImU64), .Name: "U64" , .PrintFmt: "%llu" , .ScanFmt: "%llu" }, |
2028 | #endif |
2029 | { .Size: sizeof(float), .Name: "float" , .PrintFmt: "%.3f" ,.ScanFmt: "%f" }, // ImGuiDataType_Float (float are promoted to double in va_arg) |
2030 | { .Size: sizeof(double), .Name: "double" ,.PrintFmt: "%f" , .ScanFmt: "%lf" }, // ImGuiDataType_Double |
2031 | }; |
2032 | IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT); |
2033 | |
2034 | const ImGuiDataTypeInfo* ImGui::DataTypeGetInfo(ImGuiDataType data_type) |
2035 | { |
2036 | IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT); |
2037 | return &GDataTypeInfo[data_type]; |
2038 | } |
2039 | |
2040 | int ImGui::DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* p_data, const char* format) |
2041 | { |
2042 | // Signedness doesn't matter when pushing integer arguments |
2043 | if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32) |
2044 | return ImFormatString(buf, buf_size, fmt: format, *(const ImU32*)p_data); |
2045 | if (data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64) |
2046 | return ImFormatString(buf, buf_size, fmt: format, *(const ImU64*)p_data); |
2047 | if (data_type == ImGuiDataType_Float) |
2048 | return ImFormatString(buf, buf_size, fmt: format, *(const float*)p_data); |
2049 | if (data_type == ImGuiDataType_Double) |
2050 | return ImFormatString(buf, buf_size, fmt: format, *(const double*)p_data); |
2051 | if (data_type == ImGuiDataType_S8) |
2052 | return ImFormatString(buf, buf_size, fmt: format, *(const ImS8*)p_data); |
2053 | if (data_type == ImGuiDataType_U8) |
2054 | return ImFormatString(buf, buf_size, fmt: format, *(const ImU8*)p_data); |
2055 | if (data_type == ImGuiDataType_S16) |
2056 | return ImFormatString(buf, buf_size, fmt: format, *(const ImS16*)p_data); |
2057 | if (data_type == ImGuiDataType_U16) |
2058 | return ImFormatString(buf, buf_size, fmt: format, *(const ImU16*)p_data); |
2059 | IM_ASSERT(0); |
2060 | return 0; |
2061 | } |
2062 | |
2063 | void ImGui::DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, const void* arg1, const void* arg2) |
2064 | { |
2065 | IM_ASSERT(op == '+' || op == '-'); |
2066 | switch (data_type) |
2067 | { |
2068 | case ImGuiDataType_S8: |
2069 | if (op == '+') { *(ImS8*)output = ImAddClampOverflow(a: *(const ImS8*)arg1, b: *(const ImS8*)arg2, mn: IM_S8_MIN, mx: IM_S8_MAX); } |
2070 | if (op == '-') { *(ImS8*)output = ImSubClampOverflow(a: *(const ImS8*)arg1, b: *(const ImS8*)arg2, mn: IM_S8_MIN, mx: IM_S8_MAX); } |
2071 | return; |
2072 | case ImGuiDataType_U8: |
2073 | if (op == '+') { *(ImU8*)output = ImAddClampOverflow(a: *(const ImU8*)arg1, b: *(const ImU8*)arg2, mn: IM_U8_MIN, mx: IM_U8_MAX); } |
2074 | if (op == '-') { *(ImU8*)output = ImSubClampOverflow(a: *(const ImU8*)arg1, b: *(const ImU8*)arg2, mn: IM_U8_MIN, mx: IM_U8_MAX); } |
2075 | return; |
2076 | case ImGuiDataType_S16: |
2077 | if (op == '+') { *(ImS16*)output = ImAddClampOverflow(a: *(const ImS16*)arg1, b: *(const ImS16*)arg2, mn: IM_S16_MIN, mx: IM_S16_MAX); } |
2078 | if (op == '-') { *(ImS16*)output = ImSubClampOverflow(a: *(const ImS16*)arg1, b: *(const ImS16*)arg2, mn: IM_S16_MIN, mx: IM_S16_MAX); } |
2079 | return; |
2080 | case ImGuiDataType_U16: |
2081 | if (op == '+') { *(ImU16*)output = ImAddClampOverflow(a: *(const ImU16*)arg1, b: *(const ImU16*)arg2, mn: IM_U16_MIN, mx: IM_U16_MAX); } |
2082 | if (op == '-') { *(ImU16*)output = ImSubClampOverflow(a: *(const ImU16*)arg1, b: *(const ImU16*)arg2, mn: IM_U16_MIN, mx: IM_U16_MAX); } |
2083 | return; |
2084 | case ImGuiDataType_S32: |
2085 | if (op == '+') { *(ImS32*)output = ImAddClampOverflow(a: *(const ImS32*)arg1, b: *(const ImS32*)arg2, mn: IM_S32_MIN, mx: IM_S32_MAX); } |
2086 | if (op == '-') { *(ImS32*)output = ImSubClampOverflow(a: *(const ImS32*)arg1, b: *(const ImS32*)arg2, mn: IM_S32_MIN, mx: IM_S32_MAX); } |
2087 | return; |
2088 | case ImGuiDataType_U32: |
2089 | if (op == '+') { *(ImU32*)output = ImAddClampOverflow(a: *(const ImU32*)arg1, b: *(const ImU32*)arg2, mn: IM_U32_MIN, mx: IM_U32_MAX); } |
2090 | if (op == '-') { *(ImU32*)output = ImSubClampOverflow(a: *(const ImU32*)arg1, b: *(const ImU32*)arg2, mn: IM_U32_MIN, mx: IM_U32_MAX); } |
2091 | return; |
2092 | case ImGuiDataType_S64: |
2093 | if (op == '+') { *(ImS64*)output = ImAddClampOverflow(a: *(const ImS64*)arg1, b: *(const ImS64*)arg2, mn: IM_S64_MIN, mx: IM_S64_MAX); } |
2094 | if (op == '-') { *(ImS64*)output = ImSubClampOverflow(a: *(const ImS64*)arg1, b: *(const ImS64*)arg2, mn: IM_S64_MIN, mx: IM_S64_MAX); } |
2095 | return; |
2096 | case ImGuiDataType_U64: |
2097 | if (op == '+') { *(ImU64*)output = ImAddClampOverflow(a: *(const ImU64*)arg1, b: *(const ImU64*)arg2, mn: IM_U64_MIN, mx: IM_U64_MAX); } |
2098 | if (op == '-') { *(ImU64*)output = ImSubClampOverflow(a: *(const ImU64*)arg1, b: *(const ImU64*)arg2, mn: IM_U64_MIN, mx: IM_U64_MAX); } |
2099 | return; |
2100 | case ImGuiDataType_Float: |
2101 | if (op == '+') { *(float*)output = *(const float*)arg1 + *(const float*)arg2; } |
2102 | if (op == '-') { *(float*)output = *(const float*)arg1 - *(const float*)arg2; } |
2103 | return; |
2104 | case ImGuiDataType_Double: |
2105 | if (op == '+') { *(double*)output = *(const double*)arg1 + *(const double*)arg2; } |
2106 | if (op == '-') { *(double*)output = *(const double*)arg1 - *(const double*)arg2; } |
2107 | return; |
2108 | case ImGuiDataType_COUNT: break; |
2109 | } |
2110 | IM_ASSERT(0); |
2111 | } |
2112 | |
2113 | // User can input math operators (e.g. +100) to edit a numerical values. |
2114 | // NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess.. |
2115 | bool ImGui::DataTypeApplyFromText(const char* buf, ImGuiDataType data_type, void* p_data, const char* format) |
2116 | { |
2117 | while (ImCharIsBlankA(c: *buf)) |
2118 | buf++; |
2119 | if (!buf[0]) |
2120 | return false; |
2121 | |
2122 | // Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all. |
2123 | const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type); |
2124 | ImGuiDataTypeTempStorage data_backup; |
2125 | memcpy(dest: &data_backup, src: p_data, n: type_info->Size); |
2126 | |
2127 | // Sanitize format |
2128 | // - For float/double we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in, so force them into %f and %lf |
2129 | // - In theory could treat empty format as using default, but this would only cover rare/bizarre case of using InputScalar() + integer + format string without %. |
2130 | char format_sanitized[32]; |
2131 | if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) |
2132 | format = type_info->ScanFmt; |
2133 | else |
2134 | format = ImParseFormatSanitizeForScanning(fmt_in: format, fmt_out: format_sanitized, IM_ARRAYSIZE(format_sanitized)); |
2135 | |
2136 | // Small types need a 32-bit buffer to receive the result from scanf() |
2137 | int v32 = 0; |
2138 | if (sscanf(s: buf, format: format, type_info->Size >= 4 ? p_data : &v32) < 1) |
2139 | return false; |
2140 | if (type_info->Size < 4) |
2141 | { |
2142 | if (data_type == ImGuiDataType_S8) |
2143 | *(ImS8*)p_data = (ImS8)ImClamp(v: v32, mn: (int)IM_S8_MIN, mx: (int)IM_S8_MAX); |
2144 | else if (data_type == ImGuiDataType_U8) |
2145 | *(ImU8*)p_data = (ImU8)ImClamp(v: v32, mn: (int)IM_U8_MIN, mx: (int)IM_U8_MAX); |
2146 | else if (data_type == ImGuiDataType_S16) |
2147 | *(ImS16*)p_data = (ImS16)ImClamp(v: v32, mn: (int)IM_S16_MIN, mx: (int)IM_S16_MAX); |
2148 | else if (data_type == ImGuiDataType_U16) |
2149 | *(ImU16*)p_data = (ImU16)ImClamp(v: v32, mn: (int)IM_U16_MIN, mx: (int)IM_U16_MAX); |
2150 | else |
2151 | IM_ASSERT(0); |
2152 | } |
2153 | |
2154 | return memcmp(s1: &data_backup, s2: p_data, n: type_info->Size) != 0; |
2155 | } |
2156 | |
2157 | template<typename T> |
2158 | static int DataTypeCompareT(const T* lhs, const T* rhs) |
2159 | { |
2160 | if (*lhs < *rhs) return -1; |
2161 | if (*lhs > *rhs) return +1; |
2162 | return 0; |
2163 | } |
2164 | |
2165 | int ImGui::DataTypeCompare(ImGuiDataType data_type, const void* arg_1, const void* arg_2) |
2166 | { |
2167 | switch (data_type) |
2168 | { |
2169 | case ImGuiDataType_S8: return DataTypeCompareT<ImS8 >(lhs: (const ImS8* )arg_1, rhs: (const ImS8* )arg_2); |
2170 | case ImGuiDataType_U8: return DataTypeCompareT<ImU8 >(lhs: (const ImU8* )arg_1, rhs: (const ImU8* )arg_2); |
2171 | case ImGuiDataType_S16: return DataTypeCompareT<ImS16 >(lhs: (const ImS16* )arg_1, rhs: (const ImS16* )arg_2); |
2172 | case ImGuiDataType_U16: return DataTypeCompareT<ImU16 >(lhs: (const ImU16* )arg_1, rhs: (const ImU16* )arg_2); |
2173 | case ImGuiDataType_S32: return DataTypeCompareT<ImS32 >(lhs: (const ImS32* )arg_1, rhs: (const ImS32* )arg_2); |
2174 | case ImGuiDataType_U32: return DataTypeCompareT<ImU32 >(lhs: (const ImU32* )arg_1, rhs: (const ImU32* )arg_2); |
2175 | case ImGuiDataType_S64: return DataTypeCompareT<ImS64 >(lhs: (const ImS64* )arg_1, rhs: (const ImS64* )arg_2); |
2176 | case ImGuiDataType_U64: return DataTypeCompareT<ImU64 >(lhs: (const ImU64* )arg_1, rhs: (const ImU64* )arg_2); |
2177 | case ImGuiDataType_Float: return DataTypeCompareT<float >(lhs: (const float* )arg_1, rhs: (const float* )arg_2); |
2178 | case ImGuiDataType_Double: return DataTypeCompareT<double>(lhs: (const double*)arg_1, rhs: (const double*)arg_2); |
2179 | case ImGuiDataType_COUNT: break; |
2180 | } |
2181 | IM_ASSERT(0); |
2182 | return 0; |
2183 | } |
2184 | |
2185 | template<typename T> |
2186 | static bool DataTypeClampT(T* v, const T* v_min, const T* v_max) |
2187 | { |
2188 | // Clamp, both sides are optional, return true if modified |
2189 | if (v_min && *v < *v_min) { *v = *v_min; return true; } |
2190 | if (v_max && *v > *v_max) { *v = *v_max; return true; } |
2191 | return false; |
2192 | } |
2193 | |
2194 | bool ImGui::DataTypeClamp(ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max) |
2195 | { |
2196 | switch (data_type) |
2197 | { |
2198 | case ImGuiDataType_S8: return DataTypeClampT<ImS8 >(v: (ImS8* )p_data, v_min: (const ImS8* )p_min, v_max: (const ImS8* )p_max); |
2199 | case ImGuiDataType_U8: return DataTypeClampT<ImU8 >(v: (ImU8* )p_data, v_min: (const ImU8* )p_min, v_max: (const ImU8* )p_max); |
2200 | case ImGuiDataType_S16: return DataTypeClampT<ImS16 >(v: (ImS16* )p_data, v_min: (const ImS16* )p_min, v_max: (const ImS16* )p_max); |
2201 | case ImGuiDataType_U16: return DataTypeClampT<ImU16 >(v: (ImU16* )p_data, v_min: (const ImU16* )p_min, v_max: (const ImU16* )p_max); |
2202 | case ImGuiDataType_S32: return DataTypeClampT<ImS32 >(v: (ImS32* )p_data, v_min: (const ImS32* )p_min, v_max: (const ImS32* )p_max); |
2203 | case ImGuiDataType_U32: return DataTypeClampT<ImU32 >(v: (ImU32* )p_data, v_min: (const ImU32* )p_min, v_max: (const ImU32* )p_max); |
2204 | case ImGuiDataType_S64: return DataTypeClampT<ImS64 >(v: (ImS64* )p_data, v_min: (const ImS64* )p_min, v_max: (const ImS64* )p_max); |
2205 | case ImGuiDataType_U64: return DataTypeClampT<ImU64 >(v: (ImU64* )p_data, v_min: (const ImU64* )p_min, v_max: (const ImU64* )p_max); |
2206 | case ImGuiDataType_Float: return DataTypeClampT<float >(v: (float* )p_data, v_min: (const float* )p_min, v_max: (const float* )p_max); |
2207 | case ImGuiDataType_Double: return DataTypeClampT<double>(v: (double*)p_data, v_min: (const double*)p_min, v_max: (const double*)p_max); |
2208 | case ImGuiDataType_COUNT: break; |
2209 | } |
2210 | IM_ASSERT(0); |
2211 | return false; |
2212 | } |
2213 | |
2214 | static float GetMinimumStepAtDecimalPrecision(int decimal_precision) |
2215 | { |
2216 | 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 }; |
2217 | if (decimal_precision < 0) |
2218 | return FLT_MIN; |
2219 | return (decimal_precision < IM_ARRAYSIZE(min_steps)) ? min_steps[decimal_precision] : ImPow(x: 10.0f, y: (float)-decimal_precision); |
2220 | } |
2221 | |
2222 | template<typename TYPE> |
2223 | TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, TYPE v) |
2224 | { |
2225 | IM_UNUSED(data_type); |
2226 | IM_ASSERT(data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double); |
2227 | const char* fmt_start = ImParseFormatFindStart(format); |
2228 | if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string |
2229 | return v; |
2230 | |
2231 | // Sanitize format |
2232 | char fmt_sanitized[32]; |
2233 | ImParseFormatSanitizeForPrinting(fmt_in: fmt_start, fmt_out: fmt_sanitized, IM_ARRAYSIZE(fmt_sanitized)); |
2234 | fmt_start = fmt_sanitized; |
2235 | |
2236 | // Format value with our rounding, and read back |
2237 | char v_str[64]; |
2238 | ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v); |
2239 | const char* p = v_str; |
2240 | while (*p == ' ') |
2241 | p++; |
2242 | v = (TYPE)ImAtof(p); |
2243 | |
2244 | return v; |
2245 | } |
2246 | |
2247 | //------------------------------------------------------------------------- |
2248 | // [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc. |
2249 | //------------------------------------------------------------------------- |
2250 | // - DragBehaviorT<>() [Internal] |
2251 | // - DragBehavior() [Internal] |
2252 | // - DragScalar() |
2253 | // - DragScalarN() |
2254 | // - DragFloat() |
2255 | // - DragFloat2() |
2256 | // - DragFloat3() |
2257 | // - DragFloat4() |
2258 | // - DragFloatRange2() |
2259 | // - DragInt() |
2260 | // - DragInt2() |
2261 | // - DragInt3() |
2262 | // - DragInt4() |
2263 | // - DragIntRange2() |
2264 | //------------------------------------------------------------------------- |
2265 | |
2266 | // This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls) |
2267 | template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE> |
2268 | bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, ImGuiSliderFlags flags) |
2269 | { |
2270 | ImGuiContext& g = *GImGui; |
2271 | const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X; |
2272 | const bool is_clamped = (v_min < v_max); |
2273 | const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0; |
2274 | const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double); |
2275 | |
2276 | // Default tweak speed |
2277 | if (v_speed == 0.0f && is_clamped && (v_max - v_min < FLT_MAX)) |
2278 | v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio); |
2279 | |
2280 | // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings |
2281 | float adjust_delta = 0.0f; |
2282 | if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && IsMouseDragPastThreshold(button: 0, lock_threshold: g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR)) |
2283 | { |
2284 | adjust_delta = g.IO.MouseDelta[axis]; |
2285 | if (g.IO.KeyAlt) |
2286 | adjust_delta *= 1.0f / 100.0f; |
2287 | if (g.IO.KeyShift) |
2288 | adjust_delta *= 10.0f; |
2289 | } |
2290 | else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) |
2291 | { |
2292 | const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, default_value: 3) : 0; |
2293 | const bool tweak_slow = IsKeyDown(key: (g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakSlow : ImGuiKey_NavKeyboardTweakSlow); |
2294 | const bool tweak_fast = IsKeyDown(key: (g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast); |
2295 | const float tweak_factor = tweak_slow ? 1.0f / 1.0f : tweak_fast ? 10.0f : 1.0f; |
2296 | adjust_delta = GetNavTweakPressedAmount(axis) * tweak_factor; |
2297 | v_speed = ImMax(lhs: v_speed, rhs: GetMinimumStepAtDecimalPrecision(decimal_precision)); |
2298 | } |
2299 | adjust_delta *= v_speed; |
2300 | |
2301 | // For vertical drag we currently assume that Up=higher value (like we do with vertical sliders). This may become a parameter. |
2302 | if (axis == ImGuiAxis_Y) |
2303 | adjust_delta = -adjust_delta; |
2304 | |
2305 | // For logarithmic use our range is effectively 0..1 so scale the delta into that range |
2306 | if (is_logarithmic && (v_max - v_min < FLT_MAX) && ((v_max - v_min) > 0.000001f)) // Epsilon to avoid /0 |
2307 | adjust_delta /= (float)(v_max - v_min); |
2308 | |
2309 | // Clear current value on activation |
2310 | // 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. |
2311 | bool is_just_activated = g.ActiveIdIsJustActivated; |
2312 | bool is_already_past_limits_and_pushing_outward = is_clamped && ((*v >= v_max && adjust_delta > 0.0f) || (*v <= v_min && adjust_delta < 0.0f)); |
2313 | if (is_just_activated || is_already_past_limits_and_pushing_outward) |
2314 | { |
2315 | g.DragCurrentAccum = 0.0f; |
2316 | g.DragCurrentAccumDirty = false; |
2317 | } |
2318 | else if (adjust_delta != 0.0f) |
2319 | { |
2320 | g.DragCurrentAccum += adjust_delta; |
2321 | g.DragCurrentAccumDirty = true; |
2322 | } |
2323 | |
2324 | if (!g.DragCurrentAccumDirty) |
2325 | return false; |
2326 | |
2327 | TYPE v_cur = *v; |
2328 | FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f; |
2329 | |
2330 | float logarithmic_zero_epsilon = 0.0f; // Only valid when is_logarithmic is true |
2331 | const float zero_deadzone_halfsize = 0.0f; // Drag widgets have no deadzone (as it doesn't make sense) |
2332 | if (is_logarithmic) |
2333 | { |
2334 | // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound. |
2335 | const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, default_value: 3) : 1; |
2336 | logarithmic_zero_epsilon = ImPow(x: 0.1f, y: (float)decimal_precision); |
2337 | |
2338 | // Convert to parametric space, apply delta, convert back |
2339 | float v_old_parametric = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); |
2340 | float v_new_parametric = v_old_parametric + g.DragCurrentAccum; |
2341 | v_cur = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_new_parametric, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); |
2342 | v_old_ref_for_accum_remainder = v_old_parametric; |
2343 | } |
2344 | else |
2345 | { |
2346 | v_cur += (SIGNEDTYPE)g.DragCurrentAccum; |
2347 | } |
2348 | |
2349 | // Round to user desired precision based on format string |
2350 | if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat)) |
2351 | v_cur = RoundScalarWithFormatT<TYPE>(format, data_type, v_cur); |
2352 | |
2353 | // Preserve remainder after rounding has been applied. This also allow slow tweaking of values. |
2354 | g.DragCurrentAccumDirty = false; |
2355 | if (is_logarithmic) |
2356 | { |
2357 | // Convert to parametric space, apply delta, convert back |
2358 | float v_new_parametric = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); |
2359 | g.DragCurrentAccum -= (float)(v_new_parametric - v_old_ref_for_accum_remainder); |
2360 | } |
2361 | else |
2362 | { |
2363 | g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v); |
2364 | } |
2365 | |
2366 | // Lose zero sign for float/double |
2367 | if (v_cur == (TYPE)-0) |
2368 | v_cur = (TYPE)0; |
2369 | |
2370 | // Clamp values (+ handle overflow/wrap-around for integer types) |
2371 | if (*v != v_cur && is_clamped) |
2372 | { |
2373 | if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f && !is_floating_point)) |
2374 | v_cur = v_min; |
2375 | if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f && !is_floating_point)) |
2376 | v_cur = v_max; |
2377 | } |
2378 | |
2379 | // Apply result |
2380 | if (*v == v_cur) |
2381 | return false; |
2382 | *v = v_cur; |
2383 | return true; |
2384 | } |
2385 | |
2386 | bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) |
2387 | { |
2388 | // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert. |
2389 | IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flags! Has the 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead." ); |
2390 | |
2391 | ImGuiContext& g = *GImGui; |
2392 | if (g.ActiveId == id) |
2393 | { |
2394 | // Those are the things we can do easily outside the DragBehaviorT<> template, saves code generation. |
2395 | if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0]) |
2396 | ClearActiveID(); |
2397 | else if ((g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) |
2398 | ClearActiveID(); |
2399 | } |
2400 | if (g.ActiveId != id) |
2401 | return false; |
2402 | if ((g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly)) |
2403 | return false; |
2404 | |
2405 | switch (data_type) |
2406 | { |
2407 | case ImGuiDataType_S8: { ImS32 v32 = (ImS32)*(ImS8*)p_v; bool r = DragBehaviorT<ImS32, ImS32, float>(data_type: ImGuiDataType_S32, v: &v32, v_speed, v_min: p_min ? *(const ImS8*) p_min : IM_S8_MIN, v_max: p_max ? *(const ImS8*)p_max : IM_S8_MAX, format, flags); if (r) *(ImS8*)p_v = (ImS8)v32; return r; } |
2408 | case ImGuiDataType_U8: { ImU32 v32 = (ImU32)*(ImU8*)p_v; bool r = DragBehaviorT<ImU32, ImS32, float>(data_type: ImGuiDataType_U32, v: &v32, v_speed, v_min: p_min ? *(const ImU8*) p_min : IM_U8_MIN, v_max: p_max ? *(const ImU8*)p_max : IM_U8_MAX, format, flags); if (r) *(ImU8*)p_v = (ImU8)v32; return r; } |
2409 | case ImGuiDataType_S16: { ImS32 v32 = (ImS32)*(ImS16*)p_v; bool r = DragBehaviorT<ImS32, ImS32, float>(data_type: ImGuiDataType_S32, v: &v32, v_speed, v_min: p_min ? *(const ImS16*)p_min : IM_S16_MIN, v_max: p_max ? *(const ImS16*)p_max : IM_S16_MAX, format, flags); if (r) *(ImS16*)p_v = (ImS16)v32; return r; } |
2410 | case ImGuiDataType_U16: { ImU32 v32 = (ImU32)*(ImU16*)p_v; bool r = DragBehaviorT<ImU32, ImS32, float>(data_type: ImGuiDataType_U32, v: &v32, v_speed, v_min: p_min ? *(const ImU16*)p_min : IM_U16_MIN, v_max: p_max ? *(const ImU16*)p_max : IM_U16_MAX, format, flags); if (r) *(ImU16*)p_v = (ImU16)v32; return r; } |
2411 | case ImGuiDataType_S32: return DragBehaviorT<ImS32, ImS32, float >(data_type, v: (ImS32*)p_v, v_speed, v_min: p_min ? *(const ImS32* )p_min : IM_S32_MIN, v_max: p_max ? *(const ImS32* )p_max : IM_S32_MAX, format, flags); |
2412 | case ImGuiDataType_U32: return DragBehaviorT<ImU32, ImS32, float >(data_type, v: (ImU32*)p_v, v_speed, v_min: p_min ? *(const ImU32* )p_min : IM_U32_MIN, v_max: p_max ? *(const ImU32* )p_max : IM_U32_MAX, format, flags); |
2413 | case ImGuiDataType_S64: return DragBehaviorT<ImS64, ImS64, double>(data_type, v: (ImS64*)p_v, v_speed, v_min: p_min ? *(const ImS64* )p_min : IM_S64_MIN, v_max: p_max ? *(const ImS64* )p_max : IM_S64_MAX, format, flags); |
2414 | case ImGuiDataType_U64: return DragBehaviorT<ImU64, ImS64, double>(data_type, v: (ImU64*)p_v, v_speed, v_min: p_min ? *(const ImU64* )p_min : IM_U64_MIN, v_max: p_max ? *(const ImU64* )p_max : IM_U64_MAX, format, flags); |
2415 | case ImGuiDataType_Float: return DragBehaviorT<float, float, float >(data_type, v: (float*)p_v, v_speed, v_min: p_min ? *(const float* )p_min : -FLT_MAX, v_max: p_max ? *(const float* )p_max : FLT_MAX, format, flags); |
2416 | case ImGuiDataType_Double: return DragBehaviorT<double,double,double>(data_type, v: (double*)p_v, v_speed, v_min: p_min ? *(const double*)p_min : -DBL_MAX, v_max: p_max ? *(const double*)p_max : DBL_MAX, format, flags); |
2417 | case ImGuiDataType_COUNT: break; |
2418 | } |
2419 | IM_ASSERT(0); |
2420 | return false; |
2421 | } |
2422 | |
2423 | // Note: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a Drag widget, p_min and p_max are optional. |
2424 | // Read code of e.g. DragFloat(), DragInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly. |
2425 | bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) |
2426 | { |
2427 | ImGuiWindow* window = GetCurrentWindow(); |
2428 | if (window->SkipItems) |
2429 | return false; |
2430 | |
2431 | ImGuiContext& g = *GImGui; |
2432 | const ImGuiStyle& style = g.Style; |
2433 | const ImGuiID id = window->GetID(str: label); |
2434 | const float w = CalcItemWidth(); |
2435 | |
2436 | const ImVec2 label_size = CalcTextSize(text: label, NULL, hide_text_after_double_hash: true); |
2437 | const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); |
2438 | 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)); |
2439 | |
2440 | const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0; |
2441 | ItemSize(bb: total_bb, text_baseline_y: style.FramePadding.y); |
2442 | if (!ItemAdd(bb: total_bb, id, nav_bb: &frame_bb, extra_flags: temp_input_allowed ? ImGuiItemFlags_Inputable : 0)) |
2443 | return false; |
2444 | |
2445 | // Default format string when passing NULL |
2446 | if (format == NULL) |
2447 | format = DataTypeGetInfo(data_type)->PrintFmt; |
2448 | |
2449 | const bool hovered = ItemHoverable(bb: frame_bb, id, item_flags: g.LastItemData.InFlags); |
2450 | bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id); |
2451 | if (!temp_input_is_active) |
2452 | { |
2453 | // Tabbing or CTRL-clicking on Drag turns it into an InputText |
2454 | const bool clicked = hovered && IsMouseClicked(button: 0, owner_id: id); |
2455 | const bool double_clicked = (hovered && g.IO.MouseClickedCount[0] == 2 && TestKeyOwner(key: ImGuiKey_MouseLeft, owner_id: id)); |
2456 | const bool make_active = (clicked || double_clicked || g.NavActivateId == id); |
2457 | if (make_active && (clicked || double_clicked)) |
2458 | SetKeyOwner(key: ImGuiKey_MouseLeft, owner_id: id); |
2459 | if (make_active && temp_input_allowed) |
2460 | if ((clicked && g.IO.KeyCtrl) || double_clicked || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput))) |
2461 | temp_input_is_active = true; |
2462 | |
2463 | // (Optional) simple click (without moving) turns Drag into an InputText |
2464 | if (g.IO.ConfigDragClickToInputText && temp_input_allowed && !temp_input_is_active) |
2465 | if (g.ActiveId == id && hovered && g.IO.MouseReleased[0] && !IsMouseDragPastThreshold(button: 0, lock_threshold: g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR)) |
2466 | { |
2467 | g.NavActivateId = id; |
2468 | g.NavActivateFlags = ImGuiActivateFlags_PreferInput; |
2469 | temp_input_is_active = true; |
2470 | } |
2471 | |
2472 | if (make_active && !temp_input_is_active) |
2473 | { |
2474 | SetActiveID(id, window); |
2475 | SetFocusID(id, window); |
2476 | FocusWindow(window); |
2477 | g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); |
2478 | } |
2479 | } |
2480 | |
2481 | if (temp_input_is_active) |
2482 | { |
2483 | // Only clamp CTRL+Click input when ImGuiSliderFlags_AlwaysClamp is set |
2484 | const bool is_clamp_input = (flags & ImGuiSliderFlags_AlwaysClamp) != 0 && (p_min == NULL || p_max == NULL || DataTypeCompare(data_type, arg_1: p_min, arg_2: p_max) < 0); |
2485 | return TempInputScalar(bb: frame_bb, id, label, data_type, p_data, format, p_clamp_min: is_clamp_input ? p_min : NULL, p_clamp_max: is_clamp_input ? p_max : NULL); |
2486 | } |
2487 | |
2488 | // Draw frame |
2489 | const ImU32 frame_col = GetColorU32(idx: g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); |
2490 | RenderNavHighlight(bb: frame_bb, id); |
2491 | RenderFrame(p_min: frame_bb.Min, p_max: frame_bb.Max, fill_col: frame_col, border: true, rounding: style.FrameRounding); |
2492 | |
2493 | // Drag behavior |
2494 | const bool value_changed = DragBehavior(id, data_type, p_v: p_data, v_speed, p_min, p_max, format, flags); |
2495 | if (value_changed) |
2496 | MarkItemEdited(id); |
2497 | |
2498 | // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. |
2499 | char value_buf[64]; |
2500 | const char* value_buf_end = value_buf + DataTypeFormatString(buf: value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format); |
2501 | if (g.LogEnabled) |
2502 | LogSetNextTextDecoration(prefix: "{" , suffix: "}" ); |
2503 | RenderTextClipped(pos_min: frame_bb.Min, pos_max: frame_bb.Max, text: value_buf, text_end: value_buf_end, NULL, align: ImVec2(0.5f, 0.5f)); |
2504 | |
2505 | if (label_size.x > 0.0f) |
2506 | RenderText(pos: ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), text: label); |
2507 | |
2508 | IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0)); |
2509 | return value_changed; |
2510 | } |
2511 | |
2512 | bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) |
2513 | { |
2514 | ImGuiWindow* window = GetCurrentWindow(); |
2515 | if (window->SkipItems) |
2516 | return false; |
2517 | |
2518 | ImGuiContext& g = *GImGui; |
2519 | bool value_changed = false; |
2520 | BeginGroup(); |
2521 | PushID(str_id: label); |
2522 | PushMultiItemsWidths(components, width_full: CalcItemWidth()); |
2523 | size_t type_size = GDataTypeInfo[data_type].Size; |
2524 | for (int i = 0; i < components; i++) |
2525 | { |
2526 | PushID(int_id: i); |
2527 | if (i > 0) |
2528 | SameLine(offset_from_start_x: 0, spacing: g.Style.ItemInnerSpacing.x); |
2529 | value_changed |= DragScalar(label: "" , data_type, p_data, v_speed, p_min, p_max, format, flags); |
2530 | PopID(); |
2531 | PopItemWidth(); |
2532 | p_data = (void*)((char*)p_data + type_size); |
2533 | } |
2534 | PopID(); |
2535 | |
2536 | const char* label_end = FindRenderedTextEnd(text: label); |
2537 | if (label != label_end) |
2538 | { |
2539 | SameLine(offset_from_start_x: 0, spacing: g.Style.ItemInnerSpacing.x); |
2540 | TextEx(text: label, text_end: label_end); |
2541 | } |
2542 | |
2543 | EndGroup(); |
2544 | return value_changed; |
2545 | } |
2546 | |
2547 | bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) |
2548 | { |
2549 | return DragScalar(label, data_type: ImGuiDataType_Float, p_data: v, v_speed, p_min: &v_min, p_max: &v_max, format, flags); |
2550 | } |
2551 | |
2552 | bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) |
2553 | { |
2554 | return DragScalarN(label, data_type: ImGuiDataType_Float, p_data: v, components: 2, v_speed, p_min: &v_min, p_max: &v_max, format, flags); |
2555 | } |
2556 | |
2557 | bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) |
2558 | { |
2559 | return DragScalarN(label, data_type: ImGuiDataType_Float, p_data: v, components: 3, v_speed, p_min: &v_min, p_max: &v_max, format, flags); |
2560 | } |
2561 | |
2562 | bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) |
2563 | { |
2564 | return DragScalarN(label, data_type: ImGuiDataType_Float, p_data: v, components: 4, v_speed, p_min: &v_min, p_max: &v_max, format, flags); |
2565 | } |
2566 | |
2567 | // NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this. |
2568 | 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, ImGuiSliderFlags flags) |
2569 | { |
2570 | ImGuiWindow* window = GetCurrentWindow(); |
2571 | if (window->SkipItems) |
2572 | return false; |
2573 | |
2574 | ImGuiContext& g = *GImGui; |
2575 | PushID(str_id: label); |
2576 | BeginGroup(); |
2577 | PushMultiItemsWidths(components: 2, width_full: CalcItemWidth()); |
2578 | |
2579 | float min_min = (v_min >= v_max) ? -FLT_MAX : v_min; |
2580 | float min_max = (v_min >= v_max) ? *v_current_max : ImMin(lhs: v_max, rhs: *v_current_max); |
2581 | ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0); |
2582 | bool value_changed = DragScalar(label: "##min" , data_type: ImGuiDataType_Float, p_data: v_current_min, v_speed, p_min: &min_min, p_max: &min_max, format, flags: min_flags); |
2583 | PopItemWidth(); |
2584 | SameLine(offset_from_start_x: 0, spacing: g.Style.ItemInnerSpacing.x); |
2585 | |
2586 | float max_min = (v_min >= v_max) ? *v_current_min : ImMax(lhs: v_min, rhs: *v_current_min); |
2587 | float max_max = (v_min >= v_max) ? FLT_MAX : v_max; |
2588 | ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0); |
2589 | value_changed |= DragScalar(label: "##max" , data_type: ImGuiDataType_Float, p_data: v_current_max, v_speed, p_min: &max_min, p_max: &max_max, format: format_max ? format_max : format, flags: max_flags); |
2590 | PopItemWidth(); |
2591 | SameLine(offset_from_start_x: 0, spacing: g.Style.ItemInnerSpacing.x); |
2592 | |
2593 | TextEx(text: label, text_end: FindRenderedTextEnd(text: label)); |
2594 | EndGroup(); |
2595 | PopID(); |
2596 | |
2597 | return value_changed; |
2598 | } |
2599 | |
2600 | // NB: v_speed is float to allow adjusting the drag speed with more precision |
2601 | bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) |
2602 | { |
2603 | return DragScalar(label, data_type: ImGuiDataType_S32, p_data: v, v_speed, p_min: &v_min, p_max: &v_max, format, flags); |
2604 | } |
2605 | |
2606 | bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) |
2607 | { |
2608 | return DragScalarN(label, data_type: ImGuiDataType_S32, p_data: v, components: 2, v_speed, p_min: &v_min, p_max: &v_max, format, flags); |
2609 | } |
2610 | |
2611 | bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) |
2612 | { |
2613 | return DragScalarN(label, data_type: ImGuiDataType_S32, p_data: v, components: 3, v_speed, p_min: &v_min, p_max: &v_max, format, flags); |
2614 | } |
2615 | |
2616 | bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) |
2617 | { |
2618 | return DragScalarN(label, data_type: ImGuiDataType_S32, p_data: v, components: 4, v_speed, p_min: &v_min, p_max: &v_max, format, flags); |
2619 | } |
2620 | |
2621 | // NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this. |
2622 | 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, ImGuiSliderFlags flags) |
2623 | { |
2624 | ImGuiWindow* window = GetCurrentWindow(); |
2625 | if (window->SkipItems) |
2626 | return false; |
2627 | |
2628 | ImGuiContext& g = *GImGui; |
2629 | PushID(str_id: label); |
2630 | BeginGroup(); |
2631 | PushMultiItemsWidths(components: 2, width_full: CalcItemWidth()); |
2632 | |
2633 | int min_min = (v_min >= v_max) ? INT_MIN : v_min; |
2634 | int min_max = (v_min >= v_max) ? *v_current_max : ImMin(lhs: v_max, rhs: *v_current_max); |
2635 | ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0); |
2636 | bool value_changed = DragInt(label: "##min" , v: v_current_min, v_speed, v_min: min_min, v_max: min_max, format, flags: min_flags); |
2637 | PopItemWidth(); |
2638 | SameLine(offset_from_start_x: 0, spacing: g.Style.ItemInnerSpacing.x); |
2639 | |
2640 | int max_min = (v_min >= v_max) ? *v_current_min : ImMax(lhs: v_min, rhs: *v_current_min); |
2641 | int max_max = (v_min >= v_max) ? INT_MAX : v_max; |
2642 | ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0); |
2643 | value_changed |= DragInt(label: "##max" , v: v_current_max, v_speed, v_min: max_min, v_max: max_max, format: format_max ? format_max : format, flags: max_flags); |
2644 | PopItemWidth(); |
2645 | SameLine(offset_from_start_x: 0, spacing: g.Style.ItemInnerSpacing.x); |
2646 | |
2647 | TextEx(text: label, text_end: FindRenderedTextEnd(text: label)); |
2648 | EndGroup(); |
2649 | PopID(); |
2650 | |
2651 | return value_changed; |
2652 | } |
2653 | |
2654 | //------------------------------------------------------------------------- |
2655 | // [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc. |
2656 | //------------------------------------------------------------------------- |
2657 | // - ScaleRatioFromValueT<> [Internal] |
2658 | // - ScaleValueFromRatioT<> [Internal] |
2659 | // - SliderBehaviorT<>() [Internal] |
2660 | // - SliderBehavior() [Internal] |
2661 | // - SliderScalar() |
2662 | // - SliderScalarN() |
2663 | // - SliderFloat() |
2664 | // - SliderFloat2() |
2665 | // - SliderFloat3() |
2666 | // - SliderFloat4() |
2667 | // - SliderAngle() |
2668 | // - SliderInt() |
2669 | // - SliderInt2() |
2670 | // - SliderInt3() |
2671 | // - SliderInt4() |
2672 | // - VSliderScalar() |
2673 | // - VSliderFloat() |
2674 | // - VSliderInt() |
2675 | //------------------------------------------------------------------------- |
2676 | |
2677 | // Convert a value v in the output space of a slider into a parametric position on the slider itself (the logical opposite of ScaleValueFromRatioT) |
2678 | template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE> |
2679 | float ImGui::ScaleRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize) |
2680 | { |
2681 | if (v_min == v_max) |
2682 | return 0.0f; |
2683 | IM_UNUSED(data_type); |
2684 | |
2685 | const TYPE v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min); |
2686 | if (is_logarithmic) |
2687 | { |
2688 | bool flipped = v_max < v_min; |
2689 | |
2690 | if (flipped) // Handle the case where the range is backwards |
2691 | ImSwap(v_min, v_max); |
2692 | |
2693 | // Fudge min/max to avoid getting close to log(0) |
2694 | FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min; |
2695 | FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max; |
2696 | |
2697 | // Awkward special cases - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon) |
2698 | if ((v_min == 0.0f) && (v_max < 0.0f)) |
2699 | v_min_fudged = -logarithmic_zero_epsilon; |
2700 | else if ((v_max == 0.0f) && (v_min < 0.0f)) |
2701 | v_max_fudged = -logarithmic_zero_epsilon; |
2702 | |
2703 | float result; |
2704 | if (v_clamped <= v_min_fudged) |
2705 | result = 0.0f; // Workaround for values that are in-range but below our fudge |
2706 | else if (v_clamped >= v_max_fudged) |
2707 | result = 1.0f; // Workaround for values that are in-range but above our fudge |
2708 | else if ((v_min * v_max) < 0.0f) // Range crosses zero, so split into two portions |
2709 | { |
2710 | float zero_point_center = (-(float)v_min) / ((float)v_max - (float)v_min); // The zero point in parametric space. There's an argument we should take the logarithmic nature into account when calculating this, but for now this should do (and the most common case of a symmetrical range works fine) |
2711 | float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize; |
2712 | float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize; |
2713 | if (v == 0.0f) |
2714 | result = zero_point_center; // Special case for exactly zero |
2715 | else if (v < 0.0f) |
2716 | result = (1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / logarithmic_zero_epsilon) / ImLog(-v_min_fudged / logarithmic_zero_epsilon))) * zero_point_snap_L; |
2717 | else |
2718 | result = zero_point_snap_R + ((float)(ImLog((FLOATTYPE)v_clamped / logarithmic_zero_epsilon) / ImLog(v_max_fudged / logarithmic_zero_epsilon)) * (1.0f - zero_point_snap_R)); |
2719 | } |
2720 | else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider |
2721 | result = 1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / -v_max_fudged) / ImLog(-v_min_fudged / -v_max_fudged)); |
2722 | else |
2723 | result = (float)(ImLog((FLOATTYPE)v_clamped / v_min_fudged) / ImLog(v_max_fudged / v_min_fudged)); |
2724 | |
2725 | return flipped ? (1.0f - result) : result; |
2726 | } |
2727 | else |
2728 | { |
2729 | // Linear slider |
2730 | return (float)((FLOATTYPE)(SIGNEDTYPE)(v_clamped - v_min) / (FLOATTYPE)(SIGNEDTYPE)(v_max - v_min)); |
2731 | } |
2732 | } |
2733 | |
2734 | // Convert a parametric position on a slider into a value v in the output space (the logical opposite of ScaleRatioFromValueT) |
2735 | template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE> |
2736 | TYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize) |
2737 | { |
2738 | // We special-case the extents because otherwise our logarithmic fudging can lead to "mathematically correct" |
2739 | // but non-intuitive behaviors like a fully-left slider not actually reaching the minimum value. Also generally simpler. |
2740 | if (t <= 0.0f || v_min == v_max) |
2741 | return v_min; |
2742 | if (t >= 1.0f) |
2743 | return v_max; |
2744 | |
2745 | TYPE result = (TYPE)0; |
2746 | if (is_logarithmic) |
2747 | { |
2748 | // Fudge min/max to avoid getting silly results close to zero |
2749 | FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min; |
2750 | FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max; |
2751 | |
2752 | const bool flipped = v_max < v_min; // Check if range is "backwards" |
2753 | if (flipped) |
2754 | ImSwap(v_min_fudged, v_max_fudged); |
2755 | |
2756 | // Awkward special case - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon) |
2757 | if ((v_max == 0.0f) && (v_min < 0.0f)) |
2758 | v_max_fudged = -logarithmic_zero_epsilon; |
2759 | |
2760 | float t_with_flip = flipped ? (1.0f - t) : t; // t, but flipped if necessary to account for us flipping the range |
2761 | |
2762 | if ((v_min * v_max) < 0.0f) // Range crosses zero, so we have to do this in two parts |
2763 | { |
2764 | float zero_point_center = (-(float)ImMin(v_min, v_max)) / ImAbs(x: (float)v_max - (float)v_min); // The zero point in parametric space |
2765 | float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize; |
2766 | float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize; |
2767 | if (t_with_flip >= zero_point_snap_L && t_with_flip <= zero_point_snap_R) |
2768 | result = (TYPE)0.0f; // Special case to make getting exactly zero possible (the epsilon prevents it otherwise) |
2769 | else if ( |
---|