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(std::optional<double> time)
235{
236 if (!time) return "--:--:--.--";
237 auto remainingTime = *time;
238 assert(remainingTime >= 0.0);
239 auto hours = int(remainingTime * (1.0 / 3600.0));
240 remainingTime -= double(hours * 3600);
241 auto minutes = int(remainingTime * (1.0 / 60.0));
242 remainingTime -= double(minutes * 60);
243 auto seconds = int(remainingTime);
244 remainingTime -= double(seconds);
245 auto hundreds = int(100.0 * remainingTime);
246
247 std::string result = "00:00:00.00";
248 auto insert = [&](size_t pos, unsigned value) {
249 assert(value < 100);
250 result[pos + 0] = char('0' + (value / 10));
251 result[pos + 1] = char('0' + (value % 10));
252 };
253 insert(0, hours % 100);
254 insert(3, minutes);
255 insert(6, seconds);
256 insert(9, hundreds);
257 return result;
258}
259
260float calculateFade(float current, float target, float period)
261{
262 const auto& io = ImGui::GetIO();
263 auto step = io.DeltaTime / period;
264 if (target > current) {
265 return std::min(target, current + step);
266 } else {
267 return std::max(target, current - step);
268 }
269}
270
271std::string getShortCutForCommand(const HotKey& hotkey, std::string_view command)
272{
273 for (const auto& info : hotkey.getGlobalBindings()) {
274 if (info.command != command) continue;
275 if (const auto* keyDown = std::get_if<KeyDownEvent>(&info.event)) {
276 std::string result;
277 auto modifiers = keyDown->getModifiers();
278 if (modifiers & KMOD_CTRL) strAppend(result, "CTRL+");
279 if (modifiers & KMOD_SHIFT) strAppend(result, "SHIFT+");
280 if (modifiers & KMOD_ALT) strAppend(result, "ALT+");
281 if (modifiers & KMOD_GUI) strAppend(result, "GUI+");
282 strAppend(result, SDL_GetKeyName(keyDown->getKeyCode()));
283 return result;
284 }
285 }
286 return "";
287}
288
289[[nodiscard]] static std::string_view superName()
290{
291 return ImGui::GetIO().ConfigMacOSXBehaviors ? "Cmd+" : "Super+";
292}
293
294std::string getKeyChordName(ImGuiKeyChord keyChord)
295{
296 int keyCode = ImGuiKey2SDL(ImGuiKey(keyChord & ~ImGuiMod_Mask_));
297 const auto* name = SDL_GetKeyName(keyCode);
298 if (!name || (*name == '\0')) return "None";
299 return strCat(
300 (keyChord & ImGuiMod_Ctrl ? "Ctrl+" : ""),
301 (keyChord & ImGuiMod_Shift ? "Shift+" : ""),
302 (keyChord & ImGuiMod_Alt ? "Alt+" : ""),
303 (keyChord & ImGuiMod_Super ? superName() : ""),
304 name);
305}
306
307std::optional<ImGuiKeyChord> parseKeyChord(std::string_view name)
308{
309 if (name == "None") return ImGuiKey_None;
310
311 // Similar to "StringOp::splitOnLast(name, '+')", but includes the last '+'
312 auto [modifiers, key] = [&]() -> std::pair<std::string_view, std::string_view> {
313 if (auto pos = name.find_last_of('+'); pos == std::string_view::npos) {
314 return {std::string_view{}, name};
315 } else {
316 return {name.substr(0, pos + 1), name.substr(pos + 1)};
317 }
318 }();
319
320 SDL_Keycode keyCode = SDL_GetKeyFromName(std::string(key).c_str());
321 if (keyCode == SDLK_UNKNOWN) return {};
322
323 auto contains = [](std::string_view haystack, std::string_view needle) {
324 // TODO in the future use c++23 std::string_view::contains()
325 return haystack.find(needle) != std::string_view::npos;
326 };
327 ImGuiKeyChord keyMods =
328 (contains(modifiers, "Ctrl+" ) ? ImGuiMod_Ctrl : 0) |
329 (contains(modifiers, "Shift+") ? ImGuiMod_Shift : 0) |
330 (contains(modifiers, "Alt+" ) ? ImGuiMod_Alt : 0) |
331 (contains(modifiers, superName()) ? ImGuiMod_Super : 0);
332
333 return SDLKey2ImGui(keyCode) | keyMods;
334}
335
336void setColors(int style)
337{
338 // style: 0->dark, 1->light, 2->classic
339 bool light = style == 1;
340 using enum imColor;
341
342 // AA'BB'GG'RR
343 imColors[size_t(TRANSPARENT )] = 0x00'00'00'00;
344 imColors[size_t(BLACK )] = 0xff'00'00'00;
345 imColors[size_t(WHITE )] = 0xff'ff'ff'ff;
346 imColors[size_t(GRAY )] = 0xff'80'80'80;
347 imColors[size_t(YELLOW )] = 0xff'00'ff'ff;
348 imColors[size_t(RED_BG )] = 0x40'00'00'ff;
349 imColors[size_t(YELLOW_BG )] = 0x80'00'ff'ff;
350
351 imColors[size_t(TEXT )] = ImGui::GetColorU32(ImGuiCol_Text);
352 imColors[size_t(TEXT_DISABLED )] = ImGui::GetColorU32(ImGuiCol_TextDisabled);
353
354 imColors[size_t(ERROR )] = 0xff'00'00'ff;
355 imColors[size_t(WARNING )] = 0xff'33'b3'ff;
356
357 imColors[size_t(COMMENT )] = 0xff'5c'ff'5c;
358 imColors[size_t(VARIABLE )] = 0xff'ff'ff'00;
359 imColors[size_t(LITERAL )] = light ? 0xff'9c'5d'27 : 0xff'00'ff'ff;
360 imColors[size_t(PROC )] = 0xff'cd'00'cd;
361 imColors[size_t(OPERATOR )] = 0xff'cd'cd'00;
362
363 imColors[size_t(KEY_ACTIVE )] = 0xff'10'40'ff;
364 imColors[size_t(KEY_NOT_ACTIVE)] = 0x80'00'00'00;
365}
366
367} // 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:421
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 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::span< const EnumToolTip > EnumToolTips
Definition ImGuiUtils.hh:75
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:77
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:53