openMSX
ImGuiUtils.hh
Go to the documentation of this file.
1#ifndef IMGUI_UTILS_HH
2#define IMGUI_UTILS_HH
3
4#include "ImGuiCpp.hh"
5
6#include "Reactor.hh"
7
8#include "function_ref.hh"
9#include "ranges.hh"
10#include "strCat.hh"
11#include "StringOp.hh"
12#include "circular_buffer.hh"
13
14#include <imgui.h>
15#include <imgui_internal.h> // ImTextCharToUtf8
16
17#include <algorithm>
18#include <concepts>
19#include <span>
20#include <string>
21#include <string_view>
22#include <utility>
23
24namespace ImGui {
25
26inline void TextUnformatted(const std::string& str)
27{
28 const char* begin = str.data();
29 const char* end = begin + str.size();
31}
32inline void TextUnformatted(std::string_view str)
33{
34 const char* begin = str.data();
35 const char* end = begin + str.size();
37}
38
39inline auto CalcTextSize(std::string_view str)
40{
41 return ImGui::CalcTextSize(str.data(), str.data() + str.size());
42}
43
44template<typename... Ts>
45void StrCat(Ts&& ...ts)
46{
47 auto s = tmpStrCat(std::forward<Ts>(ts)...);
48 TextUnformatted(std::string_view(s));
49}
50
51inline void RightAlignText(std::string_view text, std::string_view maxWidthText)
52{
53 auto maxWidth = ImGui::CalcTextSize(maxWidthText).x;
54 auto actualWidth = ImGui::CalcTextSize(text).x;
55 if (auto spacing = maxWidth - actualWidth; spacing > 0.0f) {
56 auto pos = ImGui::GetCursorPosX();
57 ImGui::SetCursorPosX(pos + spacing);
58 }
60}
61
62} // namespace ImGui
63
64namespace openmsx {
65
66class BooleanSetting;
67class FloatSetting;
68class HotKey;
69class IntegerSetting;
70class Setting;
71class VideoSourceSetting;
72
74 std::string_view value;
75 std::string_view tip;
76};
77using EnumToolTips = std::span<const EnumToolTip>;
78
79inline void simpleToolTip(std::string_view desc)
80{
81 if (desc.empty()) return;
83 im::TextWrapPos(ImGui::GetFontSize() * 35.0f, [&]{
85 });
86 });
87}
88
89void simpleToolTip(std::invocable<> auto descFunc)
90{
91 if (!ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip)) return;
92 auto desc = std::invoke(descFunc);
93 if (desc.empty()) return;
94 im::Tooltip([&]{
95 im::TextWrapPos(ImGui::GetFontSize() * 35.0f, [&]{
97 });
98 });
99}
100
101void HelpMarker(std::string_view desc);
102
104 const char* label, gl::vec2 size, bool pressed,
105 std::invocable<gl::vec2 /*center*/, ImDrawList*> auto render)
106{
107 bool result = false;
108 im::StyleColor(pressed, ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_ButtonActive), [&]{
109 gl::vec2 topLeft = ImGui::GetCursorScreenPos();
110 gl::vec2 center = topLeft + size * 0.5f;
111 result = ImGui::Button(label, size);
112 render(center, ImGui::GetWindowDrawList());
113 });
114 return result;
115}
116
118 const char* label, gl::vec2 size,
119 std::invocable<gl::vec2 /*center*/, ImDrawList*> auto render)
120{
121 return ButtonWithCustomRendering(label, size, false, render);
122}
123
124inline bool ButtonWithCenteredGlyph(ImWchar glyph, gl::vec2 maxGlyphSize)
125{
126 std::array<char, 2 + 5> label = {'#', '#'};
127 ImTextCharToUtf8(&label[2], glyph);
128
129 const auto& style = ImGui::GetStyle();
130 auto buttonSize = maxGlyphSize + 2.0f* gl::vec2(style.FramePadding);
131
132 return ButtonWithCustomRendering(label.data(), buttonSize, [&](gl::vec2 center, ImDrawList* drawList) {
133 const auto* font = ImGui::GetFont();
134 auto texId = font->ContainerAtlas->TexID;
135 const auto* g = font->FindGlyph(glyph);
136 auto halfSize = gl::vec2{g->X1 - g->X0, g->Y1 - g->Y0} * 0.5f;
137 drawList->AddImage(texId, center - halfSize, center + halfSize, {g->U0, g->V0}, {g->U1, g->V1});
138 });
139};
140
142{
143 static constexpr gl::vec2 center{0.5f, 0.5f};
144 gl::vec2 windowPos = ImGui::GetWindowPos();
145 gl::vec2 windowSize = ImGui::GetWindowSize();
146 auto windowCenter = windowPos + center * windowSize;
147 ImGui::SetNextWindowPos(windowCenter, ImGuiCond_Appearing, center);
148}
149
151 std::string operator()(const Setting& setting) const;
152};
153
154bool Checkbox(const HotKey& hotkey, BooleanSetting& setting);
155bool Checkbox(const HotKey& hotkey, const char* label, BooleanSetting& setting, function_ref<std::string(const Setting&)> getTooltip = GetSettingDescription{});
156bool SliderInt(IntegerSetting& setting, ImGuiSliderFlags flags = 0);
157bool SliderInt(const char* label, IntegerSetting& setting, ImGuiSliderFlags flags = 0);
158bool SliderFloat(FloatSetting& setting, const char* format = "%.3f", ImGuiSliderFlags flags = 0);
159bool SliderFloat(const char* label, FloatSetting& setting, const char* format = "%.3f", ImGuiSliderFlags flags = 0);
160bool InputText(Setting& setting);
161bool InputText(const char* label, Setting& setting);
162void ComboBox(Setting& setting, EnumToolTips toolTips = {}); // must be an EnumSetting
163void ComboBox(const char* label, Setting& setting, EnumToolTips toolTips = {}); // must be an EnumSetting
164void ComboBox(const char* label, Setting& setting, function_ref<std::string(const std::string&)> displayValue, EnumToolTips toolTips = {});
165void ComboBox(VideoSourceSetting& setting);
166void ComboBox(const char* label, VideoSourceSetting& setting);
167
168const char* getComboString(int item, const char* itemsSeparatedByZeros);
169
170std::string formatTime(std::optional<double> time);
171float calculateFade(float current, float target, float period);
172
173template<int HexDigits>
174void comboHexSequence(const char* label, int* value, int mult, int max, int offset) {
175 assert(offset < mult);
176 *value &= ~(mult - 1);
177 // only apply offset in display, not in the actual value
178 auto preview = tmpStrCat("0x", hex_string<HexDigits>(*value | offset));
179 im::Combo(label, preview.c_str(), [&]{
180 for (int addr = 0; addr < max; addr += mult) {
181 if (auto str = tmpStrCat("0x", hex_string<HexDigits>(addr | offset));
182 ImGui::Selectable(str.c_str(), *value == addr)) {
183 *value = addr;
184 }
185 if (*value == addr) {
186 ImGui::SetItemDefaultFocus();
187 }
188 }
189 });
190};
191
192template<typename Range, typename Projection>
193void sortUpDown_T(Range& range, const ImGuiTableSortSpecs* sortSpecs, Projection proj) {
194 if (sortSpecs->Specs->SortDirection == ImGuiSortDirection_Descending) {
195 ranges::stable_sort(range, std::greater<>{}, proj);
196 } else {
197 ranges::stable_sort(range, std::less<>{}, proj);
198 }
199};
200template<typename Range, typename Projection>
201void sortUpDown_String(Range& range, const ImGuiTableSortSpecs* sortSpecs, Projection proj) {
202 if (sortSpecs->Specs->SortDirection == ImGuiSortDirection_Descending) {
204 } else {
206 }
207};
208
209
210[[nodiscard]] inline const std::string* getOptionalDictValue(
211 const std::vector<std::pair<std::string, std::string>>& info,
212 std::string_view key)
213{
214 auto it = ranges::find_if(info, [&](const auto& p) { return p.first == key; });
215 if (it == info.end()) return {};
216 return &it->second;
217}
218
219template<typename T> // 'MachineInfo' or 'ExtensionInfo', both have a 'configInfo' member
220[[nodiscard]] std::vector<std::string> getAllValuesFor(std::string_view key, const std::vector<T>& items)
221{
222 std::vector<std::string> result;
223 for (const auto& item : items) {
224 if (const auto* value = getOptionalDictValue(item.configInfo, key)) {
225 if (!contains(result, *value)) { // O(N^2), but that's fine
226 result.emplace_back(*value);
227 }
228 }
229 }
231 return result;
232}
233
234template<typename T>
235void displayFilterCombo(std::string& selection, zstring_view key, const std::vector<T>& items)
236{
237 im::Combo(key.c_str(), selection.empty() ? "--all--" : selection.c_str(), [&]{
238 if (ImGui::Selectable("--all--")) {
239 selection.clear();
240 }
241 for (const auto& type : getAllValuesFor(key, items)) {
242 if (ImGui::Selectable(type.c_str())) {
243 selection = type;
244 }
245 }
246 });
247}
248
249template<typename T>
250void applyComboFilter(std::string_view key, std::string_view value, const std::vector<T>& items, std::vector<size_t>& indices)
251{
252 if (value.empty()) return;
253 std::erase_if(indices, [&](auto idx) {
254 const auto& info = items[idx].configInfo;
255 const auto* val = getOptionalDictValue(info, key);
256 if (!val) return true; // remove items that don't have the key
257 return *val != value;
258 });
259}
260
261template<std::invocable<size_t> GetName>
262void filterIndices(std::string_view filterString, GetName getName, std::vector<size_t>& indices)
263{
264 if (filterString.empty()) return;
265 std::erase_if(indices, [&](auto idx) {
266 const auto& name = getName(idx);
267 return !ranges::all_of(StringOp::split_view<StringOp::EmptyParts::REMOVE>(filterString, ' '),
268 [&](auto part) { return StringOp::containsCaseInsensitive(name, part); });
269 });
270}
271
272template<typename T>
273void applyDisplayNameFilter(std::string_view filterString, const std::vector<T>& items, std::vector<size_t>& indices)
274{
275 filterIndices(filterString, [&](size_t idx) { return items[idx].displayName; }, indices);
276}
277
278template<typename T>
279void addRecentItem(circular_buffer<T>& recentItems, const T& item)
280{
281 if (auto it = ranges::find(recentItems, item); it != recentItems.end()) {
282 // was already present, move to front
283 std::rotate(recentItems.begin(), it, it + 1);
284 } else {
285 // new entry, add it, but possibly remove oldest entry
286 if (recentItems.full()) recentItems.pop_back();
287 recentItems.push_front(item);
288 }
289}
290
291// Similar to c++23 chunk_by(). Main difference is internal vs external iteration.
292template<typename Range, typename BinaryPred, typename Action>
293static void chunk_by(Range&& range, BinaryPred pred, Action action)
294{
295 auto it = std::begin(range);
296 auto last = std::end(range);
297 while (it != last) {
298 auto start = it;
299 auto prev = it++;
300 while (it != last && pred(*prev, *it)) {
301 prev = it++;
302 }
303 action(start, it);
304 }
305}
306
307std::string getShortCutForCommand(const HotKey& hotkey, std::string_view command);
308
309std::string getKeyChordName(ImGuiKeyChord keyChord);
310std::optional<ImGuiKeyChord> parseKeyChord(std::string_view name);
311
312// Read from VRAM-table, including mirroring behavior
313// shared between ImGuiCharacter, ImGuiSpriteViewer
315public:
316 VramTable(std::span<const uint8_t> vram_, bool planar_ = false)
317 : vram(vram_), planar(planar_) {}
318
319 void setRegister(unsigned value, unsigned extraLsbBits) {
320 registerMask = (value << extraLsbBits) | ~(~0u << extraLsbBits);
321 }
322 void setIndexSize(unsigned bits) {
323 indexMask = ~0u << bits;
324 }
325
326 [[nodiscard]] auto getAddress(unsigned index) const {
327 return registerMask & (indexMask | index);
328 }
329 [[nodiscard]] uint8_t operator[](unsigned index) const {
330 auto addr = getAddress(index);
331 if (planar) {
332 addr = ((addr << 16) | (addr >> 1)) & 0x1'FFFF;
333 }
334 return vram[addr];
335 }
336private:
337 std::span<const uint8_t> vram;
338 unsigned registerMask = 0;
339 unsigned indexMask = 0;
340 bool planar = false;
341};
342
343enum class imColor : unsigned {
345 BLACK,
346 WHITE,
347 GRAY,
348 YELLOW,
349 RED_BG, // red background (transparent)
350 YELLOW_BG, // yellow background (transparent)
351
352 TEXT,
354
355 ERROR,
356 WARNING,
357
358 COMMENT, // syntax highlighting in the console
359 VARIABLE,
360 LITERAL,
361 PROC,
362 OPERATOR,
363
364 KEY_ACTIVE, // virtual keyboard
366
368};
369inline std::array<ImU32, size_t(imColor::NUM_COLORS)> imColors;
370
371void setColors(int style);
372
373inline ImU32 getColor(imColor col) {
374 assert(col < imColor::NUM_COLORS);
375 return imColors[size_t(col)];
376}
377
378} // namespace openmsx
379
380#endif
BaseSetting * setting
int g
Circular buffer class, based on boost::circular_buffer/.
void push_front(T2 &&t)
void setRegister(unsigned value, unsigned extraLsbBits)
auto getAddress(unsigned index) const
VramTable(std::span< const uint8_t > vram_, bool planar_=false)
uint8_t operator[](unsigned index) const
void setIndexSize(unsigned bits)
Like std::string_view, but with the extra guarantee that it refers to a zero-terminated string.
constexpr const char * c_str() const
const char * ImTextCharToUtf8(char out_buf[5], unsigned int c)
Definition imgui.cc:2500
void StrCat(Ts &&...ts)
Definition ImGuiUtils.hh:45
auto CalcTextSize(std::string_view str)
Definition ImGuiUtils.hh:39
void TextUnformatted(const std::string &str)
Definition ImGuiUtils.hh:26
void RightAlignText(std::string_view text, std::string_view maxWidthText)
Definition ImGuiUtils.hh:51
bool containsCaseInsensitive(std::string_view haystack, std::string_view needle)
Definition StringOp.hh:181
vecN< 2, float > vec2
Definition gl_vec.hh:382
void Combo(const char *label, const char *preview_value, ImGuiComboFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:289
void StyleColor(bool active, Args &&...args)
Definition ImGuiCpp.hh:175
void TextWrapPos(float wrap_local_pos_x, std::invocable<> auto next)
Definition ImGuiCpp.hh:212
void Tooltip(std::invocable<> auto next)
Definition ImGuiCpp.hh:374
void ItemTooltip(std::invocable<> auto next)
Definition ImGuiCpp.hh:382
This file implemented 3 utility functions:
Definition Autofire.cc:11
bool Checkbox(const HotKey &hotKey, BooleanSetting &setting)
Definition ImGuiUtils.cc:58
std::span< const EnumToolTip > EnumToolTips
Definition ImGuiUtils.hh:77
void centerNextWindowOverCurrent()
void filterIndices(std::string_view filterString, GetName getName, std::vector< size_t > &indices)
void ComboBox(const char *label, Setting &setting, function_ref< std::string(const std::string &)> displayValue, EnumToolTips toolTips)
void applyComboFilter(std::string_view key, std::string_view value, const std::vector< T > &items, std::vector< size_t > &indices)
void addRecentItem(circular_buffer< T > &recentItems, const T &item)
void sortUpDown_String(Range &range, const ImGuiTableSortSpecs *sortSpecs, Projection proj)
void simpleToolTip(std::string_view desc)
Definition ImGuiUtils.hh:79
std::vector< std::string > getAllValuesFor(std::string_view key, const std::vector< T > &items)
std::string getShortCutForCommand(const HotKey &hotkey, std::string_view command)
std::array< ImU32, size_t(imColor::NUM_COLORS)> imColors
void sortUpDown_T(Range &range, const ImGuiTableSortSpecs *sortSpecs, Projection proj)
void displayFilterCombo(std::string &selection, zstring_view key, const std::vector< T > &items)
void HelpMarker(std::string_view desc)
Definition ImGuiUtils.cc:23
const std::string * getOptionalDictValue(const std::vector< std::pair< std::string, std::string > > &info, std::string_view key)
ImU32 getColor(imColor col)
std::optional< ImGuiKeyChord > parseKeyChord(std::string_view name)
const char * getComboString(int item, const char *itemsSeparatedByZeros)
void setColors(int style)
std::string getKeyChordName(ImGuiKeyChord keyChord)
void comboHexSequence(const char *label, int *value, int mult, int max, int offset)
void applyDisplayNameFilter(std::string_view filterString, const std::vector< T > &items, std::vector< size_t > &indices)
bool ButtonWithCenteredGlyph(ImWchar glyph, gl::vec2 maxGlyphSize)
std::string formatTime(std::optional< double > time)
float calculateFade(float current, float target, float period)
bool ButtonWithCustomRendering(const char *label, gl::vec2 size, bool pressed, std::invocable< gl::vec2, ImDrawList * > auto render)
constexpr bool all_of(InputRange &&range, UnaryPredicate pred)
Definition ranges.hh:188
auto find_if(InputRange &&range, UnaryPredicate pred)
Definition ranges.hh:175
void stable_sort(RandomAccessRange &&range)
Definition ranges.hh:78
auto find(InputRange &&range, const T &value)
Definition ranges.hh:162
constexpr void sort(RandomAccessRange &&range)
Definition ranges.hh:51
constexpr bool contains(ITER first, ITER last, const VAL &val)
Check if a range contains a given value, using linear search.
Definition stl.hh:35
TemporaryString tmpStrCat(Ts &&... ts)
Definition strCat.hh:742
std::string_view value
Definition ImGuiUtils.hh:74
std::string_view tip
Definition ImGuiUtils.hh:75
constexpr auto begin(const zstring_view &x)
constexpr auto end(const zstring_view &x)