49#include <imgui_stdlib.h>
55using namespace std::literals;
79void ImGuiSettings::setStyle()
const
81 switch (selectedStyle) {
82 case 0: ImGui::StyleColorsDark();
break;
83 case 1: ImGui::StyleColorsLight();
break;
84 case 2: ImGui::StyleColorsClassic();
break;
93[[nodiscard]]
static ImGuiKeyChord getCurrentlyPressedKeyChord()
95 static constexpr auto mods = std::array{
96 ImGuiKey_LeftCtrl, ImGuiKey_LeftShift, ImGuiKey_LeftAlt, ImGuiKey_LeftSuper,
97 ImGuiKey_RightCtrl, ImGuiKey_RightShift, ImGuiKey_RightAlt, ImGuiKey_RightSuper,
98 ImGuiKey_ReservedForModCtrl, ImGuiKey_ReservedForModShift, ImGuiKey_ReservedForModAlt,
99 ImGuiKey_ReservedForModSuper, ImGuiKey_MouseLeft, ImGuiKey_MouseRight, ImGuiKey_MouseMiddle,
100 ImGuiKey_MouseX1, ImGuiKey_MouseX2, ImGuiKey_MouseWheelX, ImGuiKey_MouseWheelY,
102 for (
int key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; ++key) {
105 if (ImGui::IsKeyPressed(
static_cast<ImGuiKey
>(key))) {
106 const ImGuiIO& io = ImGui::GetIO();
108 | (io.KeyCtrl ? ImGuiMod_Ctrl : 0)
109 | (io.KeyShift ? ImGuiMod_Shift : 0)
110 | (io.KeyAlt ? ImGuiMod_Alt : 0)
111 | (io.KeySuper ? ImGuiMod_Super : 0);
114 return ImGuiKey_None;
119 bool openConfirmPopup =
false;
124 auto& renderSettings = reactor.getDisplay().getRenderSettings();
125 const auto& settingsManager = reactor.getGlobalCommandController().getSettingsManager();
126 const auto& hotKey = reactor.getHotKey();
129 im::TreeNode(
"Look and feel", ImGuiTreeNodeFlags_DefaultOpen, [&]{
130 auto& scaler = renderSettings.getScaleAlgorithmSetting();
139 static constexpr std::array algoEnables = {
141 AlgoEnable{SIMPLE,
true,
true },
142 AlgoEnable{SCALE,
false,
false},
143 AlgoEnable{HQ,
false,
false},
144 AlgoEnable{HQLITE,
false,
false},
145 AlgoEnable{RGBTRIPLET,
true,
true },
146 AlgoEnable{TV,
true,
false},
148 auto it =
ranges::find(algoEnables, scaler.getEnum(), &AlgoEnable::algo);
149 assert(it != algoEnables.end());
151 SliderInt(
"Scanline (%)", renderSettings.getScanlineSetting());
154 SliderInt(
"Blur (%)", renderSettings.getBlurSetting());
158 SliderInt(
"Scale factor", renderSettings.getScaleFactorSetting());
159 Checkbox(hotKey,
"Deinterlace", renderSettings.getDeinterlaceSetting());
160 Checkbox(hotKey,
"Deflicker", renderSettings.getDeflickerSetting());
162 im::TreeNode(
"Colors", ImGuiTreeNodeFlags_DefaultOpen, [&]{
163 SliderFloat(
"Noise (%)", renderSettings.getNoiseSetting());
164 SliderFloat(
"Brightness", renderSettings.getBrightnessSetting());
165 SliderFloat(
"Contrast", renderSettings.getContrastSetting());
166 SliderFloat(
"Gamma", renderSettings.getGammaSetting());
167 SliderInt(
"Glow (%)", renderSettings.getGlowSetting());
168 if (
auto* monitor =
dynamic_cast<Setting*
>(settingsManager.findSetting(
"monitor_type"))) {
169 ComboBox(
"Monitor type", *monitor, [](std::string s) {
175 im::TreeNode(
"Shape", ImGuiTreeNodeFlags_DefaultOpen, [&]{
176 SliderFloat(
"Horizontal stretch", renderSettings.getHorizontalStretchSetting(),
"%.0f");
177 ComboBox(
"Display deformation", renderSettings.getDisplayDeformSetting());
179 im::TreeNode(
"Misc", ImGuiTreeNodeFlags_DefaultOpen, [&]{
180 Checkbox(hotKey,
"Full screen", renderSettings.getFullScreenSetting());
184 Checkbox(hotKey,
"VSync", renderSettings.getVSyncSetting());
185 SliderInt(
"Minimum frame-skip", renderSettings.getMinFrameSkipSetting());
186 SliderInt(
"Maximum frame-skip", renderSettings.getMaxFrameSkipSetting());
189 Checkbox(hotKey,
"Enforce VDP sprites-per-line limit", renderSettings.getLimitSpritesSetting());
190 Checkbox(hotKey,
"Disable sprites", renderSettings.getDisableSpritesSetting());
191 ComboBox(
"Way to handle too fast VDP access", renderSettings.getTooFastAccessSetting());
192 ComboBox(
"Emulate VDP command timing", renderSettings.getCmdTimingSetting());
196 auto& mixer = reactor.getMixer();
197 auto& muteSetting = mixer.getMuteSetting();
199 SliderInt(
"Master volume", mixer.getMasterVolume());
201 Checkbox(hotKey,
"Mute", muteSetting);
203 static constexpr std::array resamplerToolTips = {
205 EnumToolTip{
"blip",
"good speed/quality tradeoff"},
208 ComboBox(
"Resampler", globalSettings.getResampleSetting(), resamplerToolTips);
211 ImGui::MenuItem(
"Show sound chip settings",
nullptr, &
manager.
soundChip->showSoundChipSettings);
214 im::TreeNode(
"Emulation", ImGuiTreeNodeFlags_DefaultOpen, [&]{
216 HelpMarker(
"These control the speed of the whole MSX machine, "
217 "the running MSX software can't tell the difference.");
219 auto& speedManager = globalSettings.getSpeedManager();
220 auto& fwdSetting = speedManager.getFastForwardSetting();
221 int fastForward = fwdSetting.getBoolean() ? 1 : 0;
224 bool fwdChanged = ImGui::RadioButton(
"normal", &fastForward, 0);
226 fwdChanged |= ImGui::RadioButton(
"fast forward", &fastForward, 1);
228 !fastForwardShortCut.empty()) {
229 HelpMarker(
strCat(
"Use '", fastForwardShortCut ,
"' to quickly toggle between these two"));
232 fwdSetting.setBoolean(fastForward != 0);
236 SliderInt(
"Speed (%)", speedManager.getSpeedSetting());
239 SliderInt(
"Fast forward speed (%)", speedManager.getFastForwardSpeedSetting());
242 Checkbox(hotKey,
"Go full speed when loading", globalSettings.getThrottleManager().getFullSpeedLoadingSetting());
245 im::TreeNode(
"MSX devices", ImGuiTreeNodeFlags_DefaultOpen, [&]{
247 HelpMarker(
"These control the speed of the specific components in the MSX machine. "
248 "So the relative speed between components can change. "
249 "And this may lead the emulation problems.");
252 auto showFreqSettings = [&](std::string_view name,
auto* core) {
254 auto& locked = core->getFreqLockedSetting();
255 auto& value = core->getFreqValueSetting();
257 bool unlocked = !locked.getBoolean();
258 if (ImGui::Checkbox(
tmpStrCat(
"unlock custom ", name,
" frequency").c_str(), &unlocked)) {
259 locked.setBoolean(!unlocked);
264 float fval = float(value.getInt()) / 1.0e6f;
265 if (ImGui::InputFloat(
tmpStrCat(
"frequency (MHz)##", name).c_str(), &fval, 0.01f, 1.0f,
"%.2f")) {
266 value.setInt(
int(fval * 1.0e6f));
269 const char* F358 = name ==
"Z80" ?
"3.58 MHz (default)"
271 if (ImGui::Selectable(F358)) {
272 value.setInt(3'579'545);
274 if (ImGui::Selectable(
"5.37 MHz")) {
275 value.setInt(5'369'318);
277 const char* F716 = name ==
"R800" ?
"7.16 MHz (default)"
279 if (ImGui::Selectable(F716)) {
280 value.setInt(7'159'090);
284 HelpMarker(
"Right-click to select commonly used values");
288 showFreqSettings(
"Z80", cpu.
getZ80());
289 showFreqSettings(
"R800", cpu.
getR800());
294 static constexpr std::array kbdModeToolTips = {
295 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."},
296 EnumToolTip{
"KEY",
"Tries to map a key you press to the corresponding MSX key"},
297 EnumToolTip{
"POSITIONAL",
"Tries to map the keyboard key positions to the MSX keyboard key positions"},
301 if (
auto* turbo =
dynamic_cast<IntegerSetting*
>(controller.findSetting(
"renshaturbo"))) {
302 SliderInt(
"Ren Sha Turbo (%)", *turbo);
305 ComboBox(
"Keyboard mapping mode", *mappingModeSetting, kbdModeToolTips);
308 ImGui::MenuItem(
"Configure MSX joysticks...",
nullptr, &showConfigureJoystick);
311 auto getExistingLayouts = [] {
312 std::vector<std::string> names;
314 const auto& path : context.getPaths()) {
315 foreach_file(path, [&](
const std::string& fullName, std::string_view name) {
316 if (name.ends_with(
".ini")) {
317 names.emplace_back(fullName);
324 auto listExistingLayouts = [&](
const std::vector<std::string>& names) {
325 std::optional<std::pair<std::string, std::string>> selectedLayout;
327 for (
const auto& name : names) {
329 if (ImGui::Selectable(displayName.c_str())) {
330 selectedLayout = std::pair{name, displayName};
333 if (ImGui::MenuItem(
"delete")) {
334 confirmText =
strCat(
"Delete layout: ", displayName);
336 openConfirmPopup =
true;
341 return selectedLayout;
344 if (
auto names = getExistingLayouts(); !names.empty()) {
346 if (
auto selectedLayout = listExistingLayouts(names)) {
347 const auto& [name, displayName] = *selectedLayout;
348 saveLayoutName = displayName;
352 ImGui::InputText(
"##save-layout-name", &saveLayoutName);
355 if (ImGui::Button(
"Save as")) {
356 (void)reactor.getDisplay().getWindowPosition();
357 ImGui::CloseCurrentPopup();
359 auto filename = FileOperations::parseCommandFileArgument(
360 saveLayoutName,
"layouts",
"",
".ini");
361 if (FileOperations::exists(filename)) {
362 confirmText = strCat(
"Overwrite layout: ", saveLayoutName);
363 confirmAction = [filename]{
364 ImGui::SaveIniSettingsToDisk(filename.c_str());
366 openConfirmPopup = true;
368 ImGui::SaveIniSettingsToDisk(filename.c_str());
375 auto names = getExistingLayouts();
376 if (
auto selectedLayout = listExistingLayouts(names)) {
377 const auto& [name, displayName] = *selectedLayout;
379 ImGui::CloseCurrentPopup();
383 std::optional<int> newStyle;
384 static constexpr std::array names = {
"Dark",
"Light",
"Classic"};
385 for (
auto i :
xrange(narrow<int>(names.size()))) {
386 if (ImGui::Selectable(names[i], selectedStyle == i)) {
391 selectedStyle = *newStyle;
395 ImGui::MenuItem(
"Select font...",
nullptr, &showFont);
396 ImGui::MenuItem(
"Edit shortcuts...",
nullptr, &showShortcut);
399 ImGui::MenuItem(
"Configure OSD icons...",
nullptr, &
manager.
osdIcons->showConfigureIcons);
401 ImGui::MenuItem(
"Configure messages...",
nullptr, &
manager.
messages->configureWindow.open);
407 std::vector<Setting*> settings;
408 for (
auto*
setting : settingsManager.getAllSettings()) {
411 settings.push_back(checked_cast<Setting*>(
setting));
414 for (
auto*
setting : settings) {
437 ImGui::Checkbox(
"ImGui Demo Window", &showDemoWindow);
439 "This is purely to demonstrate the ImGui capabilities.\n"
440 "There is no connection with any openMSX functionality.");
444 if (showDemoWindow) {
445 ImGui::ShowDemoWindow(&showDemoWindow);
448 const auto confirmTitle =
"Confirm##settings";
449 if (openConfirmPopup) {
450 ImGui::OpenPopup(confirmTitle);
452 im::PopupModal(confirmTitle,
nullptr, ImGuiWindowFlags_AlwaysAutoResize, [&]{
456 if (ImGui::Button(
"Ok")) {
461 close |= ImGui::Button(
"Cancel");
463 ImGui::CloseCurrentPopup();
472[[nodiscard]]
static std::string settingName(
unsigned joystick)
474 return (joystick < 2) ?
strCat(
"msxjoystick", joystick + 1,
"_config")
475 :
strCat(
"joymega", joystick - 1,
"_config");
479[[nodiscard]]
static std::string joystickToGuiString(
unsigned joystick)
481 return (joystick < 2) ?
strCat(
"MSX joystick ", joystick + 1)
482 :
strCat(
"JoyMega controller ", joystick - 1);
485[[nodiscard]]
static std::string toGuiString(
const BooleanInput& input,
const JoystickManager& joystickManager)
488 [](
const BooleanKeyboard& k) {
489 return strCat(
"keyboard key ", SDLKey::toString(k.getKeyCode()));
491 [](
const BooleanMouseButton& m) {
492 return strCat(
"mouse button ", m.getButton());
494 [&](
const BooleanJoystickButton& j) {
495 return strCat(joystickManager.getDisplayName(j.getJoystick()),
" button ", j.getButton());
497 [&](
const BooleanJoystickHat& h) {
498 return strCat(joystickManager.getDisplayName(h.getJoystick()),
" D-pad ", h.getHat(),
' ',
toString(h.getValue()));
500 [&](
const BooleanJoystickAxis& a) {
501 return strCat(joystickManager.getDisplayName(a.getJoystick()),
502 " stick axis ", a.getAxis(),
", ",
503 (a.getDirection() == BooleanJoystickAxis::Direction::POS ?
"positive" :
"negative"),
" direction");
508[[nodiscard]]
static bool insideCircle(
gl::vec2 mouse,
gl::vec2 center,
float radius)
510 auto delta = center - mouse;
511 return gl::sum(delta * delta) <= (radius * radius);
513[[nodiscard]]
static bool between(
float x,
float min,
float max)
515 return (min <= x) && (x <=
max);
524 return between(mouse.x, r.topLeft.x, r.bottomRight.x) &&
525 between(mouse.y, r.topLeft.y, r.bottomRight.y);
529static constexpr auto fractionDPad = 1.0f / 3.0f;
530static constexpr auto thickness = 3.0f;
532static void drawDPad(
gl::vec2 center,
float size, std::span<const uint8_t, 4> hovered,
int hoveredRow)
534 const auto F = fractionDPad;
535 std::array<std::array<ImVec2, 5 + 1>, 4> points = {
536 std::array<ImVec2, 5 + 1>{
544 std::array<ImVec2, 5 + 1>{
552 std::array<ImVec2, 5 + 1>{
560 std::array<ImVec2, 5 + 1>{
570 auto* drawList = ImGui::GetWindowDrawList();
571 auto hoverColor = ImGui::GetColorU32(ImGuiCol_ButtonHovered);
573 auto color =
getColor(imColor::TEXT);
574 for (
auto i :
xrange(4)) {
575 if (hovered[i] || (hoveredRow == i)) {
576 drawList->AddConvexPolyFilled(points[i].data(), 5, hoverColor);
578 drawList->AddPolyline(points[i].data(), 5 + 1, color, 0, thickness);
582static void drawFilledCircle(
gl::vec2 center,
float radius,
bool fill)
584 auto* drawList = ImGui::GetWindowDrawList();
586 auto hoverColor = ImGui::GetColorU32(ImGuiCol_ButtonHovered);
587 drawList->AddCircleFilled(center, radius, hoverColor);
589 auto color =
getColor(imColor::TEXT);
590 drawList->AddCircle(center, radius, color, 0, thickness);
592static void drawFilledRectangle(Rectangle r,
float corner,
bool fill)
594 auto* drawList = ImGui::GetWindowDrawList();
596 auto hoverColor = ImGui::GetColorU32(ImGuiCol_ButtonHovered);
597 drawList->AddRectFilled(r.topLeft, r.bottomRight, hoverColor, corner);
599 auto color =
getColor(imColor::TEXT);
600 drawList->AddRect(r.topLeft, r.bottomRight, color, corner, 0, thickness);
603static void drawLetterA(
gl::vec2 center)
605 auto* drawList = ImGui::GetWindowDrawList();
606 auto tr = [&](
gl::vec2 p) {
return center + p; };
607 const std::array<ImVec2, 3> lines = { tr({-6, 7}), tr({0, -7}), tr({6, 7}) };
608 auto color =
getColor(imColor::TEXT);
609 drawList->AddPolyline(lines.data(), lines.size(), color, 0, thickness);
610 drawList->AddLine(tr({-3, 1}), tr({3, 1}), color, thickness);
612static void drawLetterB(
gl::vec2 center)
614 auto* drawList = ImGui::GetWindowDrawList();
615 auto tr = [&](
gl::vec2 p) {
return center + p; };
616 const std::array<ImVec2, 4> lines = { tr({1, -7}), tr({-4, -7}), tr({-4, 7}), tr({2, 7}) };
617 auto color =
getColor(imColor::TEXT);
618 drawList->AddPolyline(lines.data(), lines.size(), color, 0, thickness);
619 drawList->AddLine(tr({-4, -1}), tr({2, -1}), color, thickness);
620 drawList->AddBezierQuadratic(tr({1, -7}), tr({4, -7}), tr({4, -4}), color, thickness);
621 drawList->AddBezierQuadratic(tr({4, -4}), tr({4, -1}), tr({1, -1}), color, thickness);
622 drawList->AddBezierQuadratic(tr({2, -1}), tr({6, -1}), tr({6, 3}), color, thickness);
623 drawList->AddBezierQuadratic(tr({6, 3}), tr({6, 7}), tr({2, 7}), color, thickness);
625static void drawLetterC(
gl::vec2 center)
627 auto* drawList = ImGui::GetWindowDrawList();
628 auto tr = [&](
gl::vec2 p) {
return center + p; };
629 auto color =
getColor(imColor::TEXT);
630 drawList->AddBezierCubic(tr({5, -5}), tr({-8, -16}), tr({-8, 16}), tr({5, 5}), color, thickness);
632static void drawLetterX(
gl::vec2 center)
634 auto* drawList = ImGui::GetWindowDrawList();
635 auto tr = [&](
gl::vec2 p) {
return center + p; };
636 auto color =
getColor(imColor::TEXT);
637 drawList->AddLine(tr({-4, -6}), tr({4, 6}), color, thickness);
638 drawList->AddLine(tr({-4, 6}), tr({4, -6}), color, thickness);
640static void drawLetterY(
gl::vec2 center)
642 auto* drawList = ImGui::GetWindowDrawList();
643 auto tr = [&](
gl::vec2 p) {
return center + p; };
644 auto color =
getColor(imColor::TEXT);
645 drawList->AddLine(tr({-4, -6}), tr({0, 0}), color, thickness);
646 drawList->AddLine(tr({-4, 6}), tr({4, -6}), color, thickness);
648static void drawLetterZ(
gl::vec2 center)
650 auto* drawList = ImGui::GetWindowDrawList();
651 auto tr = [&](
gl::vec2 p) {
return center + p; };
652 const std::array<ImVec2, 4> linesZ2 = { tr({-4, -6}), tr({4, -6}), tr({-4, 6}), tr({4, 6}) };
653 auto color =
getColor(imColor::TEXT);
654 drawList->AddPolyline(linesZ2.data(), 4, color, 0, thickness);
657namespace msxjoystick {
661static constexpr std::array<zstring_view, NUM_BUTTONS> buttonNames = {
662 "Up",
"Down",
"Left",
"Right",
"A",
"B"
664static constexpr std::array<zstring_view, NUM_BUTTONS> keyNames = {
665 "UP",
"DOWN",
"LEFT",
"RIGHT",
"A",
"B"
669static constexpr auto boundingBox =
gl::vec2{300.0f, 100.0f};
670static constexpr auto radius = 20.0f;
671static constexpr auto corner = 10.0f;
672static constexpr auto centerA =
gl::vec2{200.0f, 50.0f};
673static constexpr auto centerB =
gl::vec2{260.0f, 50.0f};
674static constexpr auto centerDPad =
gl::vec2{50.0f, 50.0f};
675static constexpr auto sizeDPad = 30.0f;
677[[nodiscard]]
static std::vector<uint8_t> buttonsHovered(
gl::vec2 mouse)
679 std::vector<uint8_t> result(NUM_BUTTONS);
680 auto mouseDPad = (mouse - centerDPad) * (1.0f / sizeDPad);
681 if (insideRectangle(mouseDPad, Rectangle{{-1, -1}, {1, 1}}) &&
682 (between(mouseDPad.x, -fractionDPad, fractionDPad) ||
683 between(mouseDPad.y, -fractionDPad, fractionDPad))) {
684 bool t1 = mouseDPad.x < mouseDPad.y;
685 bool t2 = mouseDPad.x < -mouseDPad.y;
686 result[
UP] = !t1 && t2;
687 result[
DOWN] = t1 && !t2;
688 result[
LEFT] = t1 && t2;
689 result[
RIGHT] = !t1 && !t2;
691 result[
TRIG_A] = insideCircle(mouse, centerA, radius);
692 result[
TRIG_B] = insideCircle(mouse, centerB, radius);
696static void draw(
gl::vec2 scrnPos, std::span<uint8_t> hovered,
int hoveredRow)
698 auto* drawList = ImGui::GetWindowDrawList();
700 auto color =
getColor(imColor::TEXT);
701 drawList->AddRect(scrnPos, scrnPos + boundingBox, color, corner, 0, thickness);
703 drawDPad(scrnPos + centerDPad, sizeDPad, subspan<4>(hovered), hoveredRow);
705 auto scrnCenterA = scrnPos + centerA;
706 drawFilledCircle(scrnCenterA, radius, hovered[TRIG_A] || (hoveredRow == TRIG_A));
707 drawLetterA(scrnCenterA);
709 auto scrnCenterB = scrnPos + centerB;
710 drawFilledCircle(scrnCenterB, radius, hovered[TRIG_B] || (hoveredRow == TRIG_B));
711 drawLetterB(scrnCenterB);
718enum {UP, DOWN, LEFT, RIGHT,
722 NUM_BUTTONS, NUM_DIRECTIONS = TRIG_A};
724static constexpr std::array<zstring_view, NUM_BUTTONS> buttonNames = {
725 "Up",
"Down",
"Left",
"Right",
730static constexpr std::array<zstring_view, NUM_BUTTONS> keyNames = {
731 "UP",
"DOWN",
"LEFT",
"RIGHT",
738static constexpr auto thickness = 3.0f;
739static constexpr auto boundingBox =
gl::vec2{300.0f, 158.0f};
740static constexpr auto centerA =
gl::vec2{205.0f, 109.9f};
741static constexpr auto centerB =
gl::vec2{235.9f, 93.5f};
742static constexpr auto centerC =
gl::vec2{269.7f, 83.9f};
743static constexpr auto centerX =
gl::vec2{194.8f, 75.2f};
744static constexpr auto centerY =
gl::vec2{223.0f, 61.3f};
745static constexpr auto centerZ =
gl::vec2{252.2f, 52.9f};
746static constexpr auto selectBox = Rectangle{
gl::vec2{130.0f, 60.0f},
gl::vec2{160.0f, 70.0f}};
747static constexpr auto startBox = Rectangle{
gl::vec2{130.0f, 86.0f},
gl::vec2{160.0f, 96.0f}};
748static constexpr auto radiusABC = 16.2f;
749static constexpr auto radiusXYZ = 12.2f;
750static constexpr auto centerDPad =
gl::vec2{65.6f, 82.7f};
751static constexpr auto sizeDPad = 34.0f;
752static constexpr auto fractionDPad = 1.0f / 3.0f;
754[[nodiscard]]
static std::vector<uint8_t> buttonsHovered(
gl::vec2 mouse)
756 std::vector<uint8_t> result(NUM_BUTTONS);
757 auto mouseDPad = (mouse - centerDPad) * (1.0f / sizeDPad);
758 if (insideRectangle(mouseDPad, Rectangle{{-1, -1}, {1, 1}}) &&
759 (between(mouseDPad.x, -fractionDPad, fractionDPad) ||
760 between(mouseDPad.y, -fractionDPad, fractionDPad))) {
761 bool t1 = mouseDPad.x < mouseDPad.y;
762 bool t2 = mouseDPad.x < -mouseDPad.y;
763 result[
UP] = !t1 && t2;
764 result[
DOWN] = t1 && !t2;
765 result[
LEFT] = t1 && t2;
766 result[
RIGHT] = !t1 && !t2;
768 result[
TRIG_A] = insideCircle(mouse, centerA, radiusABC);
769 result[
TRIG_B] = insideCircle(mouse, centerB, radiusABC);
770 result[
TRIG_C] = insideCircle(mouse, centerC, radiusABC);
771 result[
TRIG_X] = insideCircle(mouse, centerX, radiusXYZ);
772 result[
TRIG_Y] = insideCircle(mouse, centerY, radiusXYZ);
773 result[
TRIG_Z] = insideCircle(mouse, centerZ, radiusXYZ);
774 result[
TRIG_START] = insideRectangle(mouse, startBox);
775 result[
TRIG_SELECT] = insideRectangle(mouse, selectBox);
779static void draw(
gl::vec2 scrnPos, std::span<uint8_t> hovered,
int hoveredRow)
781 auto* drawList = ImGui::GetWindowDrawList();
782 auto tr = [&](
gl::vec2 p) {
return scrnPos + p; };
783 auto color =
getColor(imColor::TEXT);
785 auto drawBezierCurve = [&](std::span<const gl::vec2> points,
float thick = 1.0f) {
786 assert((points.size() % 2) == 0);
787 for (
size_t i = 0; i < points.size() - 2; i += 2) {
788 auto ap = points[i + 0];
789 auto ad = points[i + 1];
790 auto bp = points[i + 2];
791 auto bd = points[i + 3];
792 drawList->AddBezierCubic(tr(ap), tr(ap + ad), tr(bp - bd), tr(bp), color, thick);
796 std::array outLine = {
811 drawBezierCurve(outLine, thickness);
813 drawDPad(tr(centerDPad), sizeDPad, subspan<4>(hovered), hoveredRow);
814 drawList->AddCircle(tr(centerDPad), 43.0f, color);
815 std::array dPadCurve = {
820 drawBezierCurve(dPadCurve);
822 drawFilledCircle(tr(centerA), radiusABC, hovered[TRIG_A] || (hoveredRow == TRIG_A));
823 drawLetterA(tr(centerA));
824 drawFilledCircle(tr(centerB), radiusABC, hovered[TRIG_B] || (hoveredRow == TRIG_B));
825 drawLetterB(tr(centerB));
826 drawFilledCircle(tr(centerC), radiusABC, hovered[TRIG_C] || (hoveredRow == TRIG_C));
827 drawLetterC(tr(centerC));
828 drawFilledCircle(tr(centerX), radiusXYZ, hovered[TRIG_X] || (hoveredRow == TRIG_X));
829 drawLetterX(tr(centerX));
830 drawFilledCircle(tr(centerY), radiusXYZ, hovered[TRIG_Y] || (hoveredRow == TRIG_Y));
831 drawLetterY(tr(centerY));
832 drawFilledCircle(tr(centerZ), radiusXYZ, hovered[TRIG_Z] || (hoveredRow == TRIG_Z));
833 drawLetterZ(tr(centerZ));
834 std::array buttonCurve = {
839 drawBezierCurve(buttonCurve);
841 auto corner = (selectBox.bottomRight.y - selectBox.topLeft.y) * 0.5f;
842 auto trR = [&](Rectangle r) {
return Rectangle{tr(r.topLeft), tr(r.bottomRight)}; };
843 drawFilledRectangle(trR(selectBox), corner, hovered[TRIG_SELECT] || (hoveredRow == TRIG_SELECT));
844 drawList->AddText(ImGui::GetFont(), ImGui::GetFontSize(), tr({123.0f, 46.0f}), color,
"Select");
845 drawFilledRectangle(trR(startBox), corner, hovered[TRIG_START] || (hoveredRow == TRIG_START));
846 drawList->AddText(ImGui::GetFont(), ImGui::GetFontSize(), tr({128.0f, 97.0f}), color,
"Start");
851void ImGuiSettings::paintJoystick(MSXMotherBoard& motherBoard)
853 ImGui::SetNextWindowSize(
gl::vec2{316, 323}, ImGuiCond_FirstUseEver);
854 im::Window(
"Configure MSX joysticks", &showConfigureJoystick, [&]{
855 ImGui::SetNextItemWidth(13.0f * ImGui::GetFontSize());
856 im::Combo(
"Select joystick", joystickToGuiString(joystick).c_str(), [&]{
857 for (
const auto& j :
xrange(4)) {
858 if (ImGui::Selectable(joystickToGuiString(j).c_str())) {
864 const auto& joystickManager = manager.getReactor().getInputEventGenerator().getJoystickManager();
865 const auto& controller = motherBoard.getMSXCommandController();
866 auto*
setting =
dynamic_cast<StringSetting*
>(controller.findSetting(settingName(joystick)));
868 auto& interp =
setting->getInterpreter();
869 TclObject bindings =
setting->getValue();
871 gl::vec2 scrnPos = ImGui::GetCursorScreenPos();
875 bool msxOrMega = joystick < 2;
876 auto hovered = msxOrMega ? msxjoystick::buttonsHovered(mouse)
877 : joymega ::buttonsHovered(mouse);
878 const auto numButtons = hovered.size();
879 using SP = std::span<const zstring_view>;
880 auto keyNames = msxOrMega ?
SP{msxjoystick::keyNames}
881 :
SP{joymega ::keyNames};
882 auto buttonNames = msxOrMega ?
SP{msxjoystick::buttonNames}
883 :
SP{joymega ::buttonNames};
886 std::optional<int> addAction;
887 std::optional<int> removeAction;
888 if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
889 for (
auto i :
xrange(numButtons)) {
890 if (hovered[i]) addAction = narrow<int>(i);
894 ImGui::Dummy(msxOrMega ? msxjoystick::boundingBox : joymega::boundingBox);
898 const auto& style = ImGui::GetStyle();
899 auto textHeight = ImGui::GetTextLineHeight();
900 float rowHeight = 2.0f * style.FramePadding.y + textHeight;
901 float bottomHeight = style.ItemSpacing.y + 2.0f * style.FramePadding.y + textHeight;
902 im::Table(
"##joystick-table", 2, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollX, {0.0f, -bottomHeight}, [&]{
904 TclObject key(keyNames[i]);
905 TclObject bindingList = bindings.getDictValue(interp, key);
906 if (ImGui::TableNextColumn()) {
907 auto pos = ImGui::GetCursorPos();
908 ImGui::Selectable(
"##row", hovered[i], ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap, ImVec2(0, rowHeight));
909 if (ImGui::IsItemHovered()) {
913 ImGui::SetCursorPos(pos);
914 ImGui::AlignTextToFramePadding();
917 if (ImGui::TableNextColumn()) {
918 if (ImGui::Button(
"Add")) {
922 auto numBindings = bindingList.size();
924 if (ImGui::Button(
"Remove")) {
925 if (numBindings == 1) {
926 bindings.setDictValue(interp, key, TclObject{});
934 if (numBindings == 0) {
935 ImGui::TextDisabled(
"no bindings");
937 size_t lastBindingIndex = numBindings - 1;
938 size_t bindingIndex = 0;
939 for (
auto binding: bindingList) {
942 if (bindingIndex < lastBindingIndex) {
953 msxOrMega ? msxjoystick::draw(scrnPos, hovered, hoveredRow)
954 : joymega ::draw(scrnPos, hovered, hoveredRow);
956 if (ImGui::Button(
"Default bindings...")) {
957 ImGui::OpenPopup(
"bindings");
960 auto addOrSet = [&](
auto getBindings) {
961 if (ImGui::MenuItem(
"Add to current bindings")) {
963 auto newBindings = getBindings();
964 for (
auto k :
xrange(int(numButtons))) {
965 TclObject key(keyNames[k]);
966 TclObject dstList = bindings.getDictValue(interp, key);
967 TclObject srcList = newBindings.getDictValue(interp, key);
969 for (
auto b : srcList) {
971 dstList.addListElement(b);
974 bindings.setDictValue(interp, key, dstList);
978 if (ImGui::MenuItem(
"Replace current bindings")) {
979 setting->setValue(getBindings());
984 return TclObject(TclObject::MakeDictTag{},
993 for (
auto joyId : joystickManager.getConnectedJoysticks()) {
994 im::Menu(joystickManager.getDisplayName(joyId).c_str(), [&]{
997 ? MSXJoystick::getDefaultConfig(joyId, joystickManager)
998 : JoyMega::getDefaultConfig(joyId, joystickManager);
1005 static constexpr auto addTitle =
"Waiting for input";
1007 popupForKey = *addAction;
1008 popupTimeout = 5.0f;
1010 ImGui::OpenPopup(addTitle);
1012 im::PopupModal(addTitle,
nullptr, ImGuiWindowFlags_NoSavedSettings, [&]{
1014 ImGui::CloseCurrentPopup();
1015 popupForKey = unsigned(-1);
1018 if (popupForKey >= numButtons) {
1023 ImGui::Text(
"Enter event for joystick button '%s'", buttonNames[popupForKey].c_str());
1024 ImGui::Text(
"Or press ESC to cancel. Timeout in %d seconds.",
int(popupTimeout));
1026 popupTimeout -= ImGui::GetIO().DeltaTime;
1027 if (popupTimeout <= 0.0f) {
1034 popupForKey = *removeAction;
1035 ImGui::OpenPopup(
"remove");
1039 ImGui::CloseCurrentPopup();
1040 popupForKey = unsigned(-1);
1042 if (popupForKey >= numButtons) {
1046 TclObject key(keyNames[popupForKey]);
1047 TclObject bindingList = bindings.getDictValue(interp, key);
1050 unsigned counter = 0;
1051 for (
const auto& b : bindingList) {
1052 if (ImGui::Selectable(b.c_str())) {
1058 if (remove !=
unsigned(-1)) {
1059 bindingList.removeListIndex(interp, remove);
1060 bindings.setDictValue(interp, key, bindingList);
1065 if (ImGui::Selectable(
"all bindings")) {
1066 bindings.setDictValue(interp, key, TclObject{});
1074void ImGuiSettings::paintFont()
1077 auto selectFilename = [&](FilenameSetting&
setting,
float width) {
1078 auto display = [](std::string_view name) {
1079 if (name.ends_with(
".gz" )) name.remove_suffix(3);
1080 if (name.ends_with(
".ttf")) name.remove_suffix(4);
1081 return std::string(name);
1083 auto current =
setting.getString();
1084 ImGui::SetNextItemWidth(width);
1086 for (
const auto& font : getAvailableFonts()) {
1087 if (ImGui::Selectable(display(font).c_str(), current == font)) {
1093 auto selectSize = [](IntegerSetting&
setting) {
1094 auto display = [](
int s) {
return strCat(s); };
1095 auto current =
setting.getInt();
1096 ImGui::SetNextItemWidth(4.0f * ImGui::GetFontSize());
1098 for (
int size : {9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 22, 24, 26, 28, 30, 32}) {
1099 if (ImGui::Selectable(display(size).c_str(), current == size)) {
1107 auto width = 12.0f * ImGui::GetFontSize();
1109 ImGui::AlignTextToFramePadding();
1111 ImGui::SameLine(pos);
1112 selectFilename(manager.fontPropFilename, width);
1114 selectSize(manager.fontPropSize);
1115 HelpMarker(
"You can install more fonts by copying .ttf file(s) to your \"<openmsx>/share/skins\" directory.");
1117 ImGui::AlignTextToFramePadding();
1119 ImGui::SameLine(pos);
1121 selectFilename(manager.fontMonoFilename, width);
1124 selectSize(manager.fontMonoSize);
1125 HelpMarker(
"Some GUI elements (e.g. the console) require a monospaced font.");
1129[[nodiscard]]
static std::string formatShortcutWithAnnotations(
const Shortcuts::Shortcut& shortcut)
1133 if (shortcut.type == Shortcuts::Type::GLOBAL) result +=
", global";
1137[[nodiscard]]
static gl::vec2 buttonSize(std::string_view text,
float defaultSize_)
1139 const auto& style = ImGui::GetStyle();
1141 auto defaultSize = ImGui::GetFontSize() * defaultSize_;
1142 return {std::max(textSize, defaultSize), 0.0f};
1145void ImGuiSettings::paintEditShortcut()
1147 using enum Shortcuts::Type;
1149 bool editShortcutWindow = editShortcutId != Shortcuts::ID::INVALID;
1150 if (!editShortcutWindow)
return;
1152 im::Window(
"Edit shortcut", &editShortcutWindow, ImGuiWindowFlags_AlwaysAutoResize, [&]{
1153 auto& shortcuts = manager.getShortcuts();
1154 auto shortcut = shortcuts.getShortcut(editShortcutId);
1157 ImGui::TableSetupColumn(
"", ImGuiTableColumnFlags_WidthFixed);
1158 ImGui::TableSetupColumn(
"", ImGuiTableColumnFlags_WidthStretch);
1160 if (ImGui::TableNextColumn()) {
1161 ImGui::AlignTextToFramePadding();
1164 static constexpr auto waitKeyTitle =
"Waiting for key";
1165 if (ImGui::TableNextColumn()) {
1167 if (ImGui::Button(text.c_str(), buttonSize(text, 4.0f))) {
1168 popupTimeout = 10.0f;
1170 ImGui::OpenPopup(waitKeyTitle);
1174 im::PopupModal(waitKeyTitle, &isOpen, ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize, [&]{
1175 ImGui::Text(
"Enter key combination for shortcut '%s'",
1176 Shortcuts::getShortcutDescription(editShortcutId).c_str());
1177 ImGui::Text(
"Timeout in %d seconds.",
int(popupTimeout));
1179 popupTimeout -= ImGui::GetIO().DeltaTime;
1180 if (!isOpen || popupTimeout <= 0.0f) {
1181 ImGui::CloseCurrentPopup();
1183 if (
auto keyChord = getCurrentlyPressedKeyChord(); keyChord != ImGuiKey_None) {
1184 shortcut.keyChord = keyChord;
1185 shortcuts.setShortcut(editShortcutId, shortcut);
1186 editShortcutWindow =
false;
1187 ImGui::CloseCurrentPopup();
1191 if (shortcut.type ==
one_of(LOCAL, GLOBAL)) {
1192 if (ImGui::TableNextColumn()) {
1193 ImGui::AlignTextToFramePadding();
1196 if (ImGui::TableNextColumn()) {
1197 bool global = shortcut.type == GLOBAL;
1198 if (ImGui::Checkbox(
"##global", &global)) {
1199 shortcut.type = global ? GLOBAL : LOCAL;
1200 shortcuts.setShortcut(editShortcutId, shortcut);
1203 "Global shortcuts react when any GUI window has focus.\n"
1204 "Local shortcuts only react when the specific GUI window has focus.\n"sv);
1209 const auto& defaultShortcut = Shortcuts::getDefaultShortcut(editShortcutId);
1211 if (ImGui::Button(
"Restore default")) {
1212 shortcuts.setShortcut(editShortcutId, defaultShortcut);
1213 editShortcutWindow =
false;
1215 simpleToolTip([&]{
return formatShortcutWithAnnotations(defaultShortcut); });
1220 if (ImGui::Button(
"Set None")) {
1221 shortcuts.setShortcut(editShortcutId, Shortcuts::Shortcut{});
1222 editShortcutWindow =
false;
1227 if (!editShortcutWindow) editShortcutId = Shortcuts::ID::INVALID;
1230void ImGuiSettings::paintShortcut()
1232 im::Window(
"Edit shortcuts", &showShortcut, [&]{
1233 int flags = ImGuiTableFlags_Resizable
1234 | ImGuiTableFlags_RowBg
1235 | ImGuiTableFlags_NoBordersInBodyUntilResize
1236 | ImGuiTableFlags_SizingStretchProp;
1237 im::Table(
"table", 2, flags, {-FLT_MIN, 0.0f}, [&]{
1238 ImGui::TableSetupColumn(
"description");
1239 ImGui::TableSetupColumn(
"key");
1241 const auto& shortcuts = manager.getShortcuts();
1243 auto id =
static_cast<Shortcuts::ID
>(i);
1244 auto shortcut = shortcuts.getShortcut(
id);
1246 if (ImGui::TableNextColumn()) {
1247 ImGui::AlignTextToFramePadding();
1250 if (ImGui::TableNextColumn()) {
1251 auto text = formatShortcutWithAnnotations(shortcut);
1252 if (ImGui::Button(text.c_str(), buttonSize(text, 9.0f))) {
1253 editShortcutId =
id;
1260 paintEditShortcut();
1265 if (selectedStyle < 0) {
1270 if (motherBoard && showConfigureJoystick) paintJoystick(*motherBoard);
1271 if (showFont) paintFont();
1272 if (showShortcut) paintShortcut();
1275std::span<const std::string> ImGuiSettings::getAvailableFonts()
1277 if (availableFonts.empty()) {
1279 const auto& path : context.getPaths()) {
1280 foreach_file(FileOperations::join(path,
"skins"), [&](
const std::string& , std::string_view name) {
1281 if (name.ends_with(
".ttf.gz") || name.ends_with(
".ttf")) {
1282 availableFonts.emplace_back(name);
1290 return availableFonts;
1293int ImGuiSettings::signalEvent(
const Event& event)
1295 bool msxOrMega = joystick < 2;
1296 using SP = std::span<const zstring_view>;
1297 auto keyNames = msxOrMega ?
SP{msxjoystick::keyNames}
1298 :
SP{joymega ::keyNames};
1299 if (
const auto numButtons = keyNames.size(); popupForKey >= numButtons) {
1304 static constexpr auto block = EventDistributor::Priority(EventDistributor::IMGUI + 1);
1306 bool escape =
false;
1307 if (
const auto* keyDown = get_event_if<KeyDownEvent>(event)) {
1308 escape = keyDown->getKeyCode() == SDLK_ESCAPE;
1311 auto getJoyDeadZone = [&](JoystickId joyId) {
1312 const auto& joyMan = manager.getReactor().getInputEventGenerator().getJoystickManager();
1313 const auto*
setting = joyMan.getJoyDeadZoneSetting(joyId);
1317 if (!b)
return block;
1320 auto* motherBoard = manager.getReactor().getMotherBoard();
1321 if (!motherBoard)
return block;
1322 const auto& controller = motherBoard->getMSXCommandController();
1323 auto*
setting =
dynamic_cast<StringSetting*
>(controller.findSetting(settingName(joystick)));
1325 auto& interp =
setting->getInterpreter();
1327 TclObject bindings =
setting->getValue();
1328 TclObject key(keyNames[popupForKey]);
1329 TclObject bindingList = bindings.getDictValue(interp, key);
1332 bindingList.addListElement(bs);
1333 bindings.setDictValue(interp, key, bindingList);
1338 popupForKey = unsigned(-1);
1342void ImGuiSettings::initListener()
1344 if (listening)
return;
1347 auto& distributor = manager.getReactor().getEventDistributor();
1352 distributor.registerEventListener(type, *
this);
1356void ImGuiSettings::deinitListener()
1358 if (!listening)
return;
1361 auto& distributor = manager.getReactor().getEventDistributor();
1365 distributor.unregisterEventListener(type, *
this);
const char * c_str() const
std::string_view getBaseName() const
A Setting with a floating point value.
std::unique_ptr< ImGuiSoundChip > soundChip
std::unique_ptr< ImGuiMessages > messages
std::unique_ptr< ImGuiOsdIcons > osdIcons
void save(ImGuiTextBuffer &buf) override
void showMenu(MSXMotherBoard *motherBoard) override
void loadLine(std::string_view name, zstring_view value) override
A Setting with an integer value.
VideoSourceSetting & getVideoSource()
MSXCommandController & getMSXCommandController()
GlobalSettings & getGlobalSettings()
ScaleAlgorithm
Scaler algorithm.
static const bool RELEASE
Like std::string_view, but with the extra guarantee that it refers to a zero-terminated string.
auto CalcTextSize(std::string_view str)
void TextUnformatted(const std::string &str)
constexpr T sum(const vecN< N, T > &x)
constexpr vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
void Table(const char *str_id, int column, ImGuiTableFlags flags, const ImVec2 &outer_size, float inner_width, std::invocable<> auto next)
void Window(const char *name, bool *p_open, ImGuiWindowFlags flags, std::invocable<> auto next)
void PopupContextItem(const char *str_id, ImGuiPopupFlags popup_flags, std::invocable<> auto next)
bool TreeNode(const char *label, ImGuiTreeNodeFlags flags, std::invocable<> auto next)
void Combo(const char *label, const char *preview_value, ImGuiComboFlags flags, std::invocable<> auto next)
void ListBox(const char *label, const ImVec2 &size, std::invocable<> auto next)
void PopupModal(const char *name, bool *p_open, ImGuiWindowFlags flags, std::invocable<> auto next)
bool Menu(const char *label, bool enabled, std::invocable<> auto next)
void Disabled(bool b, std::invocable<> auto next)
void Font(ImFont *font, std::invocable<> auto next)
void Indent(float indent_w, std::invocable<> auto next)
void Popup(const char *str_id, ImGuiWindowFlags flags, std::invocable<> auto next)
void ID_for_range(int count, std::invocable< int > auto next)
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:
const FileContext & systemFileContext()
std::optional< BooleanInput > captureBooleanInput(const Event &event, function_ref< int(JoystickId)> getJoyDeadZone)
bool Checkbox(const HotKey &hotKey, BooleanSetting &setting)
bool SliderFloat(FloatSetting &setting, const char *format, ImGuiSliderFlags flags)
void centerNextWindowOverCurrent()
bool SliderInt(IntegerSetting &setting, ImGuiSliderFlags flags)
void ComboBox(const char *label, Setting &setting, function_ref< std::string(const std::string &)> displayValue, EnumToolTips toolTips)
bool loadOnePersistent(std::string_view name, zstring_view value, C &c, const std::tuple< Elements... > &tup)
void simpleToolTip(std::string_view desc)
void savePersistent(ImGuiTextBuffer &buf, C &c, const std::tuple< Elements... > &tup)
std::string getShortCutForCommand(const HotKey &hotkey, std::string_view command)
void HelpMarker(std::string_view desc)
std::optional< BooleanInput > parseBooleanInput(std::string_view text)
bool InputText(Setting &setting)
ImU32 getColor(imColor col)
void setColors(int style)
std::string getKeyChordName(ImGuiKeyChord keyChord)
std::string toString(const BooleanInput &input)
bool foreach_file(std::string path, FileAction fileAction)
TclObject makeTclList(Args &&... args)
FileContext userDataFileContext(string_view subDir)
auto unique(ForwardRange &&range)
auto remove(ForwardRange &&range, const T &value)
auto find(InputRange &&range, const T &value)
constexpr void replace(ForwardRange &&range, const T &old_value, const T &new_value)
constexpr void sort(RandomAccessRange &&range)
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.
TemporaryString tmpStrCat(Ts &&... ts)
constexpr auto xrange(T e)
constexpr auto end(const zstring_view &x)