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 auto value = setting.getFloat();
111 auto min = narrow_cast<float>(setting.getMinValue());
112 auto 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 auto current = setting.getValue().getString();
183 im::Combo(label, current.c_str(), [&]{
184 for (const auto& value : setting.getPossibleValues()) {
185 bool selected = value == current;
186 if (ImGui::Selectable(std::string(value).c_str(), selected)) {
187 try {
188 setting.setValue(TclObject(value));
189 } catch (MSXException&) {
190 // ignore
191 }
192 }
193 }
194 });
195 settingStuff(setting);
196}
197
198const char* getComboString(int item, const char* itemsSeparatedByZeros)
199{
200 const char* p = itemsSeparatedByZeros;
201 while (true) {
202 assert(*p);
203 if (item == 0) return p;
204 --item;
205 while (*p) ++p;
206 ++p;
207 }
208}
209
210std::string formatTime(std::optional<double> time)
211{
212 if (!time) return "--:--:--.--";
213 auto remainingTime = *time;
214 assert(remainingTime >= 0.0);
215 auto hours = int(remainingTime * (1.0 / 3600.0));
216 remainingTime -= double(hours * 3600);
217 auto minutes = int(remainingTime * (1.0 / 60.0));
218 remainingTime -= double(minutes * 60);
219 auto seconds = int(remainingTime);
220 remainingTime -= double(seconds);
221 auto hundreds = int(100.0 * remainingTime);
222
223 std::string result = "00:00:00.00";
224 auto insert = [&](size_t pos, unsigned value) {
225 assert(value < 100);
226 result[pos + 0] = char('0' + (value / 10));
227 result[pos + 1] = char('0' + (value % 10));
228 };
229 insert(0, hours % 100);
230 insert(3, minutes);
231 insert(6, seconds);
232 insert(9, hundreds);
233 return result;
234}
235
236float calculateFade(float current, float target, float period)
237{
238 const auto& io = ImGui::GetIO();
239 auto step = io.DeltaTime / period;
240 if (target > current) {
241 return std::min(target, current + step);
242 } else {
243 return std::max(target, current - step);
244 }
245}
246
247std::string getShortCutForCommand(const HotKey& hotkey, std::string_view command)
248{
249 for (const auto& info : hotkey.getGlobalBindings()) {
250 if (info.command != command) continue;
251 if (const auto* keyDown = std::get_if<KeyDownEvent>(&info.event)) {
252 std::string result;
253 auto modifiers = keyDown->getModifiers();
254 if (modifiers & KMOD_CTRL) strAppend(result, "CTRL+");
255 if (modifiers & KMOD_SHIFT) strAppend(result, "SHIFT+");
256 if (modifiers & KMOD_ALT) strAppend(result, "ALT+");
257 if (modifiers & KMOD_GUI) strAppend(result, "GUI+");
258 strAppend(result, SDL_GetKeyName(keyDown->getKeyCode()));
259 return result;
260 }
261 }
262 return "";
263}
264
265[[nodiscard]] static std::string_view superName()
266{
267 return ImGui::GetIO().ConfigMacOSXBehaviors ? "Cmd+" : "Super+";
268}
269
270std::string getKeyChordName(ImGuiKeyChord keyChord)
271{
272 int keyCode = ImGuiKey2SDL(ImGuiKey(keyChord & ~ImGuiMod_Mask_));
273 const auto* name = SDL_GetKeyName(keyCode);
274 if (!name || (*name == '\0')) return "None";
275 return strCat(
276 (keyChord & ImGuiMod_Ctrl ? "Ctrl+" : ""),
277 (keyChord & ImGuiMod_Shift ? "Shift+" : ""),
278 (keyChord & ImGuiMod_Alt ? "Alt+" : ""),
279 (keyChord & ImGuiMod_Super ? superName() : ""),
280 name);
281}
282
283std::optional<ImGuiKeyChord> parseKeyChord(std::string_view name)
284{
285 if (name == "None") return ImGuiKey_None;
286
287 // Similar to "StringOp::splitOnLast(name, '+')", but includes the last '+'
288 auto [modifiers, key] = [&]() -> std::pair<std::string_view, std::string_view> {
289 if (auto pos = name.find_last_of('+'); pos == std::string_view::npos) {
290 return {std::string_view{}, name};
291 } else {
292 return {name.substr(0, pos + 1), name.substr(pos + 1)};
293 }
294 }();
295
296 SDL_Keycode keyCode = SDL_GetKeyFromName(std::string(key).c_str());
297 if (keyCode == SDLK_UNKNOWN) return {};
298
299 auto contains = [](std::string_view haystack, std::string_view needle) {
300 // TODO in the future use c++23 std::string_view::contains()
301 return haystack.find(needle) != std::string_view::npos;
302 };
303 ImGuiKeyChord keyMods =
304 (contains(modifiers, "Ctrl+" ) ? ImGuiMod_Ctrl : 0) |
305 (contains(modifiers, "Shift+") ? ImGuiMod_Shift : 0) |
306 (contains(modifiers, "Alt+" ) ? ImGuiMod_Alt : 0) |
307 (contains(modifiers, superName()) ? ImGuiMod_Super : 0);
308
309 return SDLKey2ImGui(keyCode) | keyMods;
310}
311
312void setColors(int style)
313{
314 // style: 0->dark, 1->light, 2->classic
315 bool light = style == 1;
316 using enum imColor;
317
318 // AA'BB'GG'RR
319 imColors[size_t(TRANSPARENT )] = 0x00'00'00'00;
320 imColors[size_t(BLACK )] = 0xff'00'00'00;
321 imColors[size_t(WHITE )] = 0xff'ff'ff'ff;
322 imColors[size_t(GRAY )] = 0xff'80'80'80;
323 imColors[size_t(YELLOW )] = 0xff'00'ff'ff;
324 imColors[size_t(RED_BG )] = 0x40'00'00'ff;
325 imColors[size_t(YELLOW_BG )] = 0x80'00'ff'ff;
326
327 imColors[size_t(TEXT )] = ImGui::GetColorU32(ImGuiCol_Text);
328 imColors[size_t(TEXT_DISABLED )] = ImGui::GetColorU32(ImGuiCol_TextDisabled);
329
330 imColors[size_t(ERROR )] = 0xff'00'00'ff;
331 imColors[size_t(WARNING )] = 0xff'33'b3'ff;
332
333 imColors[size_t(COMMENT )] = 0xff'5c'ff'5c;
334 imColors[size_t(VARIABLE )] = 0xff'ff'ff'00;
335 imColors[size_t(LITERAL )] = light ? 0xff'9c'5d'27 : 0xff'00'ff'ff;
336 imColors[size_t(PROC )] = 0xff'cd'00'cd;
337 imColors[size_t(OPERATOR )] = 0xff'cd'cd'00;
338
339 imColors[size_t(KEY_ACTIVE )] = 0xff'10'40'ff;
340 imColors[size_t(KEY_NOT_ACTIVE)] = 0x80'00'00'00;
341}
342
343} // 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:45
auto CalcTextSize(std::string_view str)
Definition ImGuiUtils.hh:39
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:77
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:79
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:35
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