openMSX
ImGuiSettings.cc
Go to the documentation of this file.
1#include "ImGuiSettings.hh"
2
3#include "ImGuiCpp.hh"
4#include "ImGuiManager.hh"
5#include "ImGuiMessages.hh"
6#include "ImGuiOsdIcons.hh"
7#include "ImGuiSoundChip.hh"
8#include "ImGuiUtils.hh"
9
10#include "BooleanInput.hh"
11#include "BooleanSetting.hh"
12#include "CPUCore.hh"
13#include "Display.hh"
14#include "EventDistributor.hh"
15#include "FileContext.hh"
16#include "FilenameSetting.hh"
17#include "FloatSetting.hh"
19#include "GlobalSettings.hh"
20#include "InputEventFactory.hh"
22#include "IntegerSetting.hh"
23#include "JoyMega.hh"
24#include "KeyCodeSetting.hh"
25#include "KeyboardSettings.hh"
26#include "Mixer.hh"
27#include "MSXCPU.hh"
29#include "MSXJoystick.hh"
30#include "MSXMotherBoard.hh"
31#include "ProxySetting.hh"
32#include "R800.hh"
33#include "Reactor.hh"
34#include "ReadOnlySetting.hh"
35#include "SettingsManager.hh"
36#include "StringSetting.hh"
37#include "Version.hh"
38#include "VideoSourceSetting.hh"
39#include "Z80.hh"
40
41#include "checked_cast.hh"
42#include "foreach_file.hh"
43#include "narrow.hh"
44#include "StringOp.hh"
45#include "unreachable.hh"
46#include "zstring_view.hh"
47
48#include <imgui.h>
49#include <imgui_stdlib.h>
50
51#include <SDL.h>
52
53#include <optional>
54
55using namespace std::literals;
56
57namespace openmsx {
58
60{
61 deinitListener();
62}
63
64void ImGuiSettings::save(ImGuiTextBuffer& buf)
65{
66 savePersistent(buf, *this, persistentElements);
67}
68
69void ImGuiSettings::loadLine(std::string_view name, zstring_view value)
70{
71 loadOnePersistent(name, value, *this, persistentElements);
72}
73
75{
76 setStyle();
77}
78
79void ImGuiSettings::setStyle()
80{
81 switch (selectedStyle) {
82 case 0: ImGui::StyleColorsDark(); break;
83 case 1: ImGui::StyleColorsLight(); break;
84 case 2: ImGui::StyleColorsClassic(); break;
85 }
86 setColors(selectedStyle);
87}
89{
90 bool openConfirmPopup = false;
91
92 im::Menu("Settings", [&]{
93 auto& reactor = manager.getReactor();
94 auto& globalSettings = reactor.getGlobalSettings();
95 auto& renderSettings = reactor.getDisplay().getRenderSettings();
96 auto& settingsManager = reactor.getGlobalCommandController().getSettingsManager();
97 const auto& hotKey = reactor.getHotKey();
98
99 im::Menu("Video", [&]{
100 im::TreeNode("Look and feel", ImGuiTreeNodeFlags_DefaultOpen, [&]{
101 auto& scaler = renderSettings.getScaleAlgorithmSetting();
102 ComboBox("Scaler", scaler);
103 im::Indent([&]{
104 struct AlgoEnable {
106 bool hasScanline;
107 bool hasBlur;
108 };
109 static constexpr std::array algoEnables = {
110 // scanline / blur
111 AlgoEnable{RenderSettings::SCALER_SIMPLE, true, true },
112 AlgoEnable{RenderSettings::SCALER_SCALE, false, false},
113 AlgoEnable{RenderSettings::SCALER_HQ, false, false},
114 AlgoEnable{RenderSettings::SCALER_HQLITE, false, false},
115 AlgoEnable{RenderSettings::SCALER_RGBTRIPLET, true, true },
116 AlgoEnable{RenderSettings::SCALER_TV, true, false},
117 };
118 auto it = ranges::find(algoEnables, scaler.getEnum(), &AlgoEnable::algo);
119 assert(it != algoEnables.end());
120 im::Disabled(!it->hasScanline, [&]{
121 SliderInt("Scanline (%)", renderSettings.getScanlineSetting());
122 });
123 im::Disabled(!it->hasBlur, [&]{
124 SliderInt("Blur (%)", renderSettings.getBlurSetting());
125 });
126 });
127
128 SliderInt("Scale factor", renderSettings.getScaleFactorSetting());
129 Checkbox(hotKey, "Deinterlace", renderSettings.getDeinterlaceSetting());
130 Checkbox(hotKey, "Deflicker", renderSettings.getDeflickerSetting());
131 });
132 im::TreeNode("Colors", ImGuiTreeNodeFlags_DefaultOpen, [&]{
133 SliderFloat("Noise (%)", renderSettings.getNoiseSetting());
134 SliderFloat("Brightness", renderSettings.getBrightnessSetting());
135 SliderFloat("Contrast", renderSettings.getContrastSetting());
136 SliderFloat("Gamma", renderSettings.getGammaSetting());
137 SliderInt("Glow (%)", renderSettings.getGlowSetting());
138 if (auto* monitor = dynamic_cast<Setting*>(settingsManager.findSetting("monitor_type"))) {
139 ComboBox("Monitor type", *monitor, [](std::string s) {
140 ranges::replace(s, '_', ' ');
141 return s;
142 });
143 }
144 });
145 im::TreeNode("Shape", ImGuiTreeNodeFlags_DefaultOpen, [&]{
146 SliderFloat("Horizontal stretch", renderSettings.getHorizontalStretchSetting(), "%.0f");
147 ComboBox("Display deformation", renderSettings.getDisplayDeformSetting());
148 });
149 im::TreeNode("Misc", ImGuiTreeNodeFlags_DefaultOpen, [&]{
150 Checkbox(hotKey, "Full screen", renderSettings.getFullScreenSetting());
151 if (motherBoard) {
152 ComboBox("Video source to display", motherBoard->getVideoSource());
153 }
154 Checkbox(hotKey, "VSync", renderSettings.getVSyncSetting());
155 SliderInt("Minimum frame-skip", renderSettings.getMinFrameSkipSetting()); // TODO: either leave out this setting, or add a tooltip like, "Leave on 0 unless you use a very slow device and want regular frame skipping");
156 SliderInt("Maximum frame-skip", renderSettings.getMaxFrameSkipSetting()); // TODO: either leave out this setting or add a tooltip like "On slow devices, skip no more than this amount of frames to keep emulation on time.");
157 });
158 im::TreeNode("Advanced (for debugging)", [&]{ // default collapsed
159 Checkbox(hotKey, "Enforce VDP sprites-per-line limit", renderSettings.getLimitSpritesSetting());
160 Checkbox(hotKey, "Disable sprites", renderSettings.getDisableSpritesSetting());
161 ComboBox("Way to handle too fast VDP access", renderSettings.getTooFastAccessSetting());
162 ComboBox("Emulate VDP command timing", renderSettings.getCmdTimingSetting());
163 });
164 });
165 im::Menu("Sound", [&]{
166 auto& mixer = reactor.getMixer();
167 auto& muteSetting = mixer.getMuteSetting();
168 im::Disabled(muteSetting.getBoolean(), [&]{
169 SliderInt("Master volume", mixer.getMasterVolume());
170 });
171 Checkbox(hotKey, "Mute", muteSetting);
172 ImGui::Separator();
173 static constexpr std::array resamplerToolTips = {
174 EnumToolTip{"hq", "best quality, uses more CPU"},
175 EnumToolTip{"blip", "good speed/quality tradeoff"},
176 EnumToolTip{"fast", "fast but low quality"},
177 };
178 ComboBox("Resampler", globalSettings.getResampleSetting(), resamplerToolTips);
179 ImGui::Separator();
180
181 ImGui::MenuItem("Show sound chip settings", nullptr, &manager.soundChip->showSoundChipSettings);
182 });
183 im::Menu("Speed", [&]{
184 im::TreeNode("Emulation", ImGuiTreeNodeFlags_DefaultOpen, [&]{
185 ImGui::SameLine();
186 HelpMarker("These control the speed of the whole MSX machine, "
187 "the running MSX software can't tell the difference.");
188
189 auto& speedManager = globalSettings.getSpeedManager();
190 auto& fwdSetting = speedManager.getFastForwardSetting();
191 int fastForward = fwdSetting.getBoolean() ? 1 : 0;
192 ImGui::TextUnformatted("Speed:"sv);
193 ImGui::SameLine();
194 bool fwdChanged = ImGui::RadioButton("normal", &fastForward, 0);
195 ImGui::SameLine();
196 fwdChanged |= ImGui::RadioButton("fast forward", &fastForward, 1);
197 auto fastForwardShortCut = getShortCutForCommand(reactor.getHotKey(), "toggle fastforward");
198 if (!fastForwardShortCut.empty()) {
199 HelpMarker(strCat("Use '", fastForwardShortCut ,"' to quickly toggle between these two"));
200 }
201 if (fwdChanged) {
202 fwdSetting.setBoolean(fastForward != 0);
203 }
204 im::Indent([&]{
205 im::Disabled(fastForward != 0, [&]{
206 SliderInt("Speed (%)", speedManager.getSpeedSetting());
207 });
208 im::Disabled(fastForward != 1, [&]{
209 SliderInt("Fast forward speed (%)", speedManager.getFastForwardSpeedSetting());
210 });
211 });
212 Checkbox(hotKey, "Go full speed when loading", globalSettings.getThrottleManager().getFullSpeedLoadingSetting());
213 });
214 if (motherBoard) {
215 im::TreeNode("MSX devices", ImGuiTreeNodeFlags_DefaultOpen, [&]{
216 ImGui::SameLine();
217 HelpMarker("These control the speed of the specific components in the MSX machine. "
218 "So the relative speed between components can change. "
219 "And this may lead the emulation problems.");
220
221 MSXCPU& cpu = motherBoard->getCPU();
222 auto showFreqSettings = [&](std::string_view name, auto* core) {
223 if (!core) return;
224 auto& locked = core->getFreqLockedSetting();
225 auto& value = core->getFreqValueSetting();
226 // Note: GUI shows "UNlocked", while the actual settings is "locked"
227 bool unlocked = !locked.getBoolean();
228 if (ImGui::Checkbox(tmpStrCat("unlock custom ", name, " frequency").c_str(), &unlocked)) {
229 locked.setBoolean(!unlocked);
230 }
231 simpleToolTip([&]{ return locked.getDescription(); });
232 im::Indent([&]{
233 im::Disabled(!unlocked, [&]{
234 float fval = float(value.getInt()) / 1.0e6f;
235 if (ImGui::InputFloat(tmpStrCat("frequency (MHz)##", name).c_str(), &fval, 0.01f, 1.0f, "%.2f")) {
236 value.setInt(int(fval * 1.0e6f));
237 }
238 im::PopupContextItem(tmpStrCat("freq-context##", name).c_str(), [&]{
239 const char* F358 = name == "Z80" ? "3.58 MHz (default)"
240 : "3.58 MHz";
241 if (ImGui::Selectable(F358)) {
242 value.setInt(3'579'545);
243 }
244 if (ImGui::Selectable("5.37 MHz")) {
245 value.setInt(5'369'318);
246 }
247 const char* F716 = name == "R800" ? "7.16 MHz (default)"
248 : "7.16 MHz";
249 if (ImGui::Selectable(F716)) {
250 value.setInt(7'159'090);
251 }
252
253 });
254 HelpMarker("Right-click to select commonly used values");
255 });
256 });
257 };
258 showFreqSettings("Z80", cpu.getZ80());
259 showFreqSettings("R800", cpu.getR800()); // might be nullptr
260 });
261 }
262 });
263 im::Menu("Input", [&]{
264 static constexpr std::array kbdModeToolTips = {
265 EnumToolTip{"CHARACTER", "Tries to understand the character you are typing and then attempts to type that character using the current MSX keyboard. May not work very well when using a non-US host keyboard."},
266 EnumToolTip{"KEY", "Tries to map a key you press to the corresponding MSX key"},
267 EnumToolTip{"POSITIONAL", "Tries to map the keyboard key positions to the MSX keyboard key positions"},
268 };
269 if (motherBoard) {
270 auto& controller = motherBoard->getMSXCommandController();
271 if (auto* turbo = dynamic_cast<IntegerSetting*>(controller.findSetting("renshaturbo"))) {
272 SliderInt("Ren Sha Turbo (%)", *turbo);
273 }
274 if (auto* mappingModeSetting = dynamic_cast<EnumSetting<KeyboardSettings::MappingMode>*>(controller.findSetting("kbd_mapping_mode"))) {
275 ComboBox("Keyboard mapping mode", *mappingModeSetting, kbdModeToolTips);
276 }
277 };
278 ImGui::MenuItem("Configure MSX joysticks...", nullptr, &showConfigureJoystick);
279 });
280 im::Menu("GUI", [&]{
281 auto getExistingLayouts = [] {
282 std::vector<std::string> names;
283 auto context = userDataFileContext("layouts");
284 for (const auto& path : context.getPaths()) {
285 foreach_file(path, [&](const std::string& fullName, std::string_view name) {
286 if (name.ends_with(".ini")) {
287 names.emplace_back(fullName);
288 }
289 });
290 }
292 return names;
293 };
294 auto listExistingLayouts = [&](const std::vector<std::string>& names) {
295 std::optional<std::pair<std::string, std::string>> selectedLayout;
296 im::ListBox("##select-layout", [&]{
297 for (const auto& name : names) {
298 auto displayName = std::string(FileOperations::stripExtension(FileOperations::getFilename(name)));
299 if (ImGui::Selectable(displayName.c_str())) {
300 selectedLayout = std::pair{name, displayName};
301 }
303 if (ImGui::MenuItem("delete")) {
304 confirmText = strCat("Delete layout: ", displayName);
305 confirmAction = [name]{ FileOperations::unlink(name); };
306 openConfirmPopup = true;
307 }
308 });
309 }
310 });
311 return selectedLayout;
312 };
313 im::Menu("Save layout", [&]{
314 auto names = getExistingLayouts();
315 if (!names.empty()) {
316 ImGui::TextUnformatted("Existing layouts"sv);
317 if (auto selectedLayout = listExistingLayouts(names)) {
318 const auto& [name, displayName] = *selectedLayout;
319 saveLayoutName = displayName;
320 }
321 }
322 ImGui::TextUnformatted("Enter name:"sv);
323 ImGui::InputText("##save-layout-name", &saveLayoutName);
324 ImGui::SameLine();
325 im::Disabled(saveLayoutName.empty(), [&]{
326 if (ImGui::Button("Create")) {
327 (void)reactor.getDisplay().getWindowPosition(); // to save up-to-date window position
328 ImGui::CloseCurrentPopup();
329
330 auto filename = FileOperations::parseCommandFileArgument(
331 saveLayoutName, "layouts", "", ".ini");
332 if (FileOperations::exists(filename)) {
333 confirmText = strCat("Overwrite layout: ", saveLayoutName);
334 confirmAction = [filename]{
335 ImGui::SaveIniSettingsToDisk(filename.c_str());
336 };
337 openConfirmPopup = true;
338 } else {
339 ImGui::SaveIniSettingsToDisk(filename.c_str());
340 }
341 }
342 });
343 });
344 im::Menu("Restore layout", [&]{
345 ImGui::TextUnformatted("Select layout"sv);
346 auto names = getExistingLayouts();
347 if (auto selectedLayout = listExistingLayouts(names)) {
348 const auto& [name, displayName] = *selectedLayout;
349 manager.loadIniFile = name;
350 ImGui::CloseCurrentPopup();
351 }
352 });
353 im::Menu("Select style", [&]{
354 std::optional<int> newStyle;
355 std::array names = {"Dark", "Light", "Classic"}; // must be in sync with setStyle()
356 for (auto i : xrange(narrow<int>(names.size()))) {
357 if (ImGui::Selectable(names[i], selectedStyle == i)) {
358 newStyle = i;
359 }
360 }
361 if (newStyle) {
362 selectedStyle = *newStyle;
363 setStyle();
364 }
365 });
366 ImGui::MenuItem("Select font...", nullptr, &showFont);
367 });
368 im::Menu("Misc", [&]{
369 ImGui::MenuItem("Configure OSD icons...", nullptr, &manager.osdIcons->showConfigureIcons);
370 ImGui::MenuItem("Fade out menu bar", nullptr, &manager.menuFade);
371 ImGui::MenuItem("Configure messages...", nullptr, &manager.messages->configureWindow.open);
372 });
373 ImGui::Separator();
374 im::Menu("Advanced", [&]{
375 ImGui::TextUnformatted("All settings"sv);
376 ImGui::Separator();
377 std::vector<Setting*> settings;
378 for (auto* setting : settingsManager.getAllSettings()) {
379 if (dynamic_cast<ProxySetting*>(setting)) continue;
380 if (dynamic_cast<ReadOnlySetting*>(setting)) continue;
381 settings.push_back(checked_cast<Setting*>(setting));
382 }
384 for (auto* setting : settings) {
385 if (auto* bSetting = dynamic_cast<BooleanSetting*>(setting)) {
386 Checkbox(hotKey, *bSetting);
387 } else if (auto* iSetting = dynamic_cast<IntegerSetting*>(setting)) {
388 SliderInt(*iSetting);
389 } else if (auto* fSetting = dynamic_cast<FloatSetting*>(setting)) {
390 SliderFloat(*fSetting);
391 } else if (auto* sSetting = dynamic_cast<StringSetting*>(setting)) {
392 InputText(*sSetting);
393 } else if (auto* fnSetting = dynamic_cast<FilenameSetting*>(setting)) {
394 InputText(*fnSetting); // TODO
395 } else if (auto* kSetting = dynamic_cast<KeyCodeSetting*>(setting)) {
396 InputText(*kSetting); // TODO
397 } else if (dynamic_cast<EnumSettingBase*>(setting)) {
399 } else if (auto* vSetting = dynamic_cast<VideoSourceSetting*>(setting)) {
400 ComboBox(*vSetting);
401 } else {
402 assert(false);
403 }
404 }
405 if (!Version::RELEASE) {
406 ImGui::Separator();
407 ImGui::Checkbox("ImGui Demo Window", &showDemoWindow);
408 HelpMarker("Show the ImGui demo window.\n"
409 "This is purely to demonstrate the ImGui capabilities.\n"
410 "There is no connection with any openMSX functionality.");
411 }
412 });
413 });
414 if (showDemoWindow) {
415 ImGui::ShowDemoWindow(&showDemoWindow);
416 }
417
418 const auto confirmTitle = "Confirm##settings";
419 if (openConfirmPopup) {
420 ImGui::OpenPopup(confirmTitle);
421 }
422 im::PopupModal(confirmTitle, nullptr, ImGuiWindowFlags_AlwaysAutoResize, [&]{
423 ImGui::TextUnformatted(confirmText);
424
425 bool close = false;
426 if (ImGui::Button("Ok")) {
427 confirmAction();
428 close = true;
429 }
430 ImGui::SameLine();
431 close |= ImGui::Button("Cancel");
432 if (close) {
433 ImGui::CloseCurrentPopup();
434 confirmAction = {};
435 }
436 });
437}
438
440
441// joystick is 0..3
442[[nodiscard]] static std::string settingName(unsigned joystick)
443{
444 return (joystick < 2) ? strCat("msxjoystick", joystick + 1, "_config")
445 : strCat("joymega", joystick - 1, "_config");
446}
447
448// joystick is 0..3
449[[nodiscard]] static std::string joystickToGuiString(unsigned joystick)
450{
451 return (joystick < 2) ? strCat("MSX joystick ", joystick + 1)
452 : strCat("JoyMega controller ", joystick - 1);
453}
454
455[[nodiscard]] static std::string toGuiString(const BooleanInput& input, const JoystickManager& joystickManager)
456{
457 return std::visit(overloaded{
458 [](const BooleanKeyboard& k) {
459 return strCat("keyboard key ", SDLKey::toString(k.getKeyCode()));
460 },
461 [](const BooleanMouseButton& m) {
462 return strCat("mouse button ", m.getButton());
463 },
464 [&](const BooleanJoystickButton& j) {
465 return strCat(joystickManager.getDisplayName(j.getJoystick()), " button ", j.getButton());
466 },
467 [&](const BooleanJoystickHat& h) {
468 const char* str = [&] {
469 switch (h.getValue()) {
470 case BooleanJoystickHat::UP: return "up";
471 case BooleanJoystickHat::RIGHT: return "right";
472 case BooleanJoystickHat::DOWN: return "down";
473 case BooleanJoystickHat::LEFT: return "left";
474 default: UNREACHABLE; return "";
475 }
476 }();
477 return strCat(joystickManager.getDisplayName(h.getJoystick()), " D-pad ", h.getHat(), ' ', str);
478 },
479 [&](const BooleanJoystickAxis& a) {
480 return strCat(joystickManager.getDisplayName(a.getJoystick()),
481 " stick axis ", a.getAxis(), ", ",
482 (a.getDirection() == BooleanJoystickAxis::POS ? "positive" : "negative"), " direction");
483 }
484 }, input);
485}
486
487[[nodiscard]] static bool insideCircle(gl::vec2 mouse, gl::vec2 center, float radius)
488{
489 auto delta = center - mouse;
490 return gl::sum(delta * delta) <= (radius * radius);
491}
492[[nodiscard]] static bool between(float x, float min, float max)
493{
494 return (min <= x) && (x <= max);
495}
496
501[[nodiscard]] static bool insideRectangle(gl::vec2 mouse, Rectangle r)
502{
503 return between(mouse[0], r.topLeft[0], r.bottomRight[0]) &&
504 between(mouse[1], r.topLeft[1], r.bottomRight[1]);
505}
506
507
508static constexpr auto fractionDPad = 1.0f / 3.0f;
509static constexpr auto thickness = 3.0f;
510
511static void drawDPad(gl::vec2 center, float size, std::span<const uint8_t, 4> hovered, int hoveredRow)
512{
513 const auto F = fractionDPad;
514 std::array<std::array<ImVec2, 5 + 1>, 4> points = {
515 std::array<ImVec2, 5 + 1>{ // UP
516 center + size * gl::vec2{ 0, 0},
517 center + size * gl::vec2{-F, -F},
518 center + size * gl::vec2{-F, -1},
519 center + size * gl::vec2{ F, -1},
520 center + size * gl::vec2{ F, -F},
521 center + size * gl::vec2{ 0, 0},
522 },
523 std::array<ImVec2, 5 + 1>{ // DOWN
524 center + size * gl::vec2{ 0, 0},
525 center + size * gl::vec2{ F, F},
526 center + size * gl::vec2{ F, 1},
527 center + size * gl::vec2{-F, 1},
528 center + size * gl::vec2{-F, F},
529 center + size * gl::vec2{ 0, 0},
530 },
531 std::array<ImVec2, 5 + 1>{ // LEFT
532 center + size * gl::vec2{ 0, 0},
533 center + size * gl::vec2{-F, F},
534 center + size * gl::vec2{-1, F},
535 center + size * gl::vec2{-1, -F},
536 center + size * gl::vec2{-F, -F},
537 center + size * gl::vec2{ 0, 0},
538 },
539 std::array<ImVec2, 5 + 1>{ // RIGHT
540 center + size * gl::vec2{ 0, 0},
541 center + size * gl::vec2{ F, -F},
542 center + size * gl::vec2{ 1, -F},
543 center + size * gl::vec2{ 1, F},
544 center + size * gl::vec2{ F, F},
545 center + size * gl::vec2{ 0, 0},
546 },
547 };
548
549 auto* drawList = ImGui::GetWindowDrawList();
550 auto hoverColor = ImGui::GetColorU32(ImGuiCol_ButtonHovered);
551
552 auto color = getColor(imColor::TEXT);
553 for (auto i : xrange(4)) {
554 if (hovered[i] || (hoveredRow == i)) {
555 drawList->AddConvexPolyFilled(points[i].data(), 5, hoverColor);
556 }
557 drawList->AddPolyline(points[i].data(), 5 + 1, color, 0, thickness);
558 }
559}
560
561static void drawFilledCircle(gl::vec2 center, float radius, bool fill)
562{
563 auto* drawList = ImGui::GetWindowDrawList();
564 if (fill) {
565 auto hoverColor = ImGui::GetColorU32(ImGuiCol_ButtonHovered);
566 drawList->AddCircleFilled(center, radius, hoverColor);
567 }
568 auto color = getColor(imColor::TEXT);
569 drawList->AddCircle(center, radius, color, 0, thickness);
570}
571static void drawFilledRectangle(Rectangle r, float corner, bool fill)
572{
573 auto* drawList = ImGui::GetWindowDrawList();
574 if (fill) {
575 auto hoverColor = ImGui::GetColorU32(ImGuiCol_ButtonHovered);
576 drawList->AddRectFilled(r.topLeft, r.bottomRight, hoverColor, corner);
577 }
578 auto color = getColor(imColor::TEXT);
579 drawList->AddRect(r.topLeft, r.bottomRight, color, corner, 0, thickness);
580}
581
582static void drawLetterA(gl::vec2 center)
583{
584 auto* drawList = ImGui::GetWindowDrawList();
585 auto tr = [&](gl::vec2 p) { return center + p; };
586 const std::array<ImVec2, 3> lines = { tr({-6, 7}), tr({0, -7}), tr({6, 7}) };
587 auto color = getColor(imColor::TEXT);
588 drawList->AddPolyline(lines.data(), lines.size(), color, 0, thickness);
589 drawList->AddLine(tr({-3, 1}), tr({3, 1}), color, thickness);
590}
591static void drawLetterB(gl::vec2 center)
592{
593 auto* drawList = ImGui::GetWindowDrawList();
594 auto tr = [&](gl::vec2 p) { return center + p; };
595 const std::array<ImVec2, 4> lines = { tr({1, -7}), tr({-4, -7}), tr({-4, 7}), tr({2, 7}) };
596 auto color = getColor(imColor::TEXT);
597 drawList->AddPolyline(lines.data(), lines.size(), color, 0, thickness);
598 drawList->AddLine(tr({-4, -1}), tr({2, -1}), color, thickness);
599 drawList->AddBezierQuadratic(tr({1, -7}), tr({4, -7}), tr({4, -4}), color, thickness);
600 drawList->AddBezierQuadratic(tr({4, -4}), tr({4, -1}), tr({1, -1}), color, thickness);
601 drawList->AddBezierQuadratic(tr({2, -1}), tr({6, -1}), tr({6, 3}), color, thickness);
602 drawList->AddBezierQuadratic(tr({6, 3}), tr({6, 7}), tr({2, 7}), color, thickness);
603}
604static void drawLetterC(gl::vec2 center)
605{
606 auto* drawList = ImGui::GetWindowDrawList();
607 auto tr = [&](gl::vec2 p) { return center + p; };
608 auto color = getColor(imColor::TEXT);
609 drawList->AddBezierCubic(tr({5, -5}), tr({-8, -16}), tr({-8, 16}), tr({5, 5}), color, thickness);
610}
611static void drawLetterX(gl::vec2 center)
612{
613 auto* drawList = ImGui::GetWindowDrawList();
614 auto tr = [&](gl::vec2 p) { return center + p; };
615 auto color = getColor(imColor::TEXT);
616 drawList->AddLine(tr({-4, -6}), tr({4, 6}), color, thickness);
617 drawList->AddLine(tr({-4, 6}), tr({4, -6}), color, thickness);
618}
619static void drawLetterY(gl::vec2 center)
620{
621 auto* drawList = ImGui::GetWindowDrawList();
622 auto tr = [&](gl::vec2 p) { return center + p; };
623 auto color = getColor(imColor::TEXT);
624 drawList->AddLine(tr({-4, -6}), tr({0, 0}), color, thickness);
625 drawList->AddLine(tr({-4, 6}), tr({4, -6}), color, thickness);
626}
627static void drawLetterZ(gl::vec2 center)
628{
629 auto* drawList = ImGui::GetWindowDrawList();
630 auto tr = [&](gl::vec2 p) { return center + p; };
631 const std::array<ImVec2, 4> linesZ2 = { tr({-4, -6}), tr({4, -6}), tr({-4, 6}), tr({4, 6}) };
632 auto color = getColor(imColor::TEXT);
633 drawList->AddPolyline(linesZ2.data(), 4, color, 0, thickness);
634}
635
636namespace msxjoystick {
637
639
640static constexpr std::array<zstring_view, NUM_BUTTONS> buttonNames = {
641 "Up", "Down", "Left", "Right", "A", "B" // show in the GUI
642};
643static constexpr std::array<zstring_view, NUM_BUTTONS> keyNames = {
644 "UP", "DOWN", "LEFT", "RIGHT", "A", "B" // keys in Tcl dict
645};
646
647// Customize joystick look
648static constexpr auto boundingBox = gl::vec2{300.0f, 100.0f};
649static constexpr auto radius = 20.0f;
650static constexpr auto corner = 10.0f;
651static constexpr auto centerA = gl::vec2{200.0f, 50.0f};
652static constexpr auto centerB = gl::vec2{260.0f, 50.0f};
653static constexpr auto centerDPad = gl::vec2{50.0f, 50.0f};
654static constexpr auto sizeDPad = 30.0f;
655
656[[nodiscard]] static std::vector<uint8_t> buttonsHovered(gl::vec2 mouse)
657{
658 std::vector<uint8_t> result(NUM_BUTTONS); // false
659 auto mouseDPad = (mouse - centerDPad) * (1.0f / sizeDPad);
660 if (insideRectangle(mouseDPad, Rectangle{{-1, -1}, {1, 1}}) &&
661 (between(mouseDPad[0], -fractionDPad, fractionDPad) ||
662 between(mouseDPad[1], -fractionDPad, fractionDPad))) { // mouse over d-pad
663 bool t1 = mouseDPad[0] < mouseDPad[1];
664 bool t2 = mouseDPad[0] < -mouseDPad[1];
665 result[UP] = !t1 && t2;
666 result[DOWN] = t1 && !t2;
667 result[LEFT] = t1 && t2;
668 result[RIGHT] = !t1 && !t2;
669 }
670 result[TRIG_A] = insideCircle(mouse, centerA, radius);
671 result[TRIG_B] = insideCircle(mouse, centerB, radius);
672 return result;
673}
674
675static void draw(gl::vec2 scrnPos, std::span<uint8_t> hovered, int hoveredRow)
676{
677 auto* drawList = ImGui::GetWindowDrawList();
678
679 auto color = getColor(imColor::TEXT);
680 drawList->AddRect(scrnPos, scrnPos + boundingBox, color, corner, 0, thickness);
681
682 drawDPad(scrnPos + centerDPad, sizeDPad, subspan<4>(hovered), hoveredRow);
683
684 auto scrnCenterA = scrnPos + centerA;
685 drawFilledCircle(scrnCenterA, radius, hovered[TRIG_A] || (hoveredRow == TRIG_A));
686 drawLetterA(scrnCenterA);
687
688 auto scrnCenterB = scrnPos + centerB;
689 drawFilledCircle(scrnCenterB, radius, hovered[TRIG_B] || (hoveredRow == TRIG_B));
690 drawLetterB(scrnCenterB);
691}
692
693} // namespace msxjoystick
694
695namespace joymega {
696
697enum {UP, DOWN, LEFT, RIGHT,
698 TRIG_A, TRIG_B, TRIG_C,
701 NUM_BUTTONS, NUM_DIRECTIONS = TRIG_A};
702
703static constexpr std::array<zstring_view, NUM_BUTTONS> buttonNames = { // show in the GUI
704 "Up", "Down", "Left", "Right",
705 "A", "B", "C",
706 "X", "Y", "Z",
707 "Select", "Start",
708};
709static constexpr std::array<zstring_view, NUM_BUTTONS> keyNames = { // keys in Tcl dict
710 "UP", "DOWN", "LEFT", "RIGHT",
711 "A", "B", "C",
712 "X", "Y", "Z",
713 "SELECT", "START",
714};
715
716// Customize joystick look
717static constexpr auto thickness = 3.0f;
718static constexpr auto boundingBox = gl::vec2{300.0f, 158.0f};
719static constexpr auto centerA = gl::vec2{205.0f, 109.9f};
720static constexpr auto centerB = gl::vec2{235.9f, 93.5f};
721static constexpr auto centerC = gl::vec2{269.7f, 83.9f};
722static constexpr auto centerX = gl::vec2{194.8f, 75.2f};
723static constexpr auto centerY = gl::vec2{223.0f, 61.3f};
724static constexpr auto centerZ = gl::vec2{252.2f, 52.9f};
725static constexpr auto selectBox = Rectangle{gl::vec2{130.0f, 60.0f}, gl::vec2{160.0f, 70.0f}};
726static constexpr auto startBox = Rectangle{gl::vec2{130.0f, 86.0f}, gl::vec2{160.0f, 96.0f}};
727static constexpr auto radiusABC = 16.2f;
728static constexpr auto radiusXYZ = 12.2f;
729static constexpr auto centerDPad = gl::vec2{65.6f, 82.7f};
730static constexpr auto sizeDPad = 34.0f;
731static constexpr auto fractionDPad = 1.0f / 3.0f;
732
733[[nodiscard]] static std::vector<uint8_t> buttonsHovered(gl::vec2 mouse)
734{
735 std::vector<uint8_t> result(NUM_BUTTONS); // false
736 auto mouseDPad = (mouse - centerDPad) * (1.0f / sizeDPad);
737 if (insideRectangle(mouseDPad, Rectangle{{-1, -1}, {1, 1}}) &&
738 (between(mouseDPad[0], -fractionDPad, fractionDPad) ||
739 between(mouseDPad[1], -fractionDPad, fractionDPad))) { // mouse over d-pad
740 bool t1 = mouseDPad[0] < mouseDPad[1];
741 bool t2 = mouseDPad[0] < -mouseDPad[1];
742 result[UP] = !t1 && t2;
743 result[DOWN] = t1 && !t2;
744 result[LEFT] = t1 && t2;
745 result[RIGHT] = !t1 && !t2;
746 }
747 result[TRIG_A] = insideCircle(mouse, centerA, radiusABC);
748 result[TRIG_B] = insideCircle(mouse, centerB, radiusABC);
749 result[TRIG_C] = insideCircle(mouse, centerC, radiusABC);
750 result[TRIG_X] = insideCircle(mouse, centerX, radiusXYZ);
751 result[TRIG_Y] = insideCircle(mouse, centerY, radiusXYZ);
752 result[TRIG_Z] = insideCircle(mouse, centerZ, radiusXYZ);
753 result[TRIG_START] = insideRectangle(mouse, startBox);
754 result[TRIG_SELECT] = insideRectangle(mouse, selectBox);
755 return result;
756}
757
758static void draw(gl::vec2 scrnPos, std::span<uint8_t> hovered, int hoveredRow)
759{
760 auto* drawList = ImGui::GetWindowDrawList();
761 auto tr = [&](gl::vec2 p) { return scrnPos + p; };
762 auto color = getColor(imColor::TEXT);
763
764 auto drawBezierCurve = [&](std::span<const gl::vec2> points, float thick = 1.0f) {
765 assert((points.size() % 2) == 0);
766 for (size_t i = 0; i < points.size() - 2; i += 2) {
767 auto ap = points[i + 0];
768 auto ad = points[i + 1];
769 auto bp = points[i + 2];
770 auto bd = points[i + 3];
771 drawList->AddBezierCubic(tr(ap), tr(ap + ad), tr(bp - bd), tr(bp), color, thick);
772 }
773 };
774
775 std::array outLine = {
776 gl::vec2{150.0f, 0.0f}, gl::vec2{ 23.1f, 0.0f},
777 gl::vec2{258.3f, 30.3f}, gl::vec2{ 36.3f, 26.4f},
778 gl::vec2{300.0f, 107.0f}, gl::vec2{ 0.0f, 13.2f},
779 gl::vec2{285.2f, 145.1f}, gl::vec2{ -9.9f, 9.9f},
780 gl::vec2{255.3f, 157.4f}, gl::vec2{ -9.0f, 0.0f},
781 gl::vec2{206.0f, 141.8f}, gl::vec2{-16.2f, -5.6f},
782 gl::vec2{150.0f, 131.9f}, gl::vec2{-16.5f, 0.0f},
783 gl::vec2{ 94.0f, 141.8f}, gl::vec2{-16.2f, 5.6f},
784 gl::vec2{ 44.7f, 157.4f}, gl::vec2{ -9.0f, 0.0f},
785 gl::vec2{ 14.8f, 145.1f}, gl::vec2{ -9.9f, -9.9f},
786 gl::vec2{ 0.0f, 107.0f}, gl::vec2{ 0.0f, -13.2f},
787 gl::vec2{ 41.7f, 30.3f}, gl::vec2{ 36.3f, -26.4f},
788 gl::vec2{150.0f, 0.0f}, gl::vec2{ 23.1f, 0.0f}, // closed loop
789 };
790 drawBezierCurve(outLine, thickness);
791
792 drawDPad(tr(centerDPad), sizeDPad, subspan<4>(hovered), hoveredRow);
793 drawList->AddCircle(tr(centerDPad), 43.0f, color);
794 std::array dPadCurve = {
795 gl::vec2{77.0f, 33.0f}, gl::vec2{ 69.2f, 0.0f},
796 gl::vec2{54.8f, 135.2f}, gl::vec2{-66.9f, 0.0f},
797 gl::vec2{77.0f, 33.0f}, gl::vec2{ 69.2f, 0.0f},
798 };
799 drawBezierCurve(dPadCurve);
800
801 drawFilledCircle(tr(centerA), radiusABC, hovered[TRIG_A] || (hoveredRow == TRIG_A));
802 drawLetterA(tr(centerA));
803 drawFilledCircle(tr(centerB), radiusABC, hovered[TRIG_B] || (hoveredRow == TRIG_B));
804 drawLetterB(tr(centerB));
805 drawFilledCircle(tr(centerC), radiusABC, hovered[TRIG_C] || (hoveredRow == TRIG_C));
806 drawLetterC(tr(centerC));
807 drawFilledCircle(tr(centerX), radiusXYZ, hovered[TRIG_X] || (hoveredRow == TRIG_X));
808 drawLetterX(tr(centerX));
809 drawFilledCircle(tr(centerY), radiusXYZ, hovered[TRIG_Y] || (hoveredRow == TRIG_Y));
810 drawLetterY(tr(centerY));
811 drawFilledCircle(tr(centerZ), radiusXYZ, hovered[TRIG_Z] || (hoveredRow == TRIG_Z));
812 drawLetterZ(tr(centerZ));
813 std::array buttonCurve = {
814 gl::vec2{221.1f, 28.9f}, gl::vec2{ 80.1f, 0.0f},
815 gl::vec2{236.9f, 139.5f}, gl::vec2{-76.8f, 0.0f},
816 gl::vec2{221.1f, 28.9f}, gl::vec2{ 80.1f, 0.0f},
817 };
818 drawBezierCurve(buttonCurve);
819
820 auto corner = (selectBox.bottomRight[1] - selectBox.topLeft[1]) * 0.5f;
821 auto trR = [&](Rectangle r) { return Rectangle{tr(r.topLeft), tr(r.bottomRight)}; };
822 drawFilledRectangle(trR(selectBox), corner, hovered[TRIG_SELECT] || (hoveredRow == TRIG_SELECT));
823 drawList->AddText(ImGui::GetFont(), ImGui::GetFontSize(), tr({123.0f, 46.0f}), color, "Select");
824 drawFilledRectangle(trR(startBox), corner, hovered[TRIG_START] || (hoveredRow == TRIG_START));
825 drawList->AddText(ImGui::GetFont(), ImGui::GetFontSize(), tr({128.0f, 97.0f}), color, "Start");
826}
827
828} // namespace joymega
829
830void ImGuiSettings::paintJoystick(MSXMotherBoard& motherBoard)
831{
832 ImGui::SetNextWindowSize(gl::vec2{316, 323}, ImGuiCond_FirstUseEver);
833 im::Window("Configure MSX joysticks", &showConfigureJoystick, [&]{
834 ImGui::SetNextItemWidth(13.0f * ImGui::GetFontSize());
835 im::Combo("Select joystick", joystickToGuiString(joystick).c_str(), [&]{
836 for (const auto& j : xrange(4)) {
837 if (ImGui::Selectable(joystickToGuiString(j).c_str())) {
838 joystick = j;
839 }
840 }
841 });
842
843 auto& joystickManager = manager.getReactor().getInputEventGenerator().getJoystickManager();
844 auto& controller = motherBoard.getMSXCommandController();
845 auto* setting = dynamic_cast<StringSetting*>(controller.findSetting(settingName(joystick)));
846 if (!setting) return;
847 auto& interp = setting->getInterpreter();
848 TclObject bindings = setting->getValue();
849
850 gl::vec2 scrnPos = ImGui::GetCursorScreenPos();
851 gl::vec2 mouse = gl::vec2(ImGui::GetIO().MousePos) - scrnPos;
852
853 // Check if buttons are hovered
854 bool msxOrMega = joystick < 2;
855 auto hovered = msxOrMega ? msxjoystick::buttonsHovered(mouse)
856 : joymega ::buttonsHovered(mouse);
857 const auto numButtons = hovered.size();
858 using SP = std::span<const zstring_view>;
859 auto keyNames = msxOrMega ? SP{msxjoystick::keyNames}
860 : SP{joymega ::keyNames};
861 auto buttonNames = msxOrMega ? SP{msxjoystick::buttonNames}
862 : SP{joymega ::buttonNames};
863
864 // Any joystick button clicked?
865 std::optional<int> addAction;
866 std::optional<int> removeAction;
867 if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
868 for (auto i : xrange(numButtons)) {
869 if (hovered[i]) addAction = narrow<int>(i);
870 }
871 }
872
873 ImGui::Dummy(msxOrMega ? msxjoystick::boundingBox : joymega::boundingBox); // reserve space for joystick drawing
874
875 // Draw table
876 int hoveredRow = -1;
877 const auto& style = ImGui::GetStyle();
878 auto textHeight = ImGui::GetTextLineHeight();
879 float rowHeight = 2.0f * style.FramePadding.y + textHeight;
880 float bottomHeight = style.ItemSpacing.y + 2.0f * style.FramePadding.y + textHeight;
881 im::Table("##joystick-table", 2, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollX, {0.0f, -bottomHeight}, [&]{
882 im::ID_for_range(numButtons, [&](int i) {
883 TclObject key(keyNames[i]);
884 TclObject bindingList = bindings.getDictValue(interp, key);
885 if (ImGui::TableNextColumn()) {
886 auto pos = ImGui::GetCursorPos();
887 ImGui::Selectable("##row", hovered[i], ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap, ImVec2(0, rowHeight));
888 if (ImGui::IsItemHovered()) {
889 hoveredRow = i;
890 }
891
892 ImGui::SetCursorPos(pos);
893 ImGui::AlignTextToFramePadding();
894 ImGui::TextUnformatted(buttonNames[i]);
895 }
896 if (ImGui::TableNextColumn()) {
897 if (ImGui::Button("Add")) {
898 addAction = i;
899 }
900 ImGui::SameLine();
901 auto numBindings = bindingList.size();
902 im::Disabled(numBindings == 0, [&]{
903 if (ImGui::Button("Remove")) {
904 if (numBindings == 1) {
905 bindings.setDictValue(interp, key, TclObject{});
906 setting->setValue(bindings);
907 } else {
908 removeAction = i;
909 }
910 }
911 });
912 ImGui::SameLine();
913 if (numBindings == 0) {
914 ImGui::TextDisabled("no bindings");
915 } else {
916 size_t lastBindingIndex = numBindings - 1;
917 size_t bindingIndex = 0;
918 for (auto binding: bindingList) {
919 ImGui::TextUnformatted(binding);
920 simpleToolTip(toGuiString(*parseBooleanInput(binding), joystickManager));
921 if (bindingIndex < lastBindingIndex) {
922 ImGui::SameLine();
924 ImGui::SameLine();
925 }
926 ++bindingIndex;
927 }
928 }
929 }
930 });
931 });
932 msxOrMega ? msxjoystick::draw(scrnPos, hovered, hoveredRow)
933 : joymega ::draw(scrnPos, hovered, hoveredRow);
934
935 if (ImGui::Button("Default bindings...")) {
936 ImGui::OpenPopup("bindings");
937 }
938 im::Popup("bindings", [&]{
939 auto addOrSet = [&](auto getBindings) {
940 if (ImGui::MenuItem("Add to current bindings")) {
941 // merge 'newBindings' into 'bindings'
942 auto newBindings = getBindings();
943 for (auto k : xrange(int(numButtons))) {
944 TclObject key(keyNames[k]);
945 TclObject dstList = bindings.getDictValue(interp, key);
946 TclObject srcList = newBindings.getDictValue(interp, key);
947 // This is O(N^2), but that's fine (here).
948 for (auto b : srcList) {
949 if (!contains(dstList, b)) {
950 dstList.addListElement(b);
951 }
952 }
953 bindings.setDictValue(interp, key, dstList);
954 }
955 setting->setValue(bindings);
956 }
957 if (ImGui::MenuItem("Replace current bindings")) {
958 setting->setValue(getBindings());
959 }
960 };
961 im::Menu("Keyboard", [&]{
962 addOrSet([] {
963 return TclObject(TclObject::MakeDictTag{},
964 "UP", makeTclList("keyb Up"),
965 "DOWN", makeTclList("keyb Down"),
966 "LEFT", makeTclList("keyb Left"),
967 "RIGHT", makeTclList("keyb Right"),
968 "A", makeTclList("keyb Space"),
969 "B", makeTclList("keyb M"));
970 });
971 });
972 for (auto joyId : joystickManager.getConnectedJoysticks()) {
973 im::Menu(joystickManager.getDisplayName(joyId).c_str(), [&]{
974 addOrSet([&]{
975 return msxOrMega
976 ? MSXJoystick::getDefaultConfig(joyId, joystickManager)
977 : JoyMega::getDefaultConfig(joyId, joystickManager);
978 });
979 });
980 }
981 });
982
983 // Popup for 'Add'
984 static constexpr auto addTitle = "Waiting for input";
985 if (addAction) {
986 popupForKey = *addAction;
987 popupTimeout = 5.0f;
988 initListener();
989 ImGui::OpenPopup(addTitle);
990 }
991 im::PopupModal(addTitle, nullptr, ImGuiWindowFlags_NoSavedSettings, [&]{
992 auto close = [&]{
993 ImGui::CloseCurrentPopup();
994 popupForKey = unsigned(-1);
995 deinitListener();
996 };
997 if (popupForKey >= numButtons) {
998 close();
999 return;
1000 }
1001
1002 ImGui::Text("Enter event for joystick button '%s'", buttonNames[popupForKey].c_str());
1003 ImGui::Text("Or press ESC to cancel. Timeout in %d seconds.", int(popupTimeout));
1004
1005 popupTimeout -= ImGui::GetIO().DeltaTime;
1006 if (popupTimeout <= 0.0f) {
1007 close();
1008 }
1009 });
1010
1011 // Popup for 'Remove'
1012 if (removeAction) {
1013 popupForKey = *removeAction;
1014 ImGui::OpenPopup("remove");
1015 }
1016 im::Popup("remove", [&]{
1017 auto close = [&]{
1018 ImGui::CloseCurrentPopup();
1019 popupForKey = unsigned(-1);
1020 };
1021 if (popupForKey >= numButtons) {
1022 close();
1023 return;
1024 }
1025 TclObject key(keyNames[popupForKey]);
1026 TclObject bindingList = bindings.getDictValue(interp, key);
1027
1028 unsigned remove = unsigned(-1);
1029 unsigned counter = 0;
1030 for (const auto& b : bindingList) {
1031 if (ImGui::Selectable(b.c_str())) {
1032 remove = counter;
1033 }
1034 simpleToolTip(toGuiString(*parseBooleanInput(b), joystickManager));
1035 ++counter;
1036 }
1037 if (remove != unsigned(-1)) {
1038 bindingList.removeListIndex(interp, remove);
1039 bindings.setDictValue(interp, key, bindingList);
1040 setting->setValue(bindings);
1041 close();
1042 }
1043
1044 if (ImGui::Selectable("all bindings")) {
1045 bindings.setDictValue(interp, key, TclObject{});
1046 setting->setValue(bindings);
1047 close();
1048 }
1049 });
1050 });
1051}
1052
1053void ImGuiSettings::paintFont()
1054{
1055 im::Window("Select font", &showFont, [&]{
1056 auto selectFilename = [&](FilenameSetting& setting, float width) {
1057 auto display = [](std::string_view name) {
1058 if (name.ends_with(".gz" )) name.remove_suffix(3);
1059 if (name.ends_with(".ttf")) name.remove_suffix(4);
1060 return std::string(name);
1061 };
1062 auto current = setting.getString();
1063 ImGui::SetNextItemWidth(width);
1064 im::Combo(tmpStrCat("##", setting.getBaseName()).c_str(), display(current).c_str(), [&]{
1065 for (const auto& font : getAvailableFonts()) {
1066 if (ImGui::Selectable(display(font).c_str(), current == font)) {
1067 setting.setString(font);
1068 }
1069 }
1070 });
1071 };
1072 auto selectSize = [](IntegerSetting& setting) {
1073 auto display = [](int s) { return strCat(s); };
1074 auto current = setting.getInt();
1075 ImGui::SetNextItemWidth(4.0f * ImGui::GetFontSize());
1076 im::Combo(tmpStrCat("##", setting.getBaseName()).c_str(), display(current).c_str(), [&]{
1077 for (int size : {9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 22, 24, 26, 28, 30, 32}) {
1078 if (ImGui::Selectable(display(size).c_str(), current == size)) {
1079 setting.setInt(size);
1080 }
1081 }
1082 });
1083 };
1084
1085 auto pos = ImGui::CalcTextSize("Monospace").x + 2.0f * ImGui::GetStyle().ItemSpacing.x;
1086 auto width = 12.0f * ImGui::GetFontSize(); // filename ComboBox (boxes are drawn with different font, but we want same width)
1087
1088 ImGui::AlignTextToFramePadding();
1089 ImGui::TextUnformatted("Normal");
1090 ImGui::SameLine(pos);
1091 selectFilename(manager.fontPropFilename, width);
1092 ImGui::SameLine();
1093 selectSize(manager.fontPropSize);
1094 HelpMarker("You can install more fonts by copying .ttf file(s) to your \"<openmsx>/share/skins\" directory.");
1095
1096 ImGui::AlignTextToFramePadding();
1097 ImGui::TextUnformatted("Monospace");
1098 ImGui::SameLine(pos);
1099 im::Font(manager.fontMono, [&]{
1100 selectFilename(manager.fontMonoFilename, width);
1101 });
1102 ImGui::SameLine();
1103 selectSize(manager.fontMonoSize);
1104 HelpMarker("Some GUI elements (e.g. the console) require a monospaced font.");
1105 });
1106}
1107
1108void ImGuiSettings::paint(MSXMotherBoard* motherBoard)
1109{
1110 if (selectedStyle < 0) {
1111 // triggers when loading "imgui.ini" did not select a style
1112 selectedStyle = 0; // dark (also the default (recommended) Dear ImGui style)
1113 setStyle();
1114 }
1115 if (motherBoard && showConfigureJoystick) paintJoystick(*motherBoard);
1116 if (showFont) paintFont();
1117}
1118
1119std::span<const std::string> ImGuiSettings::getAvailableFonts()
1120{
1121 if (availableFonts.empty()) {
1122 auto context = systemFileContext();
1123 for (const auto& path : context.getPaths()) {
1124 foreach_file(FileOperations::join(path, "skins"), [&](const std::string& /*fullName*/, std::string_view name) {
1125 if (name.ends_with(".ttf.gz") || name.ends_with(".ttf")) {
1126 availableFonts.emplace_back(name);
1127 }
1128 });
1129 }
1130 // sort and remove duplicates
1131 ranges::sort(availableFonts);
1132 availableFonts.erase(ranges::unique(availableFonts), end(availableFonts));
1133 }
1134 return availableFonts;
1135}
1136
1137int ImGuiSettings::signalEvent(const Event& event)
1138{
1139 bool msxOrMega = joystick < 2;
1140 using SP = std::span<const zstring_view>;
1141 auto keyNames = msxOrMega ? SP{msxjoystick::keyNames}
1142 : SP{joymega ::keyNames};
1143 const auto numButtons = keyNames.size();
1144
1145 if (popupForKey >= numButtons) {
1146 deinitListener();
1147 return 0;
1148 }
1149
1150 bool escape = false;
1151 if (const auto* keyDown = get_event_if<KeyDownEvent>(event)) {
1152 escape = keyDown->getKeyCode() == SDLK_ESCAPE;
1153 }
1154 if (!escape) {
1155 auto getJoyDeadZone = [&](JoystickId joyId) {
1156 auto& joyMan = manager.getReactor().getInputEventGenerator().getJoystickManager();
1157 auto* setting = joyMan.getJoyDeadZoneSetting(joyId);
1158 return setting ? setting->getInt() : 0;
1159 };
1160 auto b = captureBooleanInput(event, getJoyDeadZone);
1161 if (!b) return EventDistributor::HOTKEY; // keep popup active
1162 auto bs = toString(*b);
1163
1164 auto* motherBoard = manager.getReactor().getMotherBoard();
1165 if (!motherBoard) return EventDistributor::HOTKEY;
1166 auto& controller = motherBoard->getMSXCommandController();
1167 auto* setting = dynamic_cast<StringSetting*>(controller.findSetting(settingName(joystick)));
1168 if (!setting) return EventDistributor::HOTKEY;
1169 auto& interp = setting->getInterpreter();
1170
1171 TclObject bindings = setting->getValue();
1172 TclObject key(keyNames[popupForKey]);
1173 TclObject bindingList = bindings.getDictValue(interp, key);
1174
1175 if (!contains(bindingList, bs)) {
1176 bindingList.addListElement(bs);
1177 bindings.setDictValue(interp, key, bindingList);
1178 setting->setValue(bindings);
1179 }
1180 }
1181
1182 popupForKey = unsigned(-1); // close popup
1183 return EventDistributor::HOTKEY; // block event
1184}
1185
1186void ImGuiSettings::initListener()
1187{
1188 if (listening) return;
1189 listening = true;
1190
1191 auto& distributor = manager.getReactor().getEventDistributor();
1192 // highest priority (higher than HOTKEY and IMGUI)
1193 distributor.registerEventListener(EventType::KEY_DOWN, *this);
1194 distributor.registerEventListener(EventType::MOUSE_BUTTON_DOWN, *this);
1195 distributor.registerEventListener(EventType::JOY_BUTTON_DOWN, *this);
1196 distributor.registerEventListener(EventType::JOY_HAT, *this);
1197 distributor.registerEventListener(EventType::JOY_AXIS_MOTION, *this);
1198}
1199
1200void ImGuiSettings::deinitListener()
1201{
1202 if (!listening) return;
1203 listening = false;
1204
1205 auto& distributor = manager.getReactor().getEventDistributor();
1206 distributor.unregisterEventListener(EventType::JOY_AXIS_MOTION, *this);
1207 distributor.unregisterEventListener(EventType::JOY_HAT, *this);
1208 distributor.unregisterEventListener(EventType::JOY_BUTTON_DOWN, *this);
1209 distributor.unregisterEventListener(EventType::MOUSE_BUTTON_DOWN, *this);
1210 distributor.unregisterEventListener(EventType::KEY_DOWN, *this);
1211}
1212
1213} // namespace openmsx
BaseSetting * setting
const char * c_str() const
std::string_view getBaseName() const
Definition Setting.hh:38
A Setting with a floating point value.
std::unique_ptr< ImGuiSoundChip > soundChip
std::unique_ptr< ImGuiMessages > messages
std::unique_ptr< ImGuiOsdIcons > osdIcons
ImGuiManager & manager
Definition ImGuiPart.hh:30
void save(ImGuiTextBuffer &buf) override
void showMenu(MSXMotherBoard *motherBoard) override
void loadLine(std::string_view name, zstring_view value) override
void loadEnd() override
A Setting with an integer value.
auto * getR800()
Definition MSXCPU.hh:155
auto * getZ80()
Definition MSXCPU.hh:154
VideoSourceSetting & getVideoSource()
MSXCommandController & getMSXCommandController()
GlobalSettings & getGlobalSettings()
Definition Reactor.hh:114
ScaleAlgorithm
Scaler algorithm.
static const bool RELEASE
Definition Version.hh:12
Like std::string_view, but with the extra guarantee that it refers to a zero-terminated string.
auto CalcTextSize(std::string_view str)
Definition ImGuiUtils.hh:37
void TextUnformatted(const std::string &str)
Definition ImGuiUtils.hh:24
vecN< 2, float > vec2
Definition gl_vec.hh:150
constexpr T sum(const vecN< N, T > &x)
Definition gl_vec.hh:308
constexpr vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
Definition gl_vec.hh:285
void Table(const char *str_id, int column, ImGuiTableFlags flags, const ImVec2 &outer_size, float inner_width, std::invocable<> auto next)
Definition ImGuiCpp.hh:473
void Window(const char *name, bool *p_open, ImGuiWindowFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:63
void PopupContextItem(const char *str_id, ImGuiPopupFlags popup_flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:439
bool TreeNode(const char *label, ImGuiTreeNodeFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:320
void Combo(const char *label, const char *preview_value, ImGuiComboFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:307
void ListBox(const char *label, const ImVec2 &size, std::invocable<> auto next)
Definition ImGuiCpp.hh:346
void PopupModal(const char *name, bool *p_open, ImGuiWindowFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:422
bool Menu(const char *label, bool enabled, std::invocable<> auto next)
Definition ImGuiCpp.hh:377
void Disabled(bool b, std::invocable<> auto next)
Definition ImGuiCpp.hh:524
void Font(ImFont *font, std::invocable<> auto next)
Definition ImGuiCpp.hh:125
void Indent(float indent_w, std::invocable<> auto next)
Definition ImGuiCpp.hh:238
void Popup(const char *str_id, ImGuiWindowFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:409
void ID_for_range(int count, std::invocable< int > auto next)
Definition ImGuiCpp.hh:295
string_view stripExtension(string_view path)
Returns the path without extension.
string_view getFilename(string_view path)
Returns the file portion of a path name.
int unlink(zstring_view path)
Call unlink() in a platform-independent manner.
This file implemented 3 utility functions:
Definition Autofire.cc:9
std::optional< BooleanInput > captureBooleanInput(const Event &event, std::function< int(JoystickId)> getJoyDeadZone)
const FileContext & systemFileContext()
bool Checkbox(const HotKey &hotKey, BooleanSetting &setting)
Definition ImGuiUtils.cc:80
bool SliderFloat(FloatSetting &setting, const char *format, ImGuiSliderFlags flags)
bool SliderInt(IntegerSetting &setting, ImGuiSliderFlags flags)
bool loadOnePersistent(std::string_view name, zstring_view value, C &c, const std::tuple< Elements... > &tup)
void simpleToolTip(std::string_view desc)
Definition ImGuiUtils.hh:66
void savePersistent(ImGuiTextBuffer &buf, C &c, const std::tuple< Elements... > &tup)
std::string getShortCutForCommand(const HotKey &hotkey, std::string_view command)
void ComboBox(const char *label, Setting &setting, std::function< std::string(const std::string &)> displayValue, EnumToolTips toolTips)
void HelpMarker(std::string_view desc)
Definition ImGuiUtils.cc:22
std::optional< BooleanInput > parseBooleanInput(std::string_view text)
bool InputText(Setting &setting)
ImU32 getColor(imColor col)
void setColors(int style)
std::string toString(const BooleanInput &input)
bool foreach_file(std::string path, FileAction fileAction)
TclObject makeTclList(Args &&... args)
Definition TclObject.hh:289
FileContext userDataFileContext(string_view subDir)
auto unique(ForwardRange &&range)
Definition ranges.hh:222
auto remove(ForwardRange &&range, const T &value)
Definition ranges.hh:281
auto find(InputRange &&range, const T &value)
Definition ranges.hh:160
constexpr void replace(ForwardRange &&range, const T &old_value, const T &new_value)
Definition ranges.hh:293
constexpr void sort(RandomAccessRange &&range)
Definition ranges.hh:49
size_t size(std::string_view utf8)
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
TemporaryString tmpStrCat(Ts &&... ts)
Definition strCat.hh:742
#define UNREACHABLE
constexpr auto xrange(T e)
Definition xrange.hh:132
constexpr auto end(const zstring_view &x)