openMSX
ImGuiUtils.cc
Go to the documentation of this file.
1#include "ImGuiUtils.hh"
2
3#include "ImGuiCpp.hh"
4
5#include "BooleanSetting.hh"
6#include "EnumSetting.hh"
7#include "HotKey.hh"
8#include "IntegerSetting.hh"
9#include "FloatSetting.hh"
10#include "VideoSourceSetting.hh"
11#include "KeyMappings.hh"
12
13#include "ranges.hh"
14
15#include <imgui.h>
16#include <imgui_stdlib.h>
17#include <SDL.h>
18
19#include <variant>
20
21namespace openmsx {
22
23void HelpMarker(std::string_view desc)
24{
25 ImGui::SameLine();
26 ImGui::TextDisabled("(?)");
27 simpleToolTip(desc);
28}
29
31{
32 return std::string(setting.getDescription());
33}
34
35template<std::invocable<const Setting&> GetTooltip = GetSettingDescription>
36static void settingStuff(Setting& setting, GetTooltip getTooltip = {})
37{
38 simpleToolTip([&] { return getTooltip(setting); });
40 auto defaultValue = setting.getDefaultValue();
41 auto defaultString = defaultValue.getString();
42 ImGui::StrCat("Default value: ", defaultString);
43 if (defaultString.empty()) {
44 ImGui::SameLine();
45 ImGui::TextDisabled("<empty>");
46 }
47 if (ImGui::Button("Restore default")) {
48 try {
49 setting.setValue(defaultValue);
50 } catch (MSXException&) {
51 // ignore
52 }
53 ImGui::CloseCurrentPopup();
54 }
55 });
56}
57
59{
60 std::string name(setting.getBaseName());
61 return Checkbox(hotKey, name.c_str(), setting);
62}
63bool Checkbox(const HotKey& hotKey, const char* label, BooleanSetting& setting, function_ref<std::string(const Setting&)> getTooltip)
64{
65 bool value = setting.getBoolean();
66 bool changed = ImGui::Checkbox(label, &value);
67 try {
68 if (changed) setting.setBoolean(value);
69 } catch (MSXException&) {
70 // ignore
71 }
72 settingStuff(setting, getTooltip);
73
74 ImGui::SameLine();
75 auto shortCut = getShortCutForCommand(hotKey, strCat("toggle ", setting.getBaseName()));
76 auto spacing = std::max(0.0f, ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(shortCut).x);
77 ImGui::SameLine(0.0f, spacing);
78 ImGui::TextDisabled("%s", shortCut.c_str());
79
80 return changed;
81}
82
83bool SliderInt(IntegerSetting& setting, ImGuiSliderFlags flags)
84{
85 std::string name(setting.getBaseName());
86 return SliderInt(name.c_str(), setting, flags);
87}
88bool SliderInt(const char* label, IntegerSetting& setting, ImGuiSliderFlags flags)
89{
90 int value = setting.getInt();
91 int min = setting.getMinValue();
92 int max = setting.getMaxValue();
93 bool changed = ImGui::SliderInt(label, &value, min, max, "%d", flags);
94 try {
95 if (changed) setting.setInt(value);
96 } catch (MSXException&) {
97 // ignore
98 }
99 settingStuff(setting);
100 return changed;
101}
102
103bool SliderFloat(FloatSetting& setting, const char* format, ImGuiSliderFlags flags)
104{
105 std::string name(setting.getBaseName());
106 return SliderFloat(name.c_str(), setting, format, flags);
107}
108bool SliderFloat(const char* label, FloatSetting& setting, const char* format, ImGuiSliderFlags flags)
109{
110 float value = setting.getFloat();
111 float min = narrow_cast<float>(setting.getMinValue());
112 float max = narrow_cast<float>(setting.getMaxValue());
113 bool changed = ImGui::SliderFloat(label, &value, min, max, format, flags);
114 try {
115 if (changed) setting.setFloat(value);
116 } catch (MSXException&) {
117 // ignore
118 }
119 settingStuff(setting);
120 return changed;
121}
122
124{
125 std::string name(setting.getBaseName());
126 return InputText(name.c_str(), setting);
127}
128bool InputText(const char* label, Setting& setting)
129{
130 auto value = std::string(setting.getValue().getString());
131 bool changed = ImGui::InputText(label, &value, ImGuiInputTextFlags_EnterReturnsTrue) || ImGui::IsItemDeactivatedAfterEdit();
132 try {
133 if (changed) setting.setValue(TclObject(value));
134 } catch (MSXException&) {
135 // ignore
136 }
137 settingStuff(setting);
138 return changed;
139}
140
141void ComboBox(const char* label, Setting& setting, function_ref<std::string(const std::string&)> displayValue, EnumToolTips toolTips)
142{
143 const auto* enumSetting = dynamic_cast<const EnumSettingBase*>(&setting);
144 assert(enumSetting);
145 auto current = setting.getValue().getString();
146 im::Combo(label, current.c_str(), [&]{
147 for (const auto& entry : enumSetting->getMap()) {
148 bool selected = entry.name == current;
149 if (const auto& display = displayValue(entry.name);
150 ImGui::Selectable(display.c_str(), selected)) {
151 try {
152 setting.setValue(TclObject(entry.name));
153 } catch (MSXException&) {
154 // ignore
155 }
156 }
157 if (auto it = ranges::find(toolTips, entry.name, &EnumToolTip::value);
158 it != toolTips.end()) {
159 simpleToolTip(it->tip);
160 }
161 }
162 });
163 settingStuff(setting);
164}
165void ComboBox(const char* label, Setting& setting, EnumToolTips toolTips)
166{
167 ComboBox(label, setting, std::identity{}, toolTips);
168}
170{
171 std::string name(setting.getBaseName());
172 ComboBox(name.c_str(), setting, toolTips);
173}
174
175void ComboBox(VideoSourceSetting& setting) // TODO share code with EnumSetting?
176{
177 std::string name(setting.getBaseName());
178 ComboBox(name.c_str(), setting);
179}
180void ComboBox(const char* label, VideoSourceSetting& setting) // TODO share code with EnumSetting?
181{
182 std::string name(setting.getBaseName());
183 auto current = setting.getValue().getString();
184 im::Combo(label, current.c_str(), [&]{
185 for (const auto& value : setting.getPossibleValues()) {
186 bool selected = value == current;
187 if (ImGui::Selectable(std::string(value).c_str(), selected)) {
188 try {
189 setting.setValue(TclObject(value));
190 } catch (MSXException&) {
191 // ignore
192 }
193 }
194 }
195 });
196 settingStuff(setting);
197}
198
199const char* getComboString(int item, const char* itemsSeparatedByZeros)
200{
201 const char* p = itemsSeparatedByZeros;
202 while (true) {
203 assert(*p);
204 if (item == 0) return p;
205 --item;
206 while (*p) ++p;
207 ++p;
208 }
209}
210
211std::string formatTime(std::optional<double> time)
212{
213 if (!time) return "--:--:--.--";
214 auto remainingTime = *time;
215 assert(remainingTime >= 0.0);
216 auto hours = int(remainingTime * (1.0 / 3600.0));
217 remainingTime -= double(hours * 3600);
218 auto minutes = int(remainingTime * (1.0 / 60.0));
219 remainingTime -= double(minutes * 60);
220 auto seconds = int(remainingTime);
221 remainingTime -= double(seconds);
222 auto hundreds = int(100.0 * remainingTime);
223
224 std::string result = "00:00:00.00";
225 auto insert = [&](size_t pos, unsigned value) {
226 assert(value < 100);
227 result[pos + 0] = char('0' + (value / 10));
228 result[pos + 1] = char('0' + (value % 10));
229 };
230 insert(0, hours % 100);
231 insert(3, minutes);
232 insert(6, seconds);
233 insert(9, hundreds);
234 return result;
235}
236
237float calculateFade(float current, float target, float period)
238{
239 const auto& io = ImGui::GetIO();
240 auto step = io.DeltaTime / period;
241 if (target > current) {
242 return std::min(target, current + step);
243 } else {
244 return std::max(target, current - step);
245 }
246}
247
248std::string getShortCutForCommand(const HotKey& hotkey, std::string_view command)
249{
250 for (const auto& info : hotkey.getGlobalBindings()) {
251 if (info.command != command) continue;
252 if (const auto* keyDown = std::get_if<KeyDownEvent>(&info.event)) {
253 std::string result;
254 auto modifiers = keyDown->getModifiers();
255 if (modifiers & KMOD_CTRL) strAppend(result, "CTRL+");
256 if (modifiers & KMOD_SHIFT) strAppend(result, "SHIFT+");
257 if (modifiers & KMOD_ALT) strAppend(result, "ALT+");
258 if (modifiers & KMOD_GUI) strAppend(result, "GUI+");
259 strAppend(result, SDL_GetKeyName(keyDown->getKeyCode()));
260 return result;
261 }
262 }
263 return "";
264}
265
266[[nodiscard]] static std::string_view superName()
267{
268 return ImGui::GetIO().ConfigMacOSXBehaviors ? "Cmd+" : "Super+";
269}
270
271std::string getKeyChordName(ImGuiKeyChord keyChord)
272{
273 int keyCode = ImGuiKey2SDL(ImGuiKey(keyChord & ~ImGuiMod_Mask_));
274 const auto* name = SDL_GetKeyName(keyCode);
275 if (!name || (*name == '\0')) return "None";
276 return strCat(
277 (keyChord & ImGuiMod_Ctrl ? "Ctrl+" : ""),
278 (keyChord & ImGuiMod_Shift ? "Shift+" : ""),
279 (keyChord & ImGuiMod_Alt ? "Alt+" : ""),
280 (keyChord & ImGuiMod_Super ? superName() : ""),
281 name);
282}
283
284std::optional<ImGuiKeyChord> parseKeyChord(std::string_view name)
285{
286 if (name == "None") return ImGuiKey_None;
287
288 // Similar to "StringOp::splitOnLast(name, '+')", but includes the last '+'
289 auto [modifiers, key] = [&]() -> std::pair<std::string_view, std::string_view> {
290 if (auto pos = name.find_last_of('+'); pos == std::string_view::npos) {
291 return {std::string_view{}, name};
292 } else {
293 return {name.substr(0, pos + 1), name.substr(pos + 1)};
294 }
295 }();
296
297 SDL_Keycode keyCode = SDL_GetKeyFromName(std::string(key).c_str());
298 if (keyCode == SDLK_UNKNOWN) return {};
299
300 auto contains = [](std::string_view haystack, std::string_view needle) {
301 // TODO in the future use c++23 std::string_view::contains()
302 return haystack.find(needle) != std::string_view::npos;
303 };
304 ImGuiKeyChord keyMods =
305 (contains(modifiers, "Ctrl+" ) ? ImGuiMod_Ctrl : 0) |
306 (contains(modifiers, "Shift+") ? ImGuiMod_Shift : 0) |
307 (contains(modifiers, "Alt+" ) ? ImGuiMod_Alt : 0) |
308 (contains(modifiers, superName()) ? ImGuiMod_Super : 0);
309
310 return SDLKey2ImGui(keyCode) | keyMods;
311}
312
313void setColors(int style)
314{
315 // style: 0->dark, 1->light, 2->classic
316 bool light = style == 1;
317 using enum imColor;
318
319 // AA'BB'GG'RR
320 imColors[size_t(TRANSPARENT )] = 0x00'00'00'00;
321 imColors[size_t(BLACK )] = 0xff'00'00'00;
322 imColors[size_t(WHITE )] = 0xff'ff'ff'ff;
323 imColors[size_t(GRAY )] = 0xff'80'80'80;
324 imColors[size_t(YELLOW )] = 0xff'00'ff'ff;
325 imColors[size_t(RED_BG )] = 0x40'00'00'ff;
326 imColors[size_t(YELLOW_BG )] = 0x80'00'ff'ff;
327
328 imColors[size_t(TEXT )] = ImGui::GetColorU32(ImGuiCol_Text);
329 imColors[size_t(TEXT_DISABLED )] = ImGui::GetColorU32(ImGuiCol_TextDisabled);
330
331 imColors[size_t(ERROR )] = 0xff'00'00'ff;
332 imColors[size_t(WARNING )] = 0xff'33'b3'ff;
333
334 imColors[size_t(COMMENT )] = 0xff'5c'ff'5c;
335 imColors[size_t(VARIABLE )] = 0xff'ff'ff'00;
336 imColors[size_t(LITERAL )] = light ? 0xff'9c'5d'27 : 0xff'00'ff'ff;
337 imColors[size_t(PROC )] = 0xff'cd'00'cd;
338 imColors[size_t(OPERATOR )] = 0xff'cd'cd'00;
339
340 imColors[size_t(KEY_ACTIVE )] = 0xff'10'40'ff;
341 imColors[size_t(KEY_NOT_ACTIVE)] = 0x80'00'00'00;
342}
343
344} // namespace openmsx
BaseSetting * setting
virtual void setValue(const TclObject &value)=0
Change the value of this setting to the given value.
virtual const TclObject & getValue() const =0
Get current value as a TclObject.
virtual TclObject getDefaultValue() const =0
Get the default value of this setting.
std::string_view getBaseName() const
Definition Setting.hh:38
A Setting with a floating point value.
const auto & getGlobalBindings() const
Definition HotKey.hh:86
A Setting with an integer value.
zstring_view getString() const
Definition TclObject.cc:141
void StrCat(Ts &&...ts)
Definition ImGuiUtils.hh:44
auto CalcTextSize(std::string_view str)
Definition ImGuiUtils.hh:38
void PopupContextItem(const char *str_id, ImGuiPopupFlags popup_flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:421
void Combo(const char *label, const char *preview_value, ImGuiComboFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:289
void format(SectorAccessibleDisk &disk, MSXBootSectorType bootType)
Format the given disk (= a single partition).
This file implemented 3 utility functions:
Definition Autofire.cc:11
bool Checkbox(const HotKey &hotKey, BooleanSetting &setting)
Definition ImGuiUtils.cc:58
bool SliderFloat(FloatSetting &setting, const char *format, ImGuiSliderFlags flags)
std::span< const EnumToolTip > EnumToolTips
Definition ImGuiUtils.hh:76
bool SliderInt(IntegerSetting &setting, ImGuiSliderFlags flags)
Definition ImGuiUtils.cc:83
void ComboBox(const char *label, Setting &setting, function_ref< std::string(const std::string &)> displayValue, EnumToolTips toolTips)
void simpleToolTip(std::string_view desc)
Definition ImGuiUtils.hh:78
std::string getShortCutForCommand(const HotKey &hotkey, std::string_view command)
ImGuiKey SDLKey2ImGui(SDL_Keycode sdl)
std::array< ImU32, size_t(imColor::NUM_COLORS)> imColors
void HelpMarker(std::string_view desc)
Definition ImGuiUtils.cc:23
SDL_Keycode ImGuiKey2SDL(ImGuiKey imgui)
bool InputText(Setting &setting)
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)
std::string formatTime(std::optional< double > time)
float calculateFade(float current, float target, float period)
constexpr bool contains(ITER first, ITER last, const VAL &val)
Check if a range contains a given value, using linear search.
Definition stl.hh:32
std::string strCat()
Definition strCat.hh:703
void strAppend(std::string &result, Ts &&...ts)
Definition strCat.hh:752
std::string operator()(const Setting &setting) const
Definition ImGuiUtils.cc:30