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