OXYGENHUB
◆ main openmsx / src / imgui / ImGuiCpp.hh
17.9 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#ifndef IMGUI_CPP_HH
2
#define IMGUI_CPP_HH
3
4
#include "narrow.hh"
5
#include "xrange.hh"
6
7
#include <imgui.h>
8
#include <imgui_internal.h>
9
10
#include <concepts>
11
12
// C++ wrapper around the Dear ImGui C-API
13
//
14
// Several Dear ImGui functions always need to be called in pairs. Some examples:
15
// * ImGui::Begin() always needs to be paired with ImGui::End().
16
// * Similar for ImGui::TreeNodeEx() and ImGui::TreePop().
17
//
18
// These wrapper ensure that you cannot do a wrong pairing.
19
//
20
// In case of the former 'End()' must always be called (whether 'Begin()'
21
// returned 'true' or 'false'). But for the latter 'TreePop()' may only be
22
// called when 'TreeNodeEx()' returned 'true'. This difference only exists for
23
// historic reason (and thus for backwards compatibility). The Dear ImGui
24
// documentation states that in some future version this difference will likely
25
// be removed. These wrappers already hide this difference today.
26
//
27
// Example usage:
28
// * This uses the original Dear ImGui C-API:
29
//     if (ImGui::Begin("my-window")) {
30
//         ImGui::BeginDisabled(b);
31
//         if (ImGui::TreeNodeEx("bla", flag)) {
32
//             ... more stuff
33
//             ImGui::TreePop();
34
//         }
35
//         if (ImGui::TreeNodeEx("foobar")) {
36
//             ... more stuff
37
//             ImGui::TreePop();
38
//         }
39
//         ImGui::EndDisabled();
40
//     }
41
//     ImGui::End();
42
//
43
// * This is the exact same example, but with our C++ wrapper functions:
44
//     im::Window("my-window", [&]{
45
//         im::Disabled(b, [&]{
46
//             im::TreeNode("bla", flag, [&]{
47
//                 ... more stuff
48
//             });
49
//             im::TreeNode("foobar", [&]{
50
//                 ... more stuff
51
//             });
52
//         });
53
//     });
54
//
55
// This wrapper is inspired by ideas from:
56
//   https://github.com/ocornut/imgui/issues/2096
57
//   https://github.com/ocornut/imgui/pull/2197
58
//   https://github.com/mnesarco/imgui_sugar
59
//   https://github.com/kfsone/imguiwrap
60
61
namespace im {
62
63
// im::Window(): wrapper around ImGui::Begin() / ImGui::End()
64
inline bool Window(const char* name, bool* p_open, ImGuiWindowFlags flags, std::invocable<> auto next)
65
{
66
	bool open = ImGui::Begin(name, p_open, flags);
67
	if (open) {
68
		next();
69
	}
70
	ImGui::End();
71
	return open;
72
}
73
inline bool Window(const char* name, bool* p_open, std::invocable<> auto next)
74
{
75
	return Window(name, p_open, 0, next);
76
}
77
inline bool Window(const char* name, std::invocable<> auto next)
78
{
79
	return Window(name, nullptr, 0, next);
80
}
81
82
struct WindowStatus {
83
	bool open = false; // [level] true <=> window is open
84
	bool do_raise = false; // [edge]  set to true when you want to raise this window
85
	                       //         gets automatically reset when done
86
	void raise() { do_raise = open = true; }
87
};
88
inline bool Window(const char* name, WindowStatus& status, ImGuiWindowFlags flags, std::invocable<> auto next)
89
{
90
	bool open = ImGui::Begin(name, &status.open, flags);
91
	if (open) {
92
		if (status.do_raise) {
93
			status.do_raise = false;
94
			if (!ImGui::IsWindowAppearing()) { // otherwise crash, viewport not yet initialized???
95
				// depending on the backend (e.g. Wayland) this could be nullptr
96
				if (auto* swf = ImGui::GetPlatformIO().Platform_SetWindowFocus) {
97
					swf(ImGui::GetWindowViewport());
98
				}
99
				// When window is inside the main viewport, this function is
100
				// needed to raise focus even if Platform_SetWindowFocus exists
101
				ImGui::SetWindowFocus();
102
			}
103
		}
104
		next();
105
	}
106
	ImGui::End();
107
	return open;
108
}
109
inline bool Window(const char* name, WindowStatus& status, std::invocable<> auto next)
110
{
111
	return Window(name, status, 0, next);
112
}
113
114
// im::Child(): wrapper around ImGui::BeginChild() / ImGui::EndChild()
115
inline bool Child(const char* str_id, const ImVec2& size, ImGuiChildFlags child_flags, ImGuiWindowFlags window_flags, std::invocable<> auto next)
116
{
117
	bool open = ImGui::BeginChild(str_id, size, child_flags, window_flags);
118
	if (open) {
119
		next();
120
	}
121
	ImGui::EndChild();
122
	return open;
123
}
124
inline bool Child(const char* str_id, const ImVec2& size, ImGuiChildFlags child_flags, std::invocable<> auto next)
125
{
126
	return Child(str_id, size, child_flags, 0, next);
127
}
128
inline bool Child(const char* str_id, const ImVec2& size, std::invocable<> auto next)
129
{
130
	return Child(str_id, size, 0, 0, next);
131
}
132
inline bool Child(const char* str_id, std::invocable<> auto next)
133
{
134
	return Child(str_id, {}, 0, 0, next);
135
}
136
137
// im::Font(): wrapper around ImGui::PushFont() / ImGui::PopFont()
138
inline void Font(ImFont* font, std::invocable<> auto next)
139
{
140
	ImGui::PushFont(font, font->LegacySize);
141
	next();
142
	ImGui::PopFont();
143
}
144
// Same functionality as im::Font(), but different usage (sometimes one sometimes the other is easier).
145
struct ScopedFont {
146
	explicit ScopedFont(ImFont* font) { ImGui::PushFont(font, font->LegacySize); }
147
	~ScopedFont() { ImGui::PopFont(); }
148
149
	ScopedFont(const ScopedFont&) = delete;
150
	ScopedFont(ScopedFont&&) = delete;
151
	ScopedFont& operator=(const ScopedFont&) = delete;
152
	ScopedFont& operator=(ScopedFont&&) = delete;
153
};
154
155
// im::StyleColor(): wrapper around ImGui::PushStyleColor() / ImGui::PopStyleColor()
156
// Can be called like:
157
//   im::StyleColor(
158
//      bool active,                  // _optional_ boolean parameter
159
//      ImGuiCol idx1, ImVec4 col1,   // followed by an arbitrary number of [idx, col] pairs
160
//      ImGuiCol idx2, ImU32 col2,    //   where col can either be 'ImVec4' or 'ImU32'
161
//      ...
162
//      std::invocable<> auto next);  // and finally a lambda that will be executed with these color changes
163
template<int N>
164
inline void StyleColor_impl(bool active, std::invocable<> auto next)
165
{
166
	next();
167
	if (active) ImGui::PopStyleColor(N);
168
}
169
template<int N, typename... Args>
170
inline void StyleColor_impl(bool active, ImGuiCol idx, ImVec4 col, Args&& ...args)
171
{
172
	if (active) ImGui::PushStyleColor(idx, col);
173
	StyleColor_impl<N>(active, std::forward<Args>(args)...);
174
}
175
template<int N, typename... Args>
176
inline void StyleColor_impl(bool active, ImGuiCol idx, ImU32 col, Args&& ...args)
177
{
178
	if (active) ImGui::PushStyleColor(idx, ImGui::ColorConvertU32ToFloat4(col));
179
	StyleColor_impl<N>(active, std::forward<Args>(args)...);
180
}
181
template<typename... Args>
182
inline void StyleColor(bool active, Args&& ...args)
183
{
184
	static constexpr auto N = sizeof...(args);
185
	static_assert(N >= 3);
186
	static_assert((N & 1) == 1);
187
	StyleColor_impl<(N - 1) / 2>(active, std::forward<Args>(args)...);
188
}
189
template<typename... Args>
190
inline void StyleColor(Args&& ...args)
191
{
192
	StyleColor(true, std::forward<Args>(args)...);
193
}
194
195
// im::StyleVar(): wrapper around ImGui::PushStyleVar() / ImGui::PopStyleVar()
196
// Add more overloads when needed
197
inline void StyleVar(ImGuiStyleVar idx, float val, std::invocable<> auto next)
198
{
199
	ImGui::PushStyleVar(idx, val);
200
	next();
201
	ImGui::PopStyleVar(1);
202
}
203
inline void StyleVar(ImGuiStyleVar idx, const ImVec2& val, std::invocable<> auto next)
204
{
205
	ImGui::PushStyleVar(idx, val);
206
	next();
207
	ImGui::PopStyleVar(1);
208
}
209
210
// im::ItemWidth(): wrapper around ImGui::PushItemWidth() / ImGui::PopItemWidth()
211
inline void ItemWidth(float item_width, std::invocable<> auto next)
212
{
213
	ImGui::PushItemWidth(item_width);
214
	next();
215
	ImGui::PopItemWidth();
216
}
217
218
// im::TextWrapPos(): wrapper around ImGui::PushTextWrapPos() / ImGui::PopTextWrapPos()
219
inline void TextWrapPos(float wrap_local_pos_x, std::invocable<> auto next)
220
{
221
	ImGui::PushTextWrapPos(wrap_local_pos_x);
222
	next();
223
	ImGui::PopTextWrapPos();
224
}
225
inline void TextWrapPos(std::invocable<> auto next)
226
{
227
	TextWrapPos(0.0f, next);
228
}
229
230
// im::Indent(): wrapper around ImGui::Indent() / ImGui::Unindent()
231
inline void Indent(float indent_w, std::invocable<> auto next)
232
{
233
	ImGui::Indent(indent_w);
234
	next();
235
	ImGui::Unindent(indent_w);
236
}
237
inline void Indent(std::invocable<> auto next)
238
{
239
	Indent(0.0f, next);
240
}
241
242
// im::Group(): wrapper around ImGui::BeginGroup() / ImGui::EndGroup()
243
inline void Group(std::invocable<> auto next)
244
{
245
	ImGui::BeginGroup();
246
	next();
247
	ImGui::EndGroup();
248
}
249
250
// im::ID(): wrapper around ImGui::PushID() / ImGui::PopID()
251
inline void ID(const char* str_id, std::invocable<> auto next)
252
{
253
	ImGui::PushID(str_id);
254
	next();
255
	ImGui::PopID();
256
}
257
inline void ID(const char* str_id_begin, const char* str_id_end, std::invocable<> auto next)
258
{
259
	ImGui::PushID(str_id_begin, str_id_end);
260
	next();
261
	ImGui::PopID();
262
}
263
inline void ID(const void* ptr_id, std::invocable<> auto next)
264
{
265
	ImGui::PushID(ptr_id);
266
	next();
267
	ImGui::PopID();
268
}
269
inline void ID(int int_id, std::invocable<> auto next)
270
{
271
	ImGui::PushID(int_id);
272
	next();
273
	ImGui::PopID();
274
}
275
inline void ID(const std::string& str, std::invocable<> auto next)
276
{
277
	const auto* begin = str.data();
278
	const auto* end = begin + str.size();
279
	ID(begin, end, next);
280
}
281
inline void ID(std::string_view str, std::invocable<> auto next)
282
{
283
	const auto* begin = str.data();
284
	const auto* end = begin + str.size();
285
	ID(begin, end, next);
286
}
287
288
// Note: consider using im::ListClipperID()
289
inline void ID_for_range(std::integral auto count, std::invocable<int> auto next)
290
{
291
	for (auto i : xrange(narrow<int>(count))) {
292
		ID(i, [&]{ next(i); });
293
	}
294
}
295
296
// im::Combo(): wrapper around ImGui::BeginCombo() / ImGui::EndCombo()
297
inline bool Combo(const char* label, const char* preview_value, ImGuiComboFlags flags, std::invocable<> auto next)
298
{
299
	bool open = ImGui::BeginCombo(label, preview_value, flags);
300
	if (open) {
301
		next();
302
		ImGui::EndCombo();
303
	}
304
	return open;
305
}
306
inline bool Combo(const char* label, const char* preview_value, std::invocable<> auto next)
307
{
308
	return Combo(label, preview_value, 0, next);
309
}
310
311
// im::TreeNode(): wrapper around ImGui::TreeNodeEx() / ImGui::TreePop()
312
inline bool TreeNode(const char* label, ImGuiTreeNodeFlags flags, std::invocable<> auto next)
313
{
314
	bool open = ImGui::TreeNodeEx(label, flags);
315
	if (open) {
316
		next();
317
		ImGui::TreePop();
318
	}
319
	return open;
320
}
321
inline bool TreeNode(const char* label, std::invocable<> auto next)
322
{
323
	return TreeNode(label, 0, next);
324
}
325
inline void TreeNode(const char* label, bool* p_open, std::invocable<> auto next)
326
{
327
	assert(p_open);
328
	ImGui::SetNextItemOpen(*p_open);
329
	int flags = 0;
330
	*p_open = ImGui::TreeNodeEx(label, flags);
331
	if (*p_open) {
332
		next();
333
		ImGui::TreePop();
334
	}
335
}
336
337
inline bool TreeNodeWithoutID(const char* label, ImGuiTreeNodeFlags flags, std::invocable<> auto next)
338
{
339
	ImGuiID id_before = ImGui::GetID(""); // capture the current ID stack top
340
341
	// TreeNodeEx pushes its own ID(s)
342
	bool open = ImGui::TreeNodeEx(label, flags);
343
	if (open) {
344
		ImGui::PushOverrideID(id_before); // restore ID stack for children (internal API)
345
		next();
346
		ImGui::PopID();
347
		ImGui::TreePop();
348
	}
349
	return open;
350
}
351
352
// im::ListBox(): wrapper around ImGui::BeginListBox() / ImGui::EndListBox()
353
inline bool ListBox(const char* label, const ImVec2& size, std::invocable<> auto next)
354
{
355
	bool open = ImGui::BeginListBox(label, size);
356
	if (open) {
357
		next();
358
		ImGui::EndListBox();
359
	}
360
	return open;
361
}
362
inline bool ListBox(const char* label, std::invocable<> auto next)
363
{
364
	return ListBox(label, {}, next);
365
}
366
367
// im::MainMenuBar(): wrapper around ImGui::BeginMenuBar() / ImGui::EndMenuBar()
368
inline bool MenuBar(std::invocable<> auto next)
369
{
370
	bool open = ImGui::BeginMenuBar();
371
	if (open) {
372
		next();
373
		ImGui::EndMenuBar();
374
	}
375
	return open;
376
}
377
378
// im::MainMenuBar(): wrapper around ImGui::BeginMainMenuBar() / ImGui::EndMainMenuBar()
379
inline bool MainMenuBar(std::invocable<> auto next)
380
{
381
	bool open = ImGui::BeginMainMenuBar();
382
	if (open) {
383
		next();
384
		ImGui::EndMainMenuBar();
385
	}
386
	return open;
387
}
388
389
// im::Menu(): wrapper around ImGui::BeginMenu() / ImGui::EndMenu()
390
inline bool Menu(const char* label, bool enabled, std::invocable<> auto next)
391
{
392
	bool open = ImGui::BeginMenu(label, enabled);
393
	if (open) {
394
		next();
395
		ImGui::EndMenu();
396
	}
397
	return open;
398
}
399
inline bool Menu(const char* label, std::invocable<> auto next)
400
{
401
	return Menu(label, true, next);
402
}
403
404
// im::Tooltip(): wrapper around ImGui::BeginTooltip() / ImGui::EndTooltip()
405
inline bool Tooltip(std::invocable<> auto next)
406
{
407
	bool open = ImGui::BeginTooltip();
408
	if (open) {
409
		next();
410
		ImGui::EndTooltip();
411
	}
412
	return open;
413
}
414
// im::Tooltip(): wrapper around ImGui::BeginItemTooltip() / ImGui::EndTooltip()
415
inline bool ItemTooltip(std::invocable<> auto next)
416
{
417
	bool open = ImGui::BeginItemTooltip();
418
	if (open) {
419
		next();
420
		ImGui::EndTooltip();
421
	}
422
	return open;
423
}
424
425
// im::Popup(): wrapper around ImGui::BeginPopup() / ImGui::EndPopup()
426
inline bool Popup(const char* str_id, ImGuiWindowFlags flags, std::invocable<> auto next)
427
{
428
	bool open = ImGui::BeginPopup(str_id, flags);
429
	if (open) {
430
		next();
431
		ImGui::EndPopup();
432
	}
433
	return open;
434
}
435
inline bool Popup(const char* str_id, std::invocable<> auto next)
436
{
437
	return Popup(str_id, 0, next);
438
}
439
440
// im::PopupModal(): wrapper around ImGui::BeginPopupModal() / ImGui::EndPopup()
441
inline bool PopupModal(const char* name, bool* p_open, ImGuiWindowFlags flags, std::invocable<> auto next)
442
{
443
	bool open = ImGui::BeginPopupModal(name, p_open, flags);
444
	if (open) {
445
		next();
446
		ImGui::EndPopup();
447
	}
448
	return open;
449
}
450
inline bool PopupModal(const char* name, bool* p_open, std::invocable<> auto next)
451
{
452
	return PopupModal(name, p_open, 0, next);
453
}
454
inline bool PopupModal(const char* name, std::invocable<> auto next)
455
{
456
	return PopupModal(name, nullptr, 0, next);
457
}
458
459
// im::PopupContextItem(): wrapper around ImGui::BeginPopupContextItem() / ImGui::EndPopup()
460
inline bool PopupContextItem(const char* str_id, ImGuiPopupFlags popup_flags, std::invocable<> auto next)
461
{
462
	bool open = ImGui::BeginPopupContextItem(str_id, popup_flags);
463
	if (open) {
464
		next();
465
		ImGui::EndPopup();
466
	}
467
	return open;
468
}
469
inline bool PopupContextItem(const char* str_id, std::invocable<> auto next)
470
{
471
	return PopupContextItem(str_id, ImGuiPopupFlags_MouseButtonRight, next);
472
}
473
inline bool PopupContextItem(std::invocable<> auto next)
474
{
475
	return PopupContextItem(nullptr, ImGuiPopupFlags_MouseButtonRight, next);
476
}
477
478
// im::PopupContextWindow(): wrapper around ImGui::BeginPopupContextWindow() / ImGui::EndPopup()
479
inline bool PopupContextWindow(const char* str_id, ImGuiPopupFlags popup_flags, std::invocable<> auto next)
480
{
481
	bool open = ImGui::BeginPopupContextWindow(str_id, popup_flags);
482
	if (open) {
483
		next();
484
		ImGui::EndPopup();
485
	}
486
	return open;
487
}
488
inline bool PopupContextWindow(const char* str_id, std::invocable<> auto next)
489
{
490
	return PopupContextWindow(str_id, ImGuiPopupFlags_None, next);
491
}
492
inline bool PopupContextWindow(std::invocable<> auto next)
493
{
494
	return PopupContextWindow(nullptr, ImGuiPopupFlags_None, next);
495
}
496
497
// im::Table(): wrapper around ImGui::BeginTable() / ImGui::EndTable()
498
inline bool Table(const char* str_id, int column, ImGuiTableFlags flags, const ImVec2& outer_size, float inner_width, std::invocable<> auto next)
499
{
500
	bool open = ImGui::BeginTable(str_id, column, flags, outer_size, inner_width);
501
	if (open) {
502
		next();
503
		ImGui::EndTable();
504
	}
505
	return open;
506
}
507
inline bool Table(const char* str_id, int column, ImGuiTableFlags flags, const ImVec2& outer_size, std::invocable<> auto next)
508
{
509
	return Table(str_id, column, flags, outer_size, 0.0f, next);
510
}
511
inline bool Table(const char* str_id, int column, ImGuiTableFlags flags, std::invocable<> auto next)
512
{
513
	return Table(str_id, column, flags, {}, 0.0f, next);
514
}
515
inline bool Table(const char* str_id, int column, std::invocable<> auto next)
516
{
517
	return Table(str_id, column, 0, {}, 0.0f, next);
518
}
519
520
// im::TabBar(): wrapper around ImGui::BeginTabBar() / ImGui::EndTabBar()
521
inline bool TabBar(const char* str_id, ImGuiTabBarFlags flags, std::invocable<> auto next)
522
{
523
	bool open = ImGui::BeginTabBar(str_id, flags);
524
	if (open) {
525
		next();
526
		ImGui::EndTabBar();
527
	}
528
	return open;
529
}
530
inline bool TabBar(const char* str_id, std::invocable<> auto next)
531
{
532
	return TabBar(str_id, 0, next);
533
}
534
535
// im::TabItem(): wrapper around ImGui::BeginTabItem() / ImGui::EndTabItem()
536
inline bool TabItem(const char* label, bool* p_open, ImGuiTabItemFlags flags, std::invocable<> auto next)
537
{
538
	bool open = ImGui::BeginTabItem(label, p_open, flags);
539
	if (open) {
540
		next();
541
		ImGui::EndTabItem();
542
	}
543
	return open;
544
}
545
inline bool TabItem(const char* label, bool* p_open, std::invocable<> auto next)
546
{
547
	return TabItem(label, p_open, 0, next);
548
}
549
inline bool TabItem(const char* label, std::invocable<> auto next)
550
{
551
	return TabItem(label, nullptr, 0, next);
552
}
553
554
// im::Disabled(): wrapper around ImGui::BeginDisabled() / ImGui::EndDisabled()
555
inline void Disabled(bool b, std::invocable<> auto next)
556
{
557
	ImGui::BeginDisabled(b);
558
	next();
559
	ImGui::EndDisabled();
560
}
561
562
// im::DisabledIndent(): combination of Disabled() and Indent()
563
inline void DisabledIndent(bool b, std::invocable<> auto next)
564
{
565
	ImGui::BeginDisabled(b);
566
	ImGui::Indent(); // TODO for now not configurable
567
	next();
568
	ImGui::Unindent();
569
	ImGui::EndDisabled();
570
}
571
572
// im::VisuallyDisabled(): similar to Disabled(), but only visually disable, still allow to interact normally
573
inline void VisuallyDisabled(bool b, std::invocable<> auto next)
574
{
575
	if (b) {
576
		const auto& style = ImGui::GetStyle();
577
		ImGui::PushStyleVar(ImGuiStyleVar_Alpha, style.Alpha * style.DisabledAlpha);
578
	}
579
	next();
580
	if (b) {
581
		ImGui::PopStyleVar(1);
582
	}
583
}
584
585
// im::ListClipper: wrapper around ImGuiListClipper
586
// hides the typical nested loop
587
inline void ListClipper(size_t count, int forceIndex, float lineHeight, std::invocable<int> auto next)
588
{
589
	ImGuiListClipper clipper; // only draw the actually visible lines
590
	clipper.Begin(narrow<int>(count), lineHeight);
591
	if (forceIndex > 0) clipper.IncludeItemByIndex(narrow<int>(forceIndex));
592
	while (clipper.Step()) {
593
		for (int i : xrange(clipper.DisplayStart, clipper.DisplayEnd)) {
594
			next(i);
595
		}
596
	}
597
}
598
inline void ListClipper(size_t count, int forceIndex, std::invocable<int> auto next)
599
{
600
	ListClipper(count, forceIndex, -1.0f, next);
601
}
602
inline void ListClipper(size_t count, std::invocable<int> auto next)
603
{
604
	ListClipper(count, -1, next);
605
}
606
607
// im::ListClipperID: combination of im::ListClipper() and im::ID()
608
inline void ListClipperID(size_t count, float items_height, std::invocable<int> auto next)
609
{
610
	ImGuiListClipper clipper; // only draw the actually visible lines
611
	clipper.Begin(narrow<int>(count), items_height);
612
	while (clipper.Step()) {
613
		for (int i : xrange(clipper.DisplayStart, clipper.DisplayEnd)) {
614
			ImGui::PushID(i);
615
			next(i);
616
			ImGui::PopID();
617
		}
618
	}
619
}
620
inline void ListClipperID(size_t count, std::invocable<int> auto next)
621
{
622
	ListClipperID(count, -1.0f, next);
623
}
624
625
} // namespace im
626
627
#endif