| 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 |
#include "VectorPath.hh" |
| 10 |
#include "VectorPathDsl.hh" |
| 11 |
|
| 12 |
#include "AnalogInput.hh" |
| 13 |
#include "BooleanInput.hh" |
| 14 |
#include "BooleanSetting.hh" |
| 15 |
#include "CPUCore.hh" |
| 16 |
#include "Display.hh" |
| 17 |
#include "EventDistributor.hh" |
| 18 |
#include "FileContext.hh" |
| 19 |
#include "FilenameSetting.hh" |
| 20 |
#include "FloatSetting.hh" |
| 21 |
#include "GlobalCommandController.hh" |
| 22 |
#include "GlobalSettings.hh" |
| 23 |
#include "InputEventFactory.hh" |
| 24 |
#include "InputEventGenerator.hh" |
| 25 |
#include "IntegerSetting.hh" |
| 26 |
#include "JoyMega.hh" |
| 27 |
#include "JoyHandle.hh" |
| 28 |
#include "KeyCodeSetting.hh" |
| 29 |
#include "KeyboardSettings.hh" |
| 30 |
#include "MSXCPU.hh" |
| 31 |
#include "MSXCommandController.hh" |
| 32 |
#include "MSXJoystick.hh" |
| 33 |
#include "MSXMotherBoard.hh" |
| 34 |
#include "Mixer.hh" |
| 35 |
#include "Plotter.hh" |
| 36 |
#include "PluggingController.hh" |
| 37 |
#include "ProxySetting.hh" |
| 38 |
#include "R800.hh" |
| 39 |
#include "Reactor.hh" |
| 40 |
#include "ReadOnlySetting.hh" |
| 41 |
#include "SettingsManager.hh" |
| 42 |
#include "StringSetting.hh" |
| 43 |
#include "Version.hh" |
| 44 |
#include "VideoSourceSetting.hh" |
| 45 |
#include "Z80.hh" |
| 46 |
|
| 47 |
#include "StringOp.hh" |
| 48 |
#include "checked_cast.hh" |
| 49 |
#include "foreach_file.hh" |
| 50 |
#include "narrow.hh" |
| 51 |
#include "strCat.hh" |
| 52 |
#include "zstring_view.hh" |
| 53 |
|
| 54 |
#include <imgui.h> |
| 55 |
#include <imgui_impl_opengl3.h> |
| 56 |
#include <imgui_stdlib.h> |
| 57 |
|
| 58 |
#include <SDL.h> |
| 59 |
|
| 60 |
#include <algorithm> |
| 61 |
#include <functional> |
| 62 |
#include <optional> |
| 63 |
#include <utility> |
| 64 |
|
| 65 |
using namespace std::literals; |
| 66 |
|
| 67 |
namespace openmsx { |
| 68 |
|
| 69 |
ImGuiSettings::ImGuiSettings(ImGuiManager& manager_) |
| 70 |
: ImGuiPart(manager_) |
| 71 |
, saveLayout("layout", ".ini", "layouts") |
| 72 |
, loadLayout("layout", ".ini", "layouts") |
| 73 |
, confirmOverwrite("Confirm##overwrite-layout") |
| 74 |
{ |
| 75 |
saveLayout.drawAction = [&]{ |
| 76 |
saveLayout.drawTable(); |
| 77 |
ImGui::TextUnformatted("Enter name:"sv); |
| 78 |
ImGui::InputText("##save-layout-name", &saveLayoutName); |
| 79 |
ImGui::SameLine(); |
| 80 |
im::Disabled(saveLayoutName.empty(), [&]{ |
| 81 |
if (ImGui::Button("Save as")) { |
| 82 |
(void)manager.getReactor().getDisplay().getWindowPosition(); |
| 83 |
|
| 84 |
auto filename = FileOperations::parseCommandFileArgument( |
| 85 |
saveLayoutName, "layouts", "", ".ini"); |
| 86 |
auto action = [filename]{ |
| 87 |
ImGui::SaveIniSettingsToDisk(filename.c_str()); |
| 88 |
ImGui::CloseCurrentPopup(); |
| 89 |
}; |
| 90 |
if (FileOperations::exists(filename)) { |
| 91 |
confirmOverwrite.open( |
| 92 |
strCat("Overwrite layout: ", saveLayoutName), |
| 93 |
action); |
| 94 |
} else { |
| 95 |
action(); |
| 96 |
} |
| 97 |
} |
| 98 |
}); |
| 99 |
confirmOverwrite.execute(); |
| 100 |
}; |
| 101 |
saveLayout.singleClickAction = [&](const FileListWidget::Entry& entry) { |
| 102 |
saveLayoutName = entry.getDefaultDisplayName(); |
| 103 |
}; |
| 104 |
|
| 105 |
loadLayout.singleClickAction = [&](const FileListWidget::Entry& entry) { |
| 106 |
manager.loadIniFile = entry.fullName; |
| 107 |
ImGui::CloseCurrentPopup(); |
| 108 |
}; |
| 109 |
} |
| 110 |
|
| 111 |
ImGuiSettings::~ImGuiSettings() |
| 112 |
{ |
| 113 |
deinitListener(); |
| 114 |
} |
| 115 |
|
| 116 |
void ImGuiSettings::save(ImGuiTextBuffer& buf) |
| 117 |
{ |
| 118 |
savePersistent(buf, *this, persistentElements); |
| 119 |
} |
| 120 |
|
| 121 |
void ImGuiSettings::loadLine(std::string_view name, zstring_view value) |
| 122 |
{ |
| 123 |
loadOnePersistent(name, value, *this, persistentElements); |
| 124 |
} |
| 125 |
|
| 126 |
void ImGuiSettings::loadEnd() |
| 127 |
{ |
| 128 |
setStyle(); |
| 129 |
} |
| 130 |
|
| 131 |
void ImGuiSettings::setStyle() const |
| 132 |
{ |
| 133 |
switch (selectedStyle) { |
| 134 |
case 0: ImGui::StyleColorsDark(); break; |
| 135 |
case 1: ImGui::StyleColorsLight(); break; |
| 136 |
case 2: ImGui::StyleColorsClassic(); break; |
| 137 |
case 3: ImGui::StyleColorsClassic(); break; |
| 138 |
} |
| 139 |
setColors(selectedStyle); |
| 140 |
} |
| 141 |
|
| 142 |
|
| 143 |
|
| 144 |
|
| 145 |
|
| 146 |
[[nodiscard]] static ImGuiKeyChord getCurrentlyPressedKeyChord() |
| 147 |
{ |
| 148 |
static constexpr auto mods = std::array{ |
| 149 |
ImGuiKey_LeftCtrl, ImGuiKey_LeftShift, ImGuiKey_LeftAlt, ImGuiKey_LeftSuper, |
| 150 |
ImGuiKey_RightCtrl, ImGuiKey_RightShift, ImGuiKey_RightAlt, ImGuiKey_RightSuper, |
| 151 |
ImGuiKey_ReservedForModCtrl, ImGuiKey_ReservedForModShift, ImGuiKey_ReservedForModAlt, |
| 152 |
ImGuiKey_ReservedForModSuper, ImGuiKey_MouseLeft, ImGuiKey_MouseRight, ImGuiKey_MouseMiddle, |
| 153 |
ImGuiKey_MouseX1, ImGuiKey_MouseX2, ImGuiKey_MouseWheelX, ImGuiKey_MouseWheelY, |
| 154 |
}; |
| 155 |
for (int key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; ++key) { |
| 156 |
|
| 157 |
if (contains(mods, key)) continue; |
| 158 |
if (ImGui::IsKeyPressed(static_cast<ImGuiKey>(key))) { |
| 159 |
const ImGuiIO& io = ImGui::GetIO(); |
| 160 |
return key |
| 161 |
| (io.KeyCtrl ? ImGuiMod_Ctrl : 0) |
| 162 |
| (io.KeyShift ? ImGuiMod_Shift : 0) |
| 163 |
| (io.KeyAlt ? ImGuiMod_Alt : 0) |
| 164 |
| (io.KeySuper ? ImGuiMod_Super : 0); |
| 165 |
} |
| 166 |
} |
| 167 |
return ImGuiKey_None; |
| 168 |
} |
| 169 |
|
| 170 |
void ImGuiSettings::showMenu(MSXMotherBoard* motherBoard) |
| 171 |
{ |
| 172 |
im::Menu("Settings", [&]{ |
| 173 |
auto& reactor = manager.getReactor(); |
| 174 |
auto& globalSettings = reactor.getGlobalSettings(); |
| 175 |
auto& renderSettings = reactor.getDisplay().getRenderSettings(); |
| 176 |
const auto& settingsManager = reactor.getGlobalCommandController().getSettingsManager(); |
| 177 |
const auto& hotKey = reactor.getHotKey(); |
| 178 |
|
| 179 |
im::Menu("Video", [&]{ |
| 180 |
im::TreeNode("Look and feel", ImGuiTreeNodeFlags_DefaultOpen, [&]{ |
| 181 |
auto& scaler = renderSettings.getScaleAlgorithmSetting(); |
| 182 |
ComboBox("Scaler", scaler); |
| 183 |
im::Indent([&]{ |
| 184 |
struct AlgoEnable { |
| 185 |
RenderSettings::ScaleAlgorithm algo; |
| 186 |
bool hasScanline; |
| 187 |
bool hasBlur; |
| 188 |
}; |
| 189 |
using enum RenderSettings::ScaleAlgorithm; |
| 190 |
static constexpr std::array algoEnables = { |
| 191 |
AlgoEnable{.algo = SIMPLE, .hasScanline = true, .hasBlur = true }, |
| 192 |
AlgoEnable{.algo = SCALE, .hasScanline = false, .hasBlur = false}, |
| 193 |
AlgoEnable{.algo = HQ, .hasScanline = false, .hasBlur = false}, |
| 194 |
AlgoEnable{.algo = RGBTRIPLET, .hasScanline = true, .hasBlur = true }, |
| 195 |
AlgoEnable{.algo = TV, .hasScanline = true, .hasBlur = false}, |
| 196 |
}; |
| 197 |
auto it = std::ranges::find(algoEnables, scaler.getEnum(), &AlgoEnable::algo); |
| 198 |
assert(it != algoEnables.end()); |
| 199 |
im::Disabled(!it->hasScanline, [&]{ |
| 200 |
SliderInt("Scanline (%)", renderSettings.getScanlineSetting()); |
| 201 |
}); |
| 202 |
im::Disabled(!it->hasBlur, [&]{ |
| 203 |
SliderInt("Blur (%)", renderSettings.getBlurSetting()); |
| 204 |
}); |
| 205 |
}); |
| 206 |
|
| 207 |
SliderInt("Scale factor", renderSettings.getScaleFactorSetting()); |
| 208 |
Checkbox(hotKey, "Deinterlace", renderSettings.getDeinterlaceSetting()); |
| 209 |
Checkbox(hotKey, "Deflicker", renderSettings.getDeflickerSetting()); |
| 210 |
}); |
| 211 |
im::TreeNode("Colors", ImGuiTreeNodeFlags_DefaultOpen, [&]{ |
| 212 |
SliderFloat("Noise (%)", renderSettings.getNoiseSetting()); |
| 213 |
SliderFloat("Brightness", renderSettings.getBrightnessSetting()); |
| 214 |
SliderFloat("Contrast", renderSettings.getContrastSetting()); |
| 215 |
SliderFloat("Gamma", renderSettings.getGammaSetting()); |
| 216 |
SliderInt("Glow (%)", renderSettings.getGlowSetting()); |
| 217 |
if (auto* monitor = dynamic_cast<Setting*>(settingsManager.findSetting("monitor_type"))) { |
| 218 |
ComboBox("Monitor type", *monitor, [](std::string s) { |
| 219 |
std::ranges::replace(s, '_', ' '); |
| 220 |
return s; |
| 221 |
}); |
| 222 |
} |
| 223 |
}); |
| 224 |
im::TreeNode("Shape", ImGuiTreeNodeFlags_DefaultOpen, [&]{ |
| 225 |
Checkbox(hotKey, "Full Stretch", renderSettings.getFullStretchSetting()); |
| 226 |
SliderFloat("Horizontal stretch", renderSettings.getHorizontalStretchSetting(), "%.0f"); |
| 227 |
ComboBox("Display deformation", renderSettings.getDisplayDeformSetting()); |
| 228 |
}); |
| 229 |
im::TreeNode("Misc", ImGuiTreeNodeFlags_DefaultOpen, [&]{ |
| 230 |
Checkbox(hotKey, "Full screen", renderSettings.getFullScreenSetting()); |
| 231 |
if (motherBoard) { |
| 232 |
ComboBox("Video source to display", motherBoard->getVideoSource()); |
| 233 |
} |
| 234 |
Checkbox(hotKey, "VSync", renderSettings.getVSyncSetting()); |
| 235 |
SliderInt("Minimum frame-skip", renderSettings.getMinFrameSkipSetting()); |
| 236 |
SliderInt("Maximum frame-skip", renderSettings.getMaxFrameSkipSetting()); |
| 237 |
}); |
| 238 |
im::TreeNode("Advanced (for debugging)", [&]{ |
| 239 |
Checkbox(hotKey, "Enforce VDP sprites-per-line limit", renderSettings.getLimitSpritesSetting()); |
| 240 |
Checkbox(hotKey, "Disable sprites", renderSettings.getDisableSpritesSetting()); |
| 241 |
ComboBox("Way to handle too fast VDP access", renderSettings.getTooFastAccessSetting()); |
| 242 |
ComboBox("Emulate VDP command timing", renderSettings.getCmdTimingSetting()); |
| 243 |
ComboBox("Rendering accuracy", renderSettings.getAccuracySetting()); |
| 244 |
}); |
| 245 |
}); |
| 246 |
im::Menu("Sound", [&]{ |
| 247 |
auto& mixer = reactor.getMixer(); |
| 248 |
auto& muteSetting = mixer.getMuteSetting(); |
| 249 |
im::Disabled(muteSetting.getBoolean(), [&]{ |
| 250 |
SliderInt("Master volume", mixer.getMasterVolume()); |
| 251 |
}); |
| 252 |
Checkbox(hotKey, "Mute", muteSetting); |
| 253 |
ImGui::Separator(); |
| 254 |
static constexpr std::array resamplerToolTips = { |
| 255 |
EnumToolTip{.value = "hq", .tip = "best quality, uses more CPU"}, |
| 256 |
EnumToolTip{.value = "blip", .tip = "good speed/quality tradeoff"}, |
| 257 |
EnumToolTip{.value = "fast", .tip = "fast but low quality"}, |
| 258 |
}; |
| 259 |
ComboBox("Resampler", globalSettings.getResampleSetting(), resamplerToolTips); |
| 260 |
ImGui::Separator(); |
| 261 |
|
| 262 |
ImGui::MenuItem("Show sound chip settings", nullptr, &manager.soundChip->showSoundChipSettings); |
| 263 |
}); |
| 264 |
im::Menu("Speed", [&]{ |
| 265 |
im::TreeNode("Emulation", ImGuiTreeNodeFlags_DefaultOpen, [&]{ |
| 266 |
ImGui::SameLine(); |
| 267 |
HelpMarker("These control the speed of the whole MSX machine, " |
| 268 |
"the running MSX software can't tell the difference."); |
| 269 |
|
| 270 |
auto& speedManager = globalSettings.getSpeedManager(); |
| 271 |
auto& fwdSetting = speedManager.getFastForwardSetting(); |
| 272 |
int fastForward = fwdSetting.getBoolean() ? 1 : 0; |
| 273 |
ImGui::TextUnformatted("Speed:"sv); |
| 274 |
ImGui::SameLine(); |
| 275 |
bool fwdChanged = ImGui::RadioButton("normal", &fastForward, 0); |
| 276 |
ImGui::SameLine(); |
| 277 |
fwdChanged |= ImGui::RadioButton("fast forward", &fastForward, 1); |
| 278 |
if (auto fastForwardShortCut = getShortCutForCommand(reactor.getHotKey(), "toggle fastforward"); |
| 279 |
!fastForwardShortCut.empty()) { |
| 280 |
HelpMarker(strCat("Use '", fastForwardShortCut ,"' to quickly toggle between these two")); |
| 281 |
} |
| 282 |
if (fwdChanged) { |
| 283 |
fwdSetting.setBoolean(fastForward != 0); |
| 284 |
} |
| 285 |
im::Indent([&]{ |
| 286 |
im::Disabled(fastForward != 0, [&]{ |
| 287 |
SliderFloat("Speed (%)", speedManager.getSpeedSetting(), "%.1f", ImGuiSliderFlags_Logarithmic); |
| 288 |
}); |
| 289 |
im::Disabled(fastForward != 1, [&]{ |
| 290 |
SliderFloat("Fast forward speed (%)", speedManager.getFastForwardSpeedSetting(), "%.1f", ImGuiSliderFlags_Logarithmic); |
| 291 |
}); |
| 292 |
}); |
| 293 |
Checkbox(hotKey, "Go full speed when loading", globalSettings.getThrottleManager().getFullSpeedLoadingSetting()); |
| 294 |
}); |
| 295 |
if (motherBoard) { |
| 296 |
im::TreeNode("MSX devices", ImGuiTreeNodeFlags_DefaultOpen, [&]{ |
| 297 |
ImGui::SameLine(); |
| 298 |
HelpMarker("These control the speed of the specific components in the MSX machine. " |
| 299 |
"So the relative speed between components can change. " |
| 300 |
"And this may lead the emulation problems."); |
| 301 |
|
| 302 |
MSXCPU& cpu = motherBoard->getCPU(); |
| 303 |
auto showFreqSettings = [&](std::string_view name, auto* core) { |
| 304 |
if (!core) return; |
| 305 |
auto& locked = core->getFreqLockedSetting(); |
| 306 |
auto& value = core->getFreqValueSetting(); |
| 307 |
|
| 308 |
bool unlocked = !locked.getBoolean(); |
| 309 |
if (ImGui::Checkbox(tmpStrCat("unlock custom ", name, " frequency").c_str(), &unlocked)) { |
| 310 |
locked.setBoolean(!unlocked); |
| 311 |
} |
| 312 |
simpleToolTip([&]{ return locked.getDescription(); }); |
| 313 |
im::Indent([&]{ |
| 314 |
im::Disabled(!unlocked, [&]{ |
| 315 |
float fval = float(value.getInt()) / 1.0e6f; |
| 316 |
if (ImGui::InputFloat(tmpStrCat("frequency (MHz)##", name).c_str(), &fval, 0.01f, 1.0f, "%.2f")) { |
| 317 |
value.setInt(int(fval * 1.0e6f)); |
| 318 |
} |
| 319 |
im::PopupContextItem(tmpStrCat("freq-context##", name).c_str(), [&]{ |
| 320 |
const char* F358 = name == "Z80" ? "3.58 MHz (default)" |
| 321 |
: "3.58 MHz"; |
| 322 |
if (ImGui::Selectable(F358)) { |
| 323 |
value.setInt(3'579'545); |
| 324 |
} |
| 325 |
if (ImGui::Selectable("5.37 MHz")) { |
| 326 |
value.setInt(5'369'318); |
| 327 |
} |
| 328 |
const char* F716 = name == "R800" ? "7.16 MHz (default)" |
| 329 |
: "7.16 MHz"; |
| 330 |
if (ImGui::Selectable(F716)) { |
| 331 |
value.setInt(7'159'090); |
| 332 |
} |
| 333 |
|
| 334 |
}); |
| 335 |
HelpMarker("Right-click to select commonly used values"); |
| 336 |
}); |
| 337 |
}); |
| 338 |
}; |
| 339 |
showFreqSettings("Z80", cpu.getZ80()); |
| 340 |
showFreqSettings("R800", cpu.getR800()); |
| 341 |
}); |
| 342 |
} |
| 343 |
}); |
| 344 |
im::Menu("Input", [&]{ |
| 345 |
static constexpr std::array kbdModeToolTips = { |
| 346 |
EnumToolTip{.value = "CHARACTER", .tip = "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."}, |
| 347 |
EnumToolTip{.value = "KEY", .tip = "Tries to map a key you press to the corresponding MSX key"}, |
| 348 |
EnumToolTip{.value = "POSITIONAL", .tip = "Tries to map the keyboard key positions to the MSX keyboard key positions"}, |
| 349 |
}; |
| 350 |
if (motherBoard) { |
| 351 |
const auto& controller = motherBoard->getMSXCommandController(); |
| 352 |
if (auto* turbo = dynamic_cast<IntegerSetting*>(controller.findSetting("renshaturbo"))) { |
| 353 |
SliderInt("Ren Sha Turbo (%)", *turbo); |
| 354 |
} |
| 355 |
if (auto* mappingModeSetting = dynamic_cast<EnumSetting<KeyboardSettings::MappingMode>*>(controller.findSetting("kbd_mapping_mode"))) { |
| 356 |
ComboBox("Keyboard mapping mode", *mappingModeSetting, kbdModeToolTips); |
| 357 |
} |
| 358 |
} |
| 359 |
ImGui::MenuItem("Configure MSX joysticks", nullptr, &showConfigureJoystick); |
| 360 |
auto& grabInputSetting = reactor.getInputEventGenerator().getGrabInput(); |
| 361 |
bool grabInput = grabInputSetting.getBoolean(); |
| 362 |
if (auto shortCut = getShortCutForCommand(hotKey, "toggle grabinput"); |
| 363 |
ImGui::MenuItem("Grab input", shortCut.c_str(), &grabInput)) { |
| 364 |
grabInputSetting.setBoolean(grabInput); |
| 365 |
} |
| 366 |
simpleToolTip("Enable this to help you keep the mouse cursor in the MSX window, e.g. when using drawing programs with mouse in windowed mode."); |
| 367 |
}); |
| 368 |
im::Menu("GUI", [&]{ |
| 369 |
saveLayout.menu("Save layout"); |
| 370 |
loadLayout.menu("Restore layout"); |
| 371 |
ImGui::Separator(); |
| 372 |
im::Menu("Select style", [&]{ |
| 373 |
std::optional<int> newStyle; |
| 374 |
static constexpr std::array names = {"Dark", "Light", "Classic", "Colorblind"}; |
| 375 |
for (auto i : xrange(narrow<int>(names.size()))) { |
| 376 |
if (ImGui::Selectable(names[i], selectedStyle == i)) { |
| 377 |
newStyle = i; |
| 378 |
} |
| 379 |
} |
| 380 |
if (newStyle) { |
| 381 |
selectedStyle = *newStyle; |
| 382 |
setStyle(); |
| 383 |
} |
| 384 |
}); |
| 385 |
ImGui::MenuItem("Select font", nullptr, &showFont); |
| 386 |
ImGui::MenuItem("Edit shortcuts", nullptr, &showShortcut); |
| 387 |
ImGui::MenuItem("Configure OSD icons", nullptr, &manager.osdIcons->showConfigureIcons); |
| 388 |
ImGui::MenuItem("Fade out menu bar", nullptr, &manager.menuFade); |
| 389 |
im::Menu("Status bar", [&]{ |
| 390 |
ImGui::Checkbox("Show", &manager.statusBarVisible); |
| 391 |
im::DisabledIndent(!manager.statusBarVisible, [&]{ |
| 392 |
manager.configStatusBarVisibilityItems(); |
| 393 |
}); |
| 394 |
}); |
| 395 |
ImGui::MenuItem("Configure messages", nullptr, &manager.messages->configureWindow.open); |
| 396 |
}); |
| 397 |
im::Menu("MSX Plotter", motherBoard != nullptr, [&]{ |
| 398 |
|
| 399 |
MSXPlotter* plotter = nullptr; |
| 400 |
for (const auto& plug : motherBoard->getPluggingController().getPluggables()) { |
| 401 |
if (auto* p = dynamic_cast<MSXPlotter*>(plug.get())) { |
| 402 |
plotter = p; |
| 403 |
break; |
| 404 |
} |
| 405 |
} |
| 406 |
|
| 407 |
|
| 408 |
static constexpr std::array charSetNames = { |
| 409 |
"International", "Japanese", "DIN (German)" |
| 410 |
}; |
| 411 |
auto currentCharSet = int(plotter->getCharacterSet()); |
| 412 |
if (ImGui::Combo("Character set", ¤tCharSet, charSetNames.data(), int(charSetNames.size()))) { |
| 413 |
plotter->setCharacterSet(static_cast<MSXPlotter::CharacterSet>(currentCharSet)); |
| 414 |
} |
| 415 |
|
| 416 |
|
| 417 |
bool dipSwitch4 = plotter->getDipSwitch4(); |
| 418 |
if (ImGui::Checkbox("Dipswitch 4", &dipSwitch4)) { |
| 419 |
plotter->setDipSwitch4(dipSwitch4); |
| 420 |
} |
| 421 |
|
| 422 |
|
| 423 |
static constexpr std::array thicknessNames = { |
| 424 |
"Standard (PRK-C41)", "Thick (PRK-C42)" |
| 425 |
}; |
| 426 |
auto currentThickness = int(plotter->getPenThicknessSetting().getEnum()); |
| 427 |
if (ImGui::Combo("Pen thickness", ¤tThickness, thicknessNames.data(), int(thicknessNames.size()))) { |
| 428 |
plotter->getPenThicknessSetting().setEnum(static_cast<MSXPlotter::PenThickness>(currentThickness)); |
| 429 |
} |
| 430 |
}); |
| 431 |
ImGui::Separator(); |
| 432 |
im::Menu("Advanced", [&]{ |
| 433 |
ImGui::TextUnformatted("All settings"sv); |
| 434 |
ImGui::Separator(); |
| 435 |
std::vector<Setting*> settings; |
| 436 |
for (auto* setting : settingsManager.getAllSettings()) { |
| 437 |
if (dynamic_cast<ProxySetting*>(setting)) continue; |
| 438 |
if (dynamic_cast<ReadOnlySetting*>(setting)) continue; |
| 439 |
settings.push_back(checked_cast<Setting*>(setting)); |
| 440 |
} |
| 441 |
std::ranges::sort(settings, StringOp::caseless{}, &Setting::getBaseName); |
| 442 |
for (auto* setting : settings) { |
| 443 |
if (auto* bSetting = dynamic_cast<BooleanSetting*>(setting)) { |
| 444 |
Checkbox(hotKey, *bSetting); |
| 445 |
} else if (auto* iSetting = dynamic_cast<IntegerSetting*>(setting)) { |
| 446 |
SliderInt(*iSetting); |
| 447 |
} else if (auto* fSetting = dynamic_cast<FloatSetting*>(setting)) { |
| 448 |
SliderFloat(*fSetting); |
| 449 |
} else if (auto* sSetting = dynamic_cast<StringSetting*>(setting)) { |
| 450 |
InputText(*sSetting); |
| 451 |
} else if (auto* fnSetting = dynamic_cast<FilenameSetting*>(setting)) { |
| 452 |
InputText(*fnSetting); |
| 453 |
} else if (auto* kSetting = dynamic_cast<KeyCodeSetting*>(setting)) { |
| 454 |
InputText(*kSetting); |
| 455 |
} else if (dynamic_cast<EnumSettingBase*>(setting)) { |
| 456 |
ComboBox(*setting); |
| 457 |
} else if (auto* vSetting = dynamic_cast<VideoSourceSetting*>(setting)) { |
| 458 |
ComboBox(*vSetting); |
| 459 |
} else { |
| 460 |
assert(false); |
| 461 |
} |
| 462 |
} |
| 463 |
}); |
| 464 |
}); |
| 465 |
} |
| 466 |
|
| 467 |
|
| 468 |
|
| 469 |
|
| 470 |
[[nodiscard]] static std::string settingName(unsigned joystick) |
| 471 |
{ |
| 472 |
return (joystick < 2) ? strCat("msxjoystick", joystick + 1, "_config") |
| 473 |
: (joystick < 4) ? strCat("joymega", joystick - 1, "_config") |
| 474 |
: strCat("joyhandle", joystick - 3, "_config"); |
| 475 |
} |
| 476 |
|
| 477 |
|
| 478 |
[[nodiscard]] static std::string joystickToGuiString(unsigned joystick) |
| 479 |
{ |
| 480 |
return (joystick < 2) ? strCat("MSX joystick ", joystick + 1) |
| 481 |
: (joystick < 4) ? strCat("JoyMega controller ", joystick - 1) |
| 482 |
: strCat("Panasonic FS-JH1 ", joystick - 3); |
| 483 |
} |
| 484 |
|
| 485 |
[[nodiscard]] static std::string toGuiString(const AnalogInput& input, const JoystickManager& joystickManager) |
| 486 |
{ |
| 487 |
return std::visit(overloaded{ |
| 488 |
[](const AnalogMouseAxis& m) { |
| 489 |
return toString(m); |
| 490 |
}, |
| 491 |
[&](const AnalogJoystickAxis& a) { |
| 492 |
return strCat(joystickManager.getDisplayName(a.getJoystick()), |
| 493 |
" stick axis ", a.getAxis()); |
| 494 |
} |
| 495 |
}, input); |
| 496 |
} |
| 497 |
[[nodiscard]] static std::string toGuiString(const BooleanInput& input, const JoystickManager& joystickManager) |
| 498 |
{ |
| 499 |
return std::visit(overloaded{ |
| 500 |
[](const BooleanKeyboard& k) { |
| 501 |
return strCat("keyboard key ", SDLKey::toString(k.getKeyCode())); |
| 502 |
}, |
| 503 |
[](const BooleanMouseButton& m) { |
| 504 |
return strCat("mouse button ", m.getButton()); |
| 505 |
}, |
| 506 |
[&](const BooleanJoystickButton& j) { |
| 507 |
return strCat(joystickManager.getDisplayName(j.getJoystick()), " button ", j.getButton()); |
| 508 |
}, |
| 509 |
[&](const BooleanJoystickHat& h) { |
| 510 |
return strCat(joystickManager.getDisplayName(h.getJoystick()), " D-pad ", h.getHat(), ' ', toString(h.getValue())); |
| 511 |
}, |
| 512 |
[&](const BooleanJoystickAxis& a) { |
| 513 |
return strCat(joystickManager.getDisplayName(a.getJoystick()), |
| 514 |
" stick axis ", a.getAxis(), ", ", |
| 515 |
(a.getDirection() == BooleanJoystickAxis::Direction::POS ? "positive"sv : "negative"sv), " direction"); |
| 516 |
} |
| 517 |
}, input); |
| 518 |
} |
| 519 |
[[nodiscard]] static std::string toGuiString(bool wheel, zstring_view str, const JoystickManager& joystickManager) |
| 520 |
{ |
| 521 |
return wheel ? toGuiString(*parseAnalogInput (str), joystickManager) |
| 522 |
: toGuiString(*parseBooleanInput(str), joystickManager); |
| 523 |
} |
| 524 |
|
| 525 |
[[nodiscard]] static bool insideCircle(gl::vec2 mouse, gl::vec2 center, float radius) |
| 526 |
{ |
| 527 |
auto delta = center - mouse; |
| 528 |
return gl::sum(delta * delta) <= (radius * radius); |
| 529 |
} |
| 530 |
[[nodiscard]] static bool between(float x, float min, float max) |
| 531 |
{ |
| 532 |
return (min <= x) && (x <= max); |
| 533 |
} |
| 534 |
|
| 535 |
namespace { |
| 536 |
struct Rectangle { |
| 537 |
gl::vec2 topLeft; |
| 538 |
gl::vec2 bottomRight; |
| 539 |
}; |
| 540 |
} |
| 541 |
[[nodiscard]] static bool insideRectangle(gl::vec2 mouse, Rectangle r) |
| 542 |
{ |
| 543 |
return between(mouse.x, r.topLeft.x, r.bottomRight.x) && |
| 544 |
between(mouse.y, r.topLeft.y, r.bottomRight.y); |
| 545 |
} |
| 546 |
|
| 547 |
|
| 548 |
static constexpr auto fractionDPad = 1.0f / 3.0f; |
| 549 |
static constexpr auto thickness = 3.0f; |
| 550 |
|
| 551 |
static void drawDPad(gl::vec2 center, float size, std::span<const uint8_t, 4> hovered, int hoveredRow) |
| 552 |
{ |
| 553 |
const auto F = fractionDPad; |
| 554 |
std::array<std::array<ImVec2, 5 + 1>, 4> points = { |
| 555 |
std::array<ImVec2, 5 + 1>{ |
| 556 |
center + size * gl::vec2{ 0, 0}, |
| 557 |
center + size * gl::vec2{-F, -F}, |
| 558 |
center + size * gl::vec2{-F, -1}, |
| 559 |
center + size * gl::vec2{ F, -1}, |
| 560 |
center + size * gl::vec2{ F, -F}, |
| 561 |
center + size * gl::vec2{ 0, 0}, |
| 562 |
}, |
| 563 |
std::array<ImVec2, 5 + 1>{ |
| 564 |
center + size * gl::vec2{ 0, 0}, |
| 565 |
center + size * gl::vec2{ F, F}, |
| 566 |
center + size * gl::vec2{ F, 1}, |
| 567 |
center + size * gl::vec2{-F, 1}, |
| 568 |
center + size * gl::vec2{-F, F}, |
| 569 |
center + size * gl::vec2{ 0, 0}, |
| 570 |
}, |
| 571 |
std::array<ImVec2, 5 + 1>{ |
| 572 |
center + size * gl::vec2{ 0, 0}, |
| 573 |
center + size * gl::vec2{-F, F}, |
| 574 |
center + size * gl::vec2{-1, F}, |
| 575 |
center + size * gl::vec2{-1, -F}, |
| 576 |
center + size * gl::vec2{-F, -F}, |
| 577 |
center + size * gl::vec2{ 0, 0}, |
| 578 |
}, |
| 579 |
std::array<ImVec2, 5 + 1>{ |
| 580 |
center + size * gl::vec2{ 0, 0}, |
| 581 |
center + size * gl::vec2{ F, -F}, |
| 582 |
center + size * gl::vec2{ 1, -F}, |
| 583 |
center + size * gl::vec2{ 1, F}, |
| 584 |
center + size * gl::vec2{ F, F}, |
| 585 |
center + size * gl::vec2{ 0, 0}, |
| 586 |
}, |
| 587 |
}; |
| 588 |
|
| 589 |
auto* drawList = ImGui::GetWindowDrawList(); |
| 590 |
auto hoverColor = ImGui::GetColorU32(ImGuiCol_ButtonHovered); |
| 591 |
|
| 592 |
auto color = getColor(imColor::TEXT); |
| 593 |
for (auto i : xrange(4)) { |
| 594 |
if (hovered[i] || (hoveredRow == i)) { |
| 595 |
drawList->AddConvexPolyFilled(points[i].data(), 5, hoverColor); |
| 596 |
} |
| 597 |
drawList->AddPolyline(points[i].data(), 5 + 1, color, thickness); |
| 598 |
} |
| 599 |
} |
| 600 |
|
| 601 |
static void drawFilledCircle(gl::vec2 center, float radius, bool fill) |
| 602 |
{ |
| 603 |
auto* drawList = ImGui::GetWindowDrawList(); |
| 604 |
if (fill) { |
| 605 |
auto hoverColor = ImGui::GetColorU32(ImGuiCol_ButtonHovered); |
| 606 |
drawList->AddCircleFilled(center, radius, hoverColor); |
| 607 |
} |
| 608 |
auto color = getColor(imColor::TEXT); |
| 609 |
drawList->AddCircle(center, radius, color, 0, thickness); |
| 610 |
} |
| 611 |
static void drawFilledRectangle(Rectangle r, float corner, bool fill) |
| 612 |
{ |
| 613 |
auto* drawList = ImGui::GetWindowDrawList(); |
| 614 |
if (fill) { |
| 615 |
auto hoverColor = ImGui::GetColorU32(ImGuiCol_ButtonHovered); |
| 616 |
drawList->AddRectFilled(r.topLeft, r.bottomRight, hoverColor, corner); |
| 617 |
} |
| 618 |
auto color = getColor(imColor::TEXT); |
| 619 |
drawList->AddRect(r.topLeft, r.bottomRight, color, corner, thickness); |
| 620 |
} |
| 621 |
|
| 622 |
static void drawLetterA(gl::vec2 center) |
| 623 |
{ |
| 624 |
auto* drawList = ImGui::GetWindowDrawList(); |
| 625 |
auto tr = [&](gl::vec2 p) { return center + p; }; |
| 626 |
const std::array<ImVec2, 3> lines = { tr({-6, 7}), tr({0, -7}), tr({6, 7}) }; |
| 627 |
auto color = getColor(imColor::TEXT); |
| 628 |
drawList->AddPolyline(lines.data(), narrow<int>(lines.size()), color, thickness); |
| 629 |
drawList->AddLine(tr({-3, 1}), tr({3, 1}), color, thickness); |
| 630 |
} |
| 631 |
static void drawLetterB(gl::vec2 center) |
| 632 |
{ |
| 633 |
auto* drawList = ImGui::GetWindowDrawList(); |
| 634 |
auto tr = [&](gl::vec2 p) { return center + p; }; |
| 635 |
const std::array<ImVec2, 4> lines = { tr({1, -7}), tr({-4, -7}), tr({-4, 7}), tr({2, 7}) }; |
| 636 |
auto color = getColor(imColor::TEXT); |
| 637 |
drawList->AddPolyline(lines.data(), narrow<int>(lines.size()), color, thickness); |
| 638 |
drawList->AddLine(tr({-4, -1}), tr({2, -1}), color, thickness); |
| 639 |
drawList->AddBezierQuadratic(tr({1, -7}), tr({4, -7}), tr({4, -4}), color, thickness); |
| 640 |
drawList->AddBezierQuadratic(tr({4, -4}), tr({4, -1}), tr({1, -1}), color, thickness); |
| 641 |
drawList->AddBezierQuadratic(tr({2, -1}), tr({6, -1}), tr({6, 3}), color, thickness); |
| 642 |
drawList->AddBezierQuadratic(tr({6, 3}), tr({6, 7}), tr({2, 7}), color, thickness); |
| 643 |
} |
| 644 |
static void drawLetterC(gl::vec2 center) |
| 645 |
{ |
| 646 |
auto* drawList = ImGui::GetWindowDrawList(); |
| 647 |
auto tr = [&](gl::vec2 p) { return center + p; }; |
| 648 |
auto color = getColor(imColor::TEXT); |
| 649 |
drawList->AddBezierCubic(tr({5, -5}), tr({-8, -16}), tr({-8, 16}), tr({5, 5}), color, thickness); |
| 650 |
} |
| 651 |
static void drawLetterX(gl::vec2 center) |
| 652 |
{ |
| 653 |
auto* drawList = ImGui::GetWindowDrawList(); |
| 654 |
auto tr = [&](gl::vec2 p) { return center + p; }; |
| 655 |
auto color = getColor(imColor::TEXT); |
| 656 |
drawList->AddLine(tr({-4, -6}), tr({4, 6}), color, thickness); |
| 657 |
drawList->AddLine(tr({-4, 6}), tr({4, -6}), color, thickness); |
| 658 |
} |
| 659 |
static void drawLetterY(gl::vec2 center) |
| 660 |
{ |
| 661 |
auto* drawList = ImGui::GetWindowDrawList(); |
| 662 |
auto tr = [&](gl::vec2 p) { return center + p; }; |
| 663 |
auto color = getColor(imColor::TEXT); |
| 664 |
drawList->AddLine(tr({-4, -6}), tr({0, 0}), color, thickness); |
| 665 |
drawList->AddLine(tr({-4, 6}), tr({4, -6}), color, thickness); |
| 666 |
} |
| 667 |
static void drawLetterZ(gl::vec2 center) |
| 668 |
{ |
| 669 |
auto* drawList = ImGui::GetWindowDrawList(); |
| 670 |
auto tr = [&](gl::vec2 p) { return center + p; }; |
| 671 |
const std::array<ImVec2, 4> linesZ2 = { tr({-4, -6}), tr({4, -6}), tr({-4, 6}), tr({4, 6}) }; |
| 672 |
auto color = getColor(imColor::TEXT); |
| 673 |
drawList->AddPolyline(linesZ2.data(), 4, color, thickness); |
| 674 |
} |
| 675 |
|
| 676 |
namespace msxjoystick { |
| 677 |
|
| 678 |
enum : uint8_t {UP, DOWN, LEFT, RIGHT, TRIG_A, TRIG_B, NUM_BUTTONS}; |
| 679 |
|
| 680 |
static constexpr std::array<zstring_view, NUM_BUTTONS> buttonNames = { |
| 681 |
"Up", "Down", "Left", "Right", "A", "B" |
| 682 |
}; |
| 683 |
static constexpr std::array<zstring_view, NUM_BUTTONS> keyNames = { |
| 684 |
"UP", "DOWN", "LEFT", "RIGHT", "A", "B" |
| 685 |
}; |
| 686 |
|
| 687 |
|
| 688 |
static constexpr auto boundingBox = gl::vec2{300.0f, 100.0f}; |
| 689 |
static constexpr auto radius = 20.0f; |
| 690 |
static constexpr auto corner = 10.0f; |
| 691 |
static constexpr auto centerA = gl::vec2{200.0f, 50.0f}; |
| 692 |
static constexpr auto centerB = gl::vec2{260.0f, 50.0f}; |
| 693 |
static constexpr auto centerDPad = gl::vec2{50.0f, 50.0f}; |
| 694 |
static constexpr auto sizeDPad = 30.0f; |
| 695 |
|
| 696 |
[[nodiscard]] static std::vector<uint8_t> buttonsHovered(gl::vec2 mouse) |
| 697 |
{ |
| 698 |
std::vector<uint8_t> result(NUM_BUTTONS); |
| 699 |
auto mouseDPad = (mouse - centerDPad) * (1.0f / sizeDPad); |
| 700 |
if (insideRectangle(mouseDPad, Rectangle{{-1, -1}, {1, 1}}) && |
| 701 |
(between(mouseDPad.x, -fractionDPad, fractionDPad) || |
| 702 |
between(mouseDPad.y, -fractionDPad, fractionDPad))) { |
| 703 |
bool t1 = mouseDPad.x < mouseDPad.y; |
| 704 |
bool t2 = mouseDPad.x < -mouseDPad.y; |
| 705 |
result[UP] = !t1 && t2; |
| 706 |
result[DOWN] = t1 && !t2; |
| 707 |
result[LEFT] = t1 && t2; |
| 708 |
result[RIGHT] = !t1 && !t2; |
| 709 |
} |
| 710 |
result[TRIG_A] = insideCircle(mouse, centerA, radius); |
| 711 |
result[TRIG_B] = insideCircle(mouse, centerB, radius); |
| 712 |
return result; |
| 713 |
} |
| 714 |
|
| 715 |
static void draw(gl::vec2 scrnPos, std::span<uint8_t> hovered, int hoveredRow) |
| 716 |
{ |
| 717 |
auto* drawList = ImGui::GetWindowDrawList(); |
| 718 |
|
| 719 |
auto color = getColor(imColor::TEXT); |
| 720 |
drawList->AddRect(scrnPos, scrnPos + boundingBox, color, corner, thickness); |
| 721 |
|
| 722 |
drawDPad(scrnPos + centerDPad, sizeDPad, subspan<4>(hovered), hoveredRow); |
| 723 |
|
| 724 |
auto scrnCenterA = scrnPos + centerA; |
| 725 |
drawFilledCircle(scrnCenterA, radius, hovered[TRIG_A] || (hoveredRow == TRIG_A)); |
| 726 |
drawLetterA(scrnCenterA); |
| 727 |
|
| 728 |
auto scrnCenterB = scrnPos + centerB; |
| 729 |
drawFilledCircle(scrnCenterB, radius, hovered[TRIG_B] || (hoveredRow == TRIG_B)); |
| 730 |
drawLetterB(scrnCenterB); |
| 731 |
} |
| 732 |
|
| 733 |
} |
| 734 |
|
| 735 |
namespace joymega { |
| 736 |
|
| 737 |
enum : uint8_t {UP, DOWN, LEFT, RIGHT, |
| 738 |
TRIG_A, TRIG_B, TRIG_C, |
| 739 |
TRIG_X, TRIG_Y, TRIG_Z, |
| 740 |
TRIG_SELECT, TRIG_START, |
| 741 |
NUM_BUTTONS}; |
| 742 |
|
| 743 |
static constexpr std::array<zstring_view, NUM_BUTTONS> buttonNames = { |
| 744 |
"Up", "Down", "Left", "Right", |
| 745 |
"A", "B", "C", |
| 746 |
"X", "Y", "Z", |
| 747 |
"Select", "Start", |
| 748 |
}; |
| 749 |
static constexpr std::array<zstring_view, NUM_BUTTONS> keyNames = { |
| 750 |
"UP", "DOWN", "LEFT", "RIGHT", |
| 751 |
"A", "B", "C", |
| 752 |
"X", "Y", "Z", |
| 753 |
"SELECT", "START", |
| 754 |
}; |
| 755 |
|
| 756 |
|
| 757 |
static constexpr auto thickness = 3.0f; |
| 758 |
static constexpr auto boundingBox = gl::vec2{300.0f, 158.0f}; |
| 759 |
static constexpr auto centerA = gl::vec2{205.0f, 109.9f}; |
| 760 |
static constexpr auto centerB = gl::vec2{235.9f, 93.5f}; |
| 761 |
static constexpr auto centerC = gl::vec2{269.7f, 83.9f}; |
| 762 |
static constexpr auto centerX = gl::vec2{194.8f, 75.2f}; |
| 763 |
static constexpr auto centerY = gl::vec2{223.0f, 61.3f}; |
| 764 |
static constexpr auto centerZ = gl::vec2{252.2f, 52.9f}; |
| 765 |
static constexpr auto selectBox = Rectangle{gl::vec2{130.0f, 60.0f}, gl::vec2{160.0f, 70.0f}}; |
| 766 |
static constexpr auto startBox = Rectangle{gl::vec2{130.0f, 86.0f}, gl::vec2{160.0f, 96.0f}}; |
| 767 |
static constexpr auto radiusABC = 16.2f; |
| 768 |
static constexpr auto radiusXYZ = 12.2f; |
| 769 |
static constexpr auto centerDPad = gl::vec2{65.6f, 82.7f}; |
| 770 |
static constexpr auto sizeDPad = 34.0f; |
| 771 |
static constexpr auto fractionDPad = 1.0f / 3.0f; |
| 772 |
|
| 773 |
[[nodiscard]] static std::vector<uint8_t> buttonsHovered(gl::vec2 mouse) |
| 774 |
{ |
| 775 |
std::vector<uint8_t> result(NUM_BUTTONS); |
| 776 |
auto mouseDPad = (mouse - centerDPad) * (1.0f / sizeDPad); |
| 777 |
if (insideRectangle(mouseDPad, Rectangle{{-1, -1}, {1, 1}}) && |
| 778 |
(between(mouseDPad.x, -fractionDPad, fractionDPad) || |
| 779 |
between(mouseDPad.y, -fractionDPad, fractionDPad))) { |
| 780 |
bool t1 = mouseDPad.x < mouseDPad.y; |
| 781 |
bool t2 = mouseDPad.x < -mouseDPad.y; |
| 782 |
result[UP] = !t1 && t2; |
| 783 |
result[DOWN] = t1 && !t2; |
| 784 |
result[LEFT] = t1 && t2; |
| 785 |
result[RIGHT] = !t1 && !t2; |
| 786 |
} |
| 787 |
result[TRIG_A] = insideCircle(mouse, centerA, radiusABC); |
| 788 |
result[TRIG_B] = insideCircle(mouse, centerB, radiusABC); |
| 789 |
result[TRIG_C] = insideCircle(mouse, centerC, radiusABC); |
| 790 |
result[TRIG_X] = insideCircle(mouse, centerX, radiusXYZ); |
| 791 |
result[TRIG_Y] = insideCircle(mouse, centerY, radiusXYZ); |
| 792 |
result[TRIG_Z] = insideCircle(mouse, centerZ, radiusXYZ); |
| 793 |
result[TRIG_START] = insideRectangle(mouse, startBox); |
| 794 |
result[TRIG_SELECT] = insideRectangle(mouse, selectBox); |
| 795 |
return result; |
| 796 |
} |
| 797 |
|
| 798 |
static void draw(gl::vec2 scrnPos, std::span<uint8_t> hovered, int hoveredRow) |
| 799 |
{ |
| 800 |
auto* drawList = ImGui::GetWindowDrawList(); |
| 801 |
auto tr = [&](gl::vec2 p) { return scrnPos + p; }; |
| 802 |
auto color = getColor(imColor::TEXT); |
| 803 |
|
| 804 |
auto drawBezierCurve = [&](std::span<const gl::vec2> points, float thick = 1.0f) { |
| 805 |
assert((points.size() % 2) == 0); |
| 806 |
for (size_t i = 0; i < points.size() - 2; i += 2) { |
| 807 |
auto ap = points[i + 0]; |
| 808 |
auto ad = points[i + 1]; |
| 809 |
auto bp = points[i + 2]; |
| 810 |
auto bd = points[i + 3]; |
| 811 |
drawList->AddBezierCubic(tr(ap), tr(ap + ad), tr(bp - bd), tr(bp), color, thick); |
| 812 |
} |
| 813 |
}; |
| 814 |
|
| 815 |
std::array outLine = { |
| 816 |
gl::vec2{150.0f, 0.0f}, gl::vec2{ 23.1f, 0.0f}, |
| 817 |
gl::vec2{258.3f, 30.3f}, gl::vec2{ 36.3f, 26.4f}, |
| 818 |
gl::vec2{300.0f, 107.0f}, gl::vec2{ 0.0f, 13.2f}, |
| 819 |
gl::vec2{285.2f, 145.1f}, gl::vec2{ -9.9f, 9.9f}, |
| 820 |
gl::vec2{255.3f, 157.4f}, gl::vec2{ -9.0f, 0.0f}, |
| 821 |
gl::vec2{206.0f, 141.8f}, gl::vec2{-16.2f, -5.6f}, |
| 822 |
gl::vec2{150.0f, 131.9f}, gl::vec2{-16.5f, 0.0f}, |
| 823 |
gl::vec2{ 94.0f, 141.8f}, gl::vec2{-16.2f, 5.6f}, |
| 824 |
gl::vec2{ 44.7f, 157.4f}, gl::vec2{ -9.0f, 0.0f}, |
| 825 |
gl::vec2{ 14.8f, 145.1f}, gl::vec2{ -9.9f, -9.9f}, |
| 826 |
gl::vec2{ 0.0f, 107.0f}, gl::vec2{ 0.0f, -13.2f}, |
| 827 |
gl::vec2{ 41.7f, 30.3f}, gl::vec2{ 36.3f, -26.4f}, |
| 828 |
gl::vec2{150.0f, 0.0f}, gl::vec2{ 23.1f, 0.0f}, |
| 829 |
}; |
| 830 |
drawBezierCurve(outLine, thickness); |
| 831 |
|
| 832 |
drawDPad(tr(centerDPad), sizeDPad, subspan<4>(hovered), hoveredRow); |
| 833 |
drawList->AddCircle(tr(centerDPad), 43.0f, color); |
| 834 |
std::array dPadCurve = { |
| 835 |
gl::vec2{77.0f, 33.0f}, gl::vec2{ 69.2f, 0.0f}, |
| 836 |
gl::vec2{54.8f, 135.2f}, gl::vec2{-66.9f, 0.0f}, |
| 837 |
gl::vec2{77.0f, 33.0f}, gl::vec2{ 69.2f, 0.0f}, |
| 838 |
}; |
| 839 |
drawBezierCurve(dPadCurve); |
| 840 |
|
| 841 |
drawFilledCircle(tr(centerA), radiusABC, hovered[TRIG_A] || (hoveredRow == TRIG_A)); |
| 842 |
drawLetterA(tr(centerA)); |
| 843 |
drawFilledCircle(tr(centerB), radiusABC, hovered[TRIG_B] || (hoveredRow == TRIG_B)); |
| 844 |
drawLetterB(tr(centerB)); |
| 845 |
drawFilledCircle(tr(centerC), radiusABC, hovered[TRIG_C] || (hoveredRow == TRIG_C)); |
| 846 |
drawLetterC(tr(centerC)); |
| 847 |
drawFilledCircle(tr(centerX), radiusXYZ, hovered[TRIG_X] || (hoveredRow == TRIG_X)); |
| 848 |
drawLetterX(tr(centerX)); |
| 849 |
drawFilledCircle(tr(centerY), radiusXYZ, hovered[TRIG_Y] || (hoveredRow == TRIG_Y)); |
| 850 |
drawLetterY(tr(centerY)); |
| 851 |
drawFilledCircle(tr(centerZ), radiusXYZ, hovered[TRIG_Z] || (hoveredRow == TRIG_Z)); |
| 852 |
drawLetterZ(tr(centerZ)); |
| 853 |
std::array buttonCurve = { |
| 854 |
gl::vec2{221.1f, 28.9f}, gl::vec2{ 80.1f, 0.0f}, |
| 855 |
gl::vec2{236.9f, 139.5f}, gl::vec2{-76.8f, 0.0f}, |
| 856 |
gl::vec2{221.1f, 28.9f}, gl::vec2{ 80.1f, 0.0f}, |
| 857 |
}; |
| 858 |
drawBezierCurve(buttonCurve); |
| 859 |
|
| 860 |
auto corner = (selectBox.bottomRight.y - selectBox.topLeft.y) * 0.5f; |
| 861 |
auto trR = [&](Rectangle r) { return Rectangle{tr(r.topLeft), tr(r.bottomRight)}; }; |
| 862 |
drawFilledRectangle(trR(selectBox), corner, hovered[TRIG_SELECT] || (hoveredRow == TRIG_SELECT)); |
| 863 |
drawList->AddText(ImGui::GetFont(), ImGui::GetFontSize(), tr({123.0f, 46.0f}), color, "Select"); |
| 864 |
drawFilledRectangle(trR(startBox), corner, hovered[TRIG_START] || (hoveredRow == TRIG_START)); |
| 865 |
drawList->AddText(ImGui::GetFont(), ImGui::GetFontSize(), tr({128.0f, 97.0f}), color, "Start"); |
| 866 |
} |
| 867 |
|
| 868 |
} |
| 869 |
|
| 870 |
namespace joyhandle { |
| 871 |
|
| 872 |
enum : uint8_t { |
| 873 |
UP, DOWN, LEFT, RIGHT, |
| 874 |
TRIG_A, TRIG_B, WHEEL, |
| 875 |
NUM_BUTTONS |
| 876 |
}; |
| 877 |
static constexpr std::array<zstring_view, NUM_BUTTONS> buttonNames = { |
| 878 |
"Up", "Down", "Left", "Right", |
| 879 |
"A", "B", "Wheel (analog)", |
| 880 |
}; |
| 881 |
static constexpr std::array<zstring_view, NUM_BUTTONS> keyNames = { |
| 882 |
"UP", "DOWN", "LEFT", "RIGHT", |
| 883 |
"A", "B", "WHEEL", |
| 884 |
}; |
| 885 |
|
| 886 |
[[nodiscard]] static std::vector<uint8_t> buttonsHovered(gl::vec2 mouse) |
| 887 |
{ |
| 888 |
std::vector<uint8_t> result(NUM_BUTTONS); |
| 889 |
auto mouse1 = mouse - gl::vec2{39, 149}; |
| 890 |
if (insideCircle(mouse1, {}, 24)) { |
| 891 |
auto ax = std::abs(mouse1.x); |
| 892 |
auto ay = std::abs(mouse1.y); |
| 893 |
if (mouse1.y < 0 && ax < ay) result[UP ] = true; |
| 894 |
if (mouse1.y > 0 && ax < ay) result[DOWN ] = true; |
| 895 |
if (mouse1.x < 0 && ay < ax) result[LEFT ] = true; |
| 896 |
if (mouse1.x > 0 && ay < ax) result[RIGHT] = true; |
| 897 |
|
| 898 |
} |
| 899 |
result[TRIG_A] = insideCircle(mouse, {77, 186}, 5) |
| 900 |
|| insideCircle(mouse, {319, 36}, 15); |
| 901 |
result[TRIG_B] = insideCircle(mouse, {63, 186}, 5) |
| 902 |
|| insideCircle(mouse, {134, 36}, 15); |
| 903 |
if (insideRectangle(mouse, {{110,80}, {343,190}})) result[WHEEL] = true; |
| 904 |
return result; |
| 905 |
} |
| 906 |
|
| 907 |
using namespace VectorPath::dsl; |
| 908 |
static constexpr auto boundingBox = gl::vec2{385.0f, 242.0f}; |
| 909 |
static constexpr auto body = strokeCollection<int16_t>( |
| 910 |
openPath(from(698,220), line(712,220), curve(719,220, 722,223, 722,230), line(727,408), |
| 911 |
line(731,454), line(731,463), curve(731,466, 728,468, 726,468), line(14,468), |
| 912 |
curve(11,468, 9,465, 9,463), line(9,454), line(13,408), line(18,230), |
| 913 |
curve(18,223, 25,220, 28,220), line(212,220)), |
| 914 |
openPath(from(9,454), line(731,454)), |
| 915 |
openPath(from(13,408), line(201,408)), |
| 916 |
openPath(from(270,408), line(640,408)), |
| 917 |
openPath(from(709,408), line(727,408)), |
| 918 |
|
| 919 |
openPath(from(20,224), line(69,168), curve(73,164, 76,164, 80,164), line(660,164), |
| 920 |
curve(664,164, 667,164, 671,168), line(720,223)), |
| 921 |
|
| 922 |
openPath(from(84,468), line(84,477), curve(84,479, 83,480, 81,480), line(21,480), |
| 923 |
curve(19,480, 18,479, 18,477), line(18,468)), |
| 924 |
openPath(from(488,468), line(488,477), curve(488,479, 487,480, 485,480), line(425,480), |
| 925 |
curve(423,480, 422,479, 422,477), line(422,468)), |
| 926 |
openPath(from(722,468), line(722,477), curve(722,479, 721,480, 719,480), line(659,480), |
| 927 |
curve(657,480, 656,479, 656,477), line(656,468)), |
| 928 |
|
| 929 |
closedPath(from(183,233), curve(186,233, 188,235, 188,238), line(188,387), |
| 930 |
curve(188,390, 186,392, 183,392), line(31,392), curve(28,392, 26,390, 26,387), |
| 931 |
line(26,238), curve(26,235, 28,233, 31,233)), |
| 932 |
|
| 933 |
closedPath(from(133,285), line(151,285), line(151,323), line(133,323)), |
| 934 |
openPath(from(138,299), line(146,299)), |
| 935 |
openPath(from(138,305), line(146,305)), |
| 936 |
|
| 937 |
closedPath(from(162,285), line(180,285), line(180,323), line(162,323)), |
| 938 |
openPath(from(167,299), line(175,299)), |
| 939 |
openPath(from(167,305), line(175,305)), |
| 940 |
|
| 941 |
closedPath(from(217,212), curve(214,212, 212,214, 212,217), line(201,392), line(201,444), |
| 942 |
line(263,444), line(263,217), curve(263,214, 261,212, 258,212)), |
| 943 |
openPath(from(201,392), line(250,392), curve(253,392, 255,390, 255,387), line(255,212)), |
| 944 |
openPath(from(263,444), line(270,408), line(270,210), curve(270,207, 268,205, 265,205)), |
| 945 |
|
| 946 |
closedPath(from(693,212), curve(696,212, 698,214, 698,217), line(709,392), line(709,444), |
| 947 |
line(647,444), line(647,217), curve(647,214, 649,212, 652,212)), |
| 948 |
openPath(from(709,392), line(660,392), curve(657,392, 655,390, 655,387), line(655,212)), |
| 949 |
openPath(from(647,444), line(640,408), line(640,210), curve(640,207, 642,205, 645,205)), |
| 950 |
|
| 951 |
openPath(from(212,217), line(213,210), curve(213,208, 215,205, 218,205), line(692,205), |
| 952 |
curve(695,205, 697,208, 697,210), line(698,217))); |
| 953 |
static constexpr auto wheelOutline = path<int16_t>( |
| 954 |
from(78,56), |
| 955 |
curve(75,64, 67,68, 57,68), line(-57,68), curve(-67,68, -75,64, -78,56), line(-156,72), |
| 956 |
curve(-186,78, -231,58, -231,19), line(-231,-21), curve(-236,-24, -240,-26, -240,-29), |
| 957 |
line(-214,-245), curve(-214,-257, -155,-255, -155,-243), line(-163,-27), |
| 958 |
curve(-163,-23, -168,-21, -175,-19), curve(-175,5, -167,17, -147,7), line(-72,-32), |
| 959 |
line(-71,-47), curve(-69,-60, -62,-65, -50,-65), line(50,-65), |
| 960 |
curve(62,-65, 69,-60, 71,-47), line(72,-32), line(147,7), |
| 961 |
curve(167,17, 175,5, 175,-19), curve(168,-21, 163,-23, 163,-27), line(155,-243), |
| 962 |
curve(155,-255, 214,-257, 214,-245), line(240,-29), curve(240,-26, 237,-24, 231,-21), |
| 963 |
line(231,22), curve(231,61, 186,78, 156,72)); |
| 964 |
static constexpr auto wheelInside = strokeCollection<int16_t>( |
| 965 |
openPath(from(-163,-27), curve(-163,-13, -240,-15, -240,-29)), |
| 966 |
openPath(from(163,-27), curve(163,-13, 240,-15, 240,-29)), |
| 967 |
closedPath(from(47,-51), curve(53,-51, 56,-49, 57,-43), line(63,40), |
| 968 |
curve(63,47, 61,51, 52,51), line(-52,51), curve(-61,51, -63,47, -63,40), |
| 969 |
line(-57,-43), curve(-56,-49, -53,-51, -47,-51)), |
| 970 |
openPath(from(-215,-18), line(-215,19), curve(-215,49, -185,60, -161,55), line(-79,40)), |
| 971 |
openPath(from(-74,-15), line(-148,23), curve(-175,33, -190,13, -190,-17)), |
| 972 |
openPath(from(-57,68), curve(-71,68, -80,61, -79,46), line(-72,-32)), |
| 973 |
openPath(from(215,-18), line(215,19), curve(215,49, 185,60, 161,55), line(79,40)), |
| 974 |
openPath(from(74,-15), line(148,23), curve(175,33, 190,13, 190,-17)), |
| 975 |
openPath(from(57,68), curve(71,68, 80,61, 79,46), line(72,-32))); |
| 976 |
static constexpr auto wheelButtonA = path<int16_t>( |
| 977 |
from(155,-243), |
| 978 |
curve(155,-255, 214,-257, 214,-245), |
| 979 |
curve(214,-233, 155,-231, 155,-243)); |
| 980 |
static constexpr auto wheelButtonB = path<int16_t>( |
| 981 |
from(-155,-243), |
| 982 |
curve(-155,-231, -214,-233, -214,-245), |
| 983 |
curve(-214,-257, -155,-255, -155,-243)); |
| 984 |
static constexpr auto wheelButtonInside = strokeCollection<int16_t>( |
| 985 |
openPath(from(-164,-243), curve(-164,-236, -207,-238, -207,-245)), |
| 986 |
openPath(from(164,-243), curve(164,-236, 207,-238, 207,-245))); |
| 987 |
|
| 988 |
static void draw(gl::vec2 scrnPos, std::span<uint8_t> hovered, int hoveredRow, float analogValue) |
| 989 |
{ |
| 990 |
static constexpr ImU32 eraseColor = IM_COL32(0, 0, 0, 1); |
| 991 |
auto color = getColor(imColor::TEXT); |
| 992 |
auto hoverColor = ImGui::GetColorU32(ImGuiCol_ButtonHovered); |
| 993 |
|
| 994 |
bool up = hovered[UP ] || hoveredRow == UP; |
| 995 |
bool down = hovered[DOWN ] || hoveredRow == DOWN; |
| 996 |
bool left = hovered[LEFT ] || hoveredRow == LEFT; |
| 997 |
bool right = hovered[RIGHT ] || hoveredRow == RIGHT; |
| 998 |
bool buttonA = hovered[TRIG_A] || hoveredRow == TRIG_A; |
| 999 |
bool buttonB = hovered[TRIG_B] || hoveredRow == TRIG_B; |
| 1000 |
bool wheel = hovered[WHEEL ] || hoveredRow == WHEEL; |
| 1001 |
|
| 1002 |
gl::vec2 stickOffset{0}; |
| 1003 |
gl::vec2 stickRadius{1}; |
| 1004 |
if (up ) { stickOffset.y = -17; stickRadius.y = 0.85f; } |
| 1005 |
if (down ) { stickOffset.y = 17; stickRadius.y = 0.85f; } |
| 1006 |
if (left ) { stickOffset.x = -17; stickRadius.x = 0.85f; } |
| 1007 |
if (right) { stickOffset.x = 17; stickRadius.x = 0.85f; } |
| 1008 |
auto stickColor = (up || down || left || right) ? hoverColor : eraseColor; |
| 1009 |
|
| 1010 |
auto wheelColor = wheel ? hoverColor : eraseColor; |
| 1011 |
|
| 1012 |
using namespace VectorPath; |
| 1013 |
static constexpr float scale = 0.5f; |
| 1014 |
static constexpr float thickness = 1.0f; |
| 1015 |
|
| 1016 |
|
| 1017 |
ImDrawList dl(ImGui::GetDrawListSharedData()); |
| 1018 |
dl._ResetForNewFrame(); |
| 1019 |
dl.PushTexture(ImGui::GetIO().Fonts->TexRef); |
| 1020 |
gl::vec2 logicalSize = boundingBox; |
| 1021 |
dl.PushClipRect({0, 0}, logicalSize); |
| 1022 |
|
| 1023 |
|
| 1024 |
ScaleTransform transform{scale}; |
| 1025 |
|
| 1026 |
float angle = analogValue * (30.0f / 180.0f * float(Math::pi)); |
| 1027 |
auto wheelTransform = makeAffineTransform(angle, scale, gl::vec2{453, 317} * scale); |
| 1028 |
|
| 1029 |
drawPathStrokes<int16_t>(&dl, body, transform, color, thickness); |
| 1030 |
static constexpr gl::vec2 stickCenter1{78, 298}; |
| 1031 |
dl.AddCircle(transform(stickCenter1), 44 * scale, color); |
| 1032 |
|
| 1033 |
|
| 1034 |
if (buttonA) { |
| 1035 |
dl.AddCircleFilled(transform(155, 372), 10 * scale, hoverColor); |
| 1036 |
} |
| 1037 |
if (buttonB) { |
| 1038 |
dl.AddCircleFilled(transform(127, 372), 10 * scale, hoverColor); |
| 1039 |
} |
| 1040 |
dl.AddCircle(transform(155, 372), 10 * scale, color, 0, thickness); |
| 1041 |
dl.AddCircle(transform(127, 372), 10 * scale, color, 0, thickness); |
| 1042 |
|
| 1043 |
auto stickCenter = transform(stickCenter1 + stickOffset); |
| 1044 |
|
| 1045 |
|
| 1046 |
{ |
| 1047 |
dl.AddCallback([](const ImDrawList*, const ImDrawCmd*) { |
| 1048 |
|
| 1049 |
glBlendFunc(GL_ONE, GL_ZERO); |
| 1050 |
}, nullptr); |
| 1051 |
dl.AddEllipseFilled(stickCenter, 35 * scale * stickRadius, stickColor); |
| 1052 |
drawPathFilled<int16_t>(&dl, wheelOutline, wheelTransform, wheelColor); |
| 1053 |
dl.AddCallback([](const ImDrawList*, const ImDrawCmd*) { |
| 1054 |
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |
| 1055 |
}, nullptr); |
| 1056 |
} |
| 1057 |
|
| 1058 |
dl.AddEllipse (stickCenter, 35 * scale * stickRadius, color); |
| 1059 |
dl.AddEllipseFilled(stickCenter, 5 * scale * stickRadius, color); |
| 1060 |
|
| 1061 |
drawPathStroke<int16_t>(&dl, wheelOutline, wheelTransform, color, thickness, ImDrawFlags_Closed); |
| 1062 |
drawPathStrokes<int16_t>(&dl, wheelInside, wheelTransform, color, thickness); |
| 1063 |
|
| 1064 |
if (buttonA) { |
| 1065 |
drawPathFilled<int16_t>(&dl, wheelButtonA, wheelTransform, hoverColor); |
| 1066 |
} |
| 1067 |
drawPathStroke<int16_t>(&dl, wheelButtonA, wheelTransform, color, thickness); |
| 1068 |
if (buttonB) { |
| 1069 |
drawPathFilled<int16_t>(&dl, wheelButtonB, wheelTransform, hoverColor); |
| 1070 |
} |
| 1071 |
drawPathStroke<int16_t>(&dl, wheelButtonB, wheelTransform, color, thickness); |
| 1072 |
drawPathStrokes<int16_t>(&dl, wheelButtonInside, wheelTransform, color, thickness); |
| 1073 |
|
| 1074 |
dl.PopClipRect(); |
| 1075 |
|
| 1076 |
|
| 1077 |
auto fbScale = ImGui::GetIO().DisplayFramebufferScale; |
| 1078 |
auto fbSize = logicalSize * fbScale; |
| 1079 |
|
| 1080 |
struct ShapeLayerRT { |
| 1081 |
gl::ColorTexture tex{0, 0}; |
| 1082 |
gl::FrameBufferObject fbo; |
| 1083 |
|
| 1084 |
void ensure(gl::ivec2 newSize) { |
| 1085 |
if (tex.size() == newSize) return; |
| 1086 |
|
| 1087 |
tex.resize(newSize.x, newSize.y); |
| 1088 |
tex.setInterpolation(true); |
| 1089 |
fbo = gl::FrameBufferObject(tex); |
| 1090 |
} |
| 1091 |
}; |
| 1092 |
static ShapeLayerRT rt; |
| 1093 |
rt.ensure(gl::ivec2(fbSize)); |
| 1094 |
|
| 1095 |
ImDrawData dd; |
| 1096 |
dd.Valid = true; |
| 1097 |
dd.DisplayPos = {0, 0}; |
| 1098 |
dd.DisplaySize = logicalSize; |
| 1099 |
dd.FramebufferScale = fbScale; |
| 1100 |
dd.Textures = &ImGui::GetPlatformIO().Textures; |
| 1101 |
dd.AddDrawList(&dl); |
| 1102 |
|
| 1103 |
rt.fbo.push(); |
| 1104 |
glViewport(0, 0, int(fbSize.x), int(fbSize.y)); |
| 1105 |
glClearColor(0.f, 0.f, 0.f, 0.f); |
| 1106 |
glClear(GL_COLOR_BUFFER_BIT); |
| 1107 |
ImGui_ImplOpenGL3_RenderDrawData(&dd); |
| 1108 |
rt.fbo.pop(); |
| 1109 |
|
| 1110 |
|
| 1111 |
auto pos = ImGui::GetCursorPos(); |
| 1112 |
ImGui::SetCursorScreenPos(scrnPos); |
| 1113 |
ImGui::GetWindowDrawList()->AddCallback([](const ImDrawList*, const ImDrawCmd*) { |
| 1114 |
|
| 1115 |
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); |
| 1116 |
}, nullptr); |
| 1117 |
ImGui::Image(rt.tex.getImGui(), logicalSize, {0, 1}, {1, 0}); |
| 1118 |
ImGui::GetWindowDrawList()->AddCallback(ImGui::GetPlatformIO().DrawCallback_ResetRenderState, nullptr); |
| 1119 |
ImGui::SetCursorPos(pos); |
| 1120 |
} |
| 1121 |
|
| 1122 |
} |
| 1123 |
|
| 1124 |
void ImGuiSettings::paintJoystick(MSXMotherBoard& motherBoard, JoystickManager& joystickManager) |
| 1125 |
{ |
| 1126 |
ImGui::SetNextWindowSize(gl::vec2{316, 323}, ImGuiCond_FirstUseEver); |
| 1127 |
im::Window("Configure MSX joysticks", &showConfigureJoystick, [&]{ |
| 1128 |
ImGui::SetNextItemWidth(13.0f * ImGui::GetFontSize()); |
| 1129 |
bool justChanged = im::Combo("Select joystick", joystickToGuiString(joystick).c_str(), [&]{ |
| 1130 |
for (const auto& j : xrange(6)) { |
| 1131 |
if (ImGui::Selectable(joystickToGuiString(j).c_str())) { |
| 1132 |
joystick = j; |
| 1133 |
} |
| 1134 |
} |
| 1135 |
}); |
| 1136 |
|
| 1137 |
const auto& controller = motherBoard.getMSXCommandController(); |
| 1138 |
auto* setting = dynamic_cast<StringSetting*>(controller.findSetting(settingName(joystick))); |
| 1139 |
if (!setting) return; |
| 1140 |
auto& interp = setting->getInterpreter(); |
| 1141 |
TclObject bindings = setting->getValue(); |
| 1142 |
|
| 1143 |
gl::vec2 scrnPos = ImGui::GetCursorScreenPos(); |
| 1144 |
gl::vec2 mouse = gl::vec2(ImGui::GetIO().MousePos) - scrnPos; |
| 1145 |
|
| 1146 |
|
| 1147 |
auto hovered = joystick < 2 ? msxjoystick::buttonsHovered(mouse) |
| 1148 |
: joystick < 4 ? joymega ::buttonsHovered(mouse) |
| 1149 |
: joyhandle ::buttonsHovered(mouse); |
| 1150 |
const auto numButtons = hovered.size(); |
| 1151 |
using SP = std::span<const zstring_view>; |
| 1152 |
auto keyNames = joystick < 2 ? SP{msxjoystick::keyNames} |
| 1153 |
: joystick < 4 ? SP{joymega ::keyNames} |
| 1154 |
: SP{joyhandle ::keyNames}; |
| 1155 |
auto buttonNames = joystick < 2 ? SP{msxjoystick::buttonNames} |
| 1156 |
: joystick < 4 ? SP{joymega ::buttonNames} |
| 1157 |
: SP{joyhandle ::buttonNames}; |
| 1158 |
|
| 1159 |
|
| 1160 |
std::optional<int> addAction; |
| 1161 |
std::optional<int> removeAction; |
| 1162 |
if (ImGui::IsMouseReleased(ImGuiMouseButton_Left) && !justChanged) { |
| 1163 |
for (auto i : xrange(numButtons)) { |
| 1164 |
if (hovered[i]) addAction = narrow<int>(i); |
| 1165 |
} |
| 1166 |
} |
| 1167 |
|
| 1168 |
ImGui::Dummy(joystick < 2 ? msxjoystick::boundingBox |
| 1169 |
: joystick < 4 ? joymega ::boundingBox |
| 1170 |
: joyhandle ::boundingBox); |
| 1171 |
|
| 1172 |
|
| 1173 |
bool anyWheel = false; |
| 1174 |
int hoveredRow = -1; |
| 1175 |
const auto& style = ImGui::GetStyle(); |
| 1176 |
auto textHeight = ImGui::GetTextLineHeight(); |
| 1177 |
float rowHeight = 2.0f * style.FramePadding.y + textHeight; |
| 1178 |
float bottomHeight = style.ItemSpacing.y + 2.0f * style.FramePadding.y + textHeight; |
| 1179 |
im::Table("##joystick-table", 2, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollX, {0.0f, -bottomHeight}, [&]{ |
| 1180 |
im::ID_for_range(numButtons, [&](int i) { |
| 1181 |
TclObject key(keyNames[i]); |
| 1182 |
bool wheel = key == "WHEEL"; |
| 1183 |
anyWheel |= wheel; |
| 1184 |
TclObject bindingList = bindings.getDictValue(interp, key); |
| 1185 |
if (wheel && analogBindings.empty()) { |
| 1186 |
for (auto str : bindingList) { |
| 1187 |
if (auto a = parseAnalogInput(str)) { |
| 1188 |
analogBindings.emplace_back(*a, 0.0f); |
| 1189 |
} |
| 1190 |
} |
| 1191 |
} |
| 1192 |
|
| 1193 |
if (ImGui::TableNextColumn()) { |
| 1194 |
auto pos = ImGui::GetCursorPos(); |
| 1195 |
ImGui::Selectable("##row", hovered[i], ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap, ImVec2(0, rowHeight)); |
| 1196 |
if (ImGui::IsItemHovered()) { |
| 1197 |
hoveredRow = i; |
| 1198 |
} |
| 1199 |
|
| 1200 |
ImGui::SetCursorPos(pos); |
| 1201 |
ImGui::AlignTextToFramePadding(); |
| 1202 |
ImGui::TextUnformatted(buttonNames[i]); |
| 1203 |
} |
| 1204 |
if (ImGui::TableNextColumn()) { |
| 1205 |
if (ImGui::Button("Add")) { |
| 1206 |
addAction = i; |
| 1207 |
} |
| 1208 |
ImGui::SameLine(); |
| 1209 |
auto numBindings = bindingList.size(); |
| 1210 |
im::Disabled(numBindings == 0, [&]{ |
| 1211 |
if (ImGui::Button("Remove")) { |
| 1212 |
if (numBindings == 1) { |
| 1213 |
bindings.setDictValue(interp, key, TclObject{}); |
| 1214 |
setting->setValue(bindings); |
| 1215 |
} else { |
| 1216 |
removeAction = i; |
| 1217 |
} |
| 1218 |
} |
| 1219 |
}); |
| 1220 |
ImGui::SameLine(); |
| 1221 |
if (numBindings == 0) { |
| 1222 |
ImGui::TextDisabledUnformatted("no bindings"); |
| 1223 |
} else { |
| 1224 |
size_t lastBindingIndex = numBindings - 1; |
| 1225 |
size_t bindingIndex = 0; |
| 1226 |
for (auto binding : bindingList) { |
| 1227 |
ImGui::TextUnformatted(binding); |
| 1228 |
simpleToolTip(toGuiString(wheel, binding, joystickManager)); |
| 1229 |
if (bindingIndex < lastBindingIndex) { |
| 1230 |
ImGui::SameLine(); |
| 1231 |
ImGui::TextUnformatted("|"sv); |
| 1232 |
ImGui::SameLine(); |
| 1233 |
} |
| 1234 |
++bindingIndex; |
| 1235 |
} |
| 1236 |
} |
| 1237 |
} |
| 1238 |
}); |
| 1239 |
}); |
| 1240 |
float analogValue = 0.0f; |
| 1241 |
if (anyWheel) { |
| 1242 |
for (auto& [b, v] : analogBindings) { |
| 1243 |
if (std::abs(v) > std::abs(analogValue)) { |
| 1244 |
analogValue = v; |
| 1245 |
} |
| 1246 |
} |
| 1247 |
} else { |
| 1248 |
analogBindings.clear(); |
| 1249 |
} |
| 1250 |
if (!analogBindings.empty()) { |
| 1251 |
initListener(); |
| 1252 |
} else { |
| 1253 |
deinitListenerIfIdle(); |
| 1254 |
} |
| 1255 |
|
| 1256 |
joystick < 2 ? msxjoystick::draw(scrnPos, hovered, hoveredRow) |
| 1257 |
: joystick < 4 ? joymega ::draw(scrnPos, hovered, hoveredRow) |
| 1258 |
: joyhandle ::draw(scrnPos, hovered, hoveredRow, analogValue); |
| 1259 |
|
| 1260 |
if (ImGui::Button("Default bindings...")) { |
| 1261 |
ImGui::OpenPopup("bindings"); |
| 1262 |
} |
| 1263 |
ImGui::SameLine(); |
| 1264 |
if (ImGui::Button("Calibrate joystick axes")) showCalibrateJoystick = true; |
| 1265 |
im::Popup("bindings", [&]{ |
| 1266 |
auto addOrSet = [&](auto getBindings) { |
| 1267 |
if (ImGui::MenuItem("Add to current bindings")) { |
| 1268 |
|
| 1269 |
auto newBindings = getBindings(); |
| 1270 |
for (auto k : xrange(int(numButtons))) { |
| 1271 |
TclObject key(keyNames[k]); |
| 1272 |
TclObject dstList = bindings.getDictValue(interp, key); |
| 1273 |
TclObject srcList = newBindings.getDictValue(interp, key); |
| 1274 |
|
| 1275 |
for (auto b : srcList) { |
| 1276 |
if (!contains(dstList, b)) { |
| 1277 |
dstList.addListElement(b); |
| 1278 |
} |
| 1279 |
} |
| 1280 |
bindings.setDictValue(interp, key, dstList); |
| 1281 |
} |
| 1282 |
setting->setValue(bindings); |
| 1283 |
} |
| 1284 |
if (ImGui::MenuItem("Replace current bindings")) { |
| 1285 |
setting->setValue(getBindings()); |
| 1286 |
} |
| 1287 |
}; |
| 1288 |
im::Menu("Keyboard", [&]{ |
| 1289 |
addOrSet([] { |
| 1290 |
return TclObject(TclObject::MakeDictTag{}, |
| 1291 |
"UP", makeTclList("keyb Up"), |
| 1292 |
"DOWN", makeTclList("keyb Down"), |
| 1293 |
"LEFT", makeTclList("keyb Left"), |
| 1294 |
"RIGHT", makeTclList("keyb Right"), |
| 1295 |
"A", makeTclList("keyb Space"), |
| 1296 |
"B", makeTclList("keyb M")); |
| 1297 |
}); |
| 1298 |
}); |
| 1299 |
for (auto joyId : joystickManager.getConnectedJoysticks()) { |
| 1300 |
im::Menu(tmpStrCat(joystickManager.getDisplayName(joyId), "##", joyId.raw()).c_str(), [&]{ |
| 1301 |
addOrSet([&]{ |
| 1302 |
return joystick < 2 ? MSXJoystick::getDefaultConfig(joyId, joystickManager) |
| 1303 |
: joystick < 4 ? JoyMega ::getDefaultConfig(joyId, joystickManager) |
| 1304 |
: JoyHandle ::getDefaultConfig(joyId, joystickManager); |
| 1305 |
}); |
| 1306 |
}); |
| 1307 |
} |
| 1308 |
}); |
| 1309 |
|
| 1310 |
|
| 1311 |
static constexpr auto addTitle = "Waiting for input"; |
| 1312 |
if (addAction) { |
| 1313 |
addPopupForKey = *addAction; |
| 1314 |
popupTimeout = 5.0f; |
| 1315 |
initListener(); |
| 1316 |
ImGui::OpenPopup(addTitle); |
| 1317 |
} |
| 1318 |
im::PopupModal(addTitle, nullptr, ImGuiWindowFlags_NoSavedSettings, [&]{ |
| 1319 |
auto close = [&]{ |
| 1320 |
ImGui::CloseCurrentPopup(); |
| 1321 |
addPopupForKey = unsigned(-1); |
| 1322 |
deinitListenerIfIdle(); |
| 1323 |
}; |
| 1324 |
if (addPopupForKey >= numButtons) { |
| 1325 |
close(); |
| 1326 |
return; |
| 1327 |
} |
| 1328 |
|
| 1329 |
ImGui::Text("Enter event for joystick button '%s'", buttonNames[addPopupForKey].c_str()); |
| 1330 |
ImGui::Text("Or press ESC to cancel. Timeout in %d seconds.", int(popupTimeout)); |
| 1331 |
|
| 1332 |
popupTimeout -= ImGui::GetIO().DeltaTime; |
| 1333 |
if (popupTimeout <= 0.0f) { |
| 1334 |
close(); |
| 1335 |
} |
| 1336 |
}); |
| 1337 |
|
| 1338 |
|
| 1339 |
if (removeAction) { |
| 1340 |
removePopupForKey = *removeAction; |
| 1341 |
ImGui::OpenPopup("remove"); |
| 1342 |
} |
| 1343 |
im::Popup("remove", [&]{ |
| 1344 |
auto close = [&]{ |
| 1345 |
ImGui::CloseCurrentPopup(); |
| 1346 |
removePopupForKey = unsigned(-1); |
| 1347 |
}; |
| 1348 |
if (removePopupForKey >= numButtons) { |
| 1349 |
close(); |
| 1350 |
return; |
| 1351 |
} |
| 1352 |
TclObject key(keyNames[removePopupForKey]); |
| 1353 |
TclObject bindingList = bindings.getDictValue(interp, key); |
| 1354 |
bool wheel = key == "WHEEL"; |
| 1355 |
|
| 1356 |
auto remove = size_t(-1); |
| 1357 |
size_t counter = 0; |
| 1358 |
for (const auto& b : bindingList) { |
| 1359 |
if (ImGui::Selectable(b.c_str())) { |
| 1360 |
remove = counter; |
| 1361 |
} |
| 1362 |
simpleToolTip(toGuiString(wheel, b, joystickManager)); |
| 1363 |
++counter; |
| 1364 |
} |
| 1365 |
if (remove != size_t(-1)) { |
| 1366 |
bindingList.removeListIndex(interp, remove); |
| 1367 |
bindings.setDictValue(interp, key, bindingList); |
| 1368 |
setting->setValue(bindings); |
| 1369 |
close(); |
| 1370 |
if (wheel) analogBindings.clear(); |
| 1371 |
} |
| 1372 |
|
| 1373 |
if (ImGui::Selectable("all bindings")) { |
| 1374 |
bindings.setDictValue(interp, key, TclObject{}); |
| 1375 |
setting->setValue(bindings); |
| 1376 |
close(); |
| 1377 |
if (wheel) analogBindings.clear(); |
| 1378 |
} |
| 1379 |
}); |
| 1380 |
}); |
| 1381 |
} |
| 1382 |
|
| 1383 |
static void verticalText(ImDrawList* drawList, gl::vec2 pos, ImU32 color, std::string_view text) |
| 1384 |
{ |
| 1385 |
auto vtxIdxStart = drawList->_VtxCurrentIdx; |
| 1386 |
drawList->AddText(pos, color, text.data(), text.data() + text.size()); |
| 1387 |
auto vtxIdxEnd = drawList->_VtxCurrentIdx; |
| 1388 |
|
| 1389 |
auto* verts = drawList->VtxBuffer.Data; |
| 1390 |
for (auto i = vtxIdxStart; i < vtxIdxEnd; ++i) { |
| 1391 |
auto offset = gl::vec2(verts[i].pos) - pos; |
| 1392 |
verts[i].pos = pos + gl::vec2{offset.y, -offset.x}; |
| 1393 |
} |
| 1394 |
} |
| 1395 |
|
| 1396 |
static float drawControlEdge(const char* id, gl::vec2 pos, float height, float dotY, float min, float max) |
| 1397 |
{ |
| 1398 |
ImGui::SetCursorScreenPos(pos - gl::vec2{2, 0}); |
| 1399 |
ImGui::InvisibleButton(id, gl::vec2(5, height)); |
| 1400 |
bool hovered = ImGui::IsItemHovered(); |
| 1401 |
bool active = ImGui::IsItemActive(); |
| 1402 |
auto current = pos.x; |
| 1403 |
if (active && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { |
| 1404 |
current = std::clamp(current + ImGui::GetIO().MouseDelta.x, min, max); |
| 1405 |
} |
| 1406 |
if (hovered) ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); |
| 1407 |
|
| 1408 |
auto colSep = ImGui::GetColorU32(ImGuiCol_Separator); |
| 1409 |
auto colActive = ImGui::GetColorU32(ImGuiCol_SeparatorActive); |
| 1410 |
auto colHover = ImGui::GetColorU32(ImGuiCol_SeparatorHovered); |
| 1411 |
auto* drawList = ImGui::GetWindowDrawList(); |
| 1412 |
auto color = active ? colActive : (hovered ? colHover : colSep); |
| 1413 |
drawList->AddLine(pos, pos + gl::vec2{0, height}, color, hovered ? 3.0f : 1.0f); |
| 1414 |
drawList->AddCircleFilled(pos + gl::vec2{0, dotY}, 10, color); |
| 1415 |
return current; |
| 1416 |
} |
| 1417 |
|
| 1418 |
static void generateCurvePoints( |
| 1419 |
std::function<float(float)> func, |
| 1420 |
std::function<gl::vec2(gl::vec2)> transform, |
| 1421 |
float xStart, float xEnd, |
| 1422 |
std::vector<ImVec2>& points) |
| 1423 |
{ |
| 1424 |
auto evalScreen = [&](float x) { return transform(gl::vec2{x, func(x)}); }; |
| 1425 |
|
| 1426 |
std::function<void(float, gl::vec2, float, gl::vec2)> subDivide = |
| 1427 |
[&](float x0, gl::vec2 p0, float x1, gl::vec2 p1) { |
| 1428 |
float xm = (x0 + x1) * 0.5f; |
| 1429 |
auto pMidCurve = evalScreen(xm); |
| 1430 |
auto pMidLineY = (p0.y + p1.y) * 0.5f; |
| 1431 |
float dy = pMidCurve.y - pMidLineY; |
| 1432 |
|
| 1433 |
if (std::abs(dy) > 2.0f) { |
| 1434 |
|
| 1435 |
subDivide(x0, p0, xm, pMidCurve); |
| 1436 |
subDivide(xm, pMidCurve, x1, p1); |
| 1437 |
} else { |
| 1438 |
|
| 1439 |
points.emplace_back(pMidCurve); |
| 1440 |
points.emplace_back(p1); |
| 1441 |
} |
| 1442 |
}; |
| 1443 |
auto pStart = evalScreen(xStart); |
| 1444 |
auto pEnd = evalScreen(xEnd); |
| 1445 |
points.emplace_back(pStart); |
| 1446 |
subDivide(xStart, pStart, xEnd, pEnd); |
| 1447 |
} |
| 1448 |
|
| 1449 |
void ImGuiSettings::paintCalibrate(JoystickManager& joystickManager) |
| 1450 |
{ |
| 1451 |
auto s = ImGui::GetFontSize(); |
| 1452 |
ImGui::SetNextWindowSize(gl::vec2{32, 25} * s, ImGuiCond_FirstUseEver); |
| 1453 |
im::Window("Calibrate joysticks", &showCalibrateJoystick, [&]{ |
| 1454 |
auto joysticks = joystickManager.getConnectedJoysticks(); |
| 1455 |
bool disabled = joysticks.empty(); |
| 1456 |
if (selectedJoystick >= joysticks.size()) selectedJoystick = 0; |
| 1457 |
|
| 1458 |
im::Disabled(disabled, [&]{ |
| 1459 |
auto* settings = disabled ? nullptr : joystickManager.getSettings(joysticks[selectedJoystick]); |
| 1460 |
im::Table("##table", 2, [&]{ |
| 1461 |
ImGui::TableSetupColumn("joystick", ImGuiTableColumnFlags_WidthFixed, 18.0f * s); |
| 1462 |
ImGui::TableSetupColumn("values"); |
| 1463 |
if (ImGui::TableNextColumn()) { |
| 1464 |
auto selectedName = disabled |
| 1465 |
? "none available" |
| 1466 |
: joystickManager.getDisplayName(joysticks[selectedJoystick]); |
| 1467 |
im::Combo("Joystick", selectedName.c_str(), [&]{ |
| 1468 |
for (auto i : xrange(joysticks.size())) { |
| 1469 |
auto name = joystickManager.getDisplayName(joysticks[selectedJoystick]); |
| 1470 |
if (ImGui::Selectable(tmpStrCat(name, "##", i).c_str()), selectedJoystick == i) { |
| 1471 |
selectedJoystick = unsigned(i); |
| 1472 |
} |
| 1473 |
} |
| 1474 |
}); |
| 1475 |
im::DisabledIndent(disabled, [&]{ |
| 1476 |
if (ImGui::Button("Restore default")) { |
| 1477 |
auto restore = [](Setting& setting) { |
| 1478 |
setting.setValue(setting.getDefaultValue()); |
| 1479 |
}; |
| 1480 |
assert(settings); |
| 1481 |
restore(*settings->deadZoneSetting); |
| 1482 |
restore(*settings->midPointSetting); |
| 1483 |
restore(*settings->saturationSetting); |
| 1484 |
} |
| 1485 |
}); |
| 1486 |
HelpMarkerNewLine( |
| 1487 |
"Set the translation curve from host joystick-axis raw-input to how openMSX sees it.\n" |
| 1488 |
"\n" |
| 1489 |
"Try changing the settings by dragging the 3 vertical lines.\n" |
| 1490 |
"\n" |
| 1491 |
"The most important setting is \"deadzone\". For most MSX input devices this is the " |
| 1492 |
"only setting that matters. Only for the FS-JH1 steering wheel the \"middle\" and " |
| 1493 |
"\"saturation\" settings can also be relevant."); |
| 1494 |
} |
| 1495 |
if (ImGui::TableNextColumn()) { |
| 1496 |
im::Disabled(disabled, [&]{ |
| 1497 |
float inner = settings ? settings->deadZoneSetting ->getFloat() : 25.0f; |
| 1498 |
float middle = settings ? settings->midPointSetting ->getFloat() : 75.0f; |
| 1499 |
float outer = settings ? settings->saturationSetting->getFloat() : 90.0f; |
| 1500 |
ImGui::SetNextItemWidth(2.0f * s); |
| 1501 |
if (ImGui::InputFloat("deadzone", &inner, {}, {}, "%.0f")) { |
| 1502 |
inner = std::clamp(inner, 0.0f, middle - 0.1f); |
| 1503 |
settings->deadZoneSetting->setFloat(inner); |
| 1504 |
} |
| 1505 |
ImGui::SetNextItemWidth(2.0f * s); |
| 1506 |
if (ImGui::InputFloat("midpoint", &middle, {}, {}, "%.0f")) { |
| 1507 |
middle = std::clamp(middle, inner + 0.1f, outer - 0.1f); |
| 1508 |
settings->midPointSetting->setFloat(middle); |
| 1509 |
} |
| 1510 |
ImGui::SetNextItemWidth(2.0f * s); |
| 1511 |
if (ImGui::InputFloat("saturation", &outer, {}, {}, "%.0f")) { |
| 1512 |
outer = std::clamp(outer, middle + 0.1f, 100.0f); |
| 1513 |
settings->saturationSetting->setFloat(outer); |
| 1514 |
} |
| 1515 |
}); |
| 1516 |
} |
| 1517 |
}); |
| 1518 |
|
| 1519 |
auto inner = settings ? (settings->deadZoneSetting ->getFloat() * (1.0f / 100.0f)) : 0.25f; |
| 1520 |
auto middle = settings ? (settings->midPointSetting ->getFloat() * (1.0f / 100.0f)) : 0.75f; |
| 1521 |
auto outer = settings ? (settings->saturationSetting->getFloat() * (1.0f / 100.0f)) : 0.90f; |
| 1522 |
|
| 1523 |
gl::vec2 pos = ImGui::GetCursorScreenPos(); |
| 1524 |
gl::vec2 size = ImGui::GetContentRegionAvail(); |
| 1525 |
auto* drawList = ImGui::GetWindowDrawList(); |
| 1526 |
auto white = getColor(imColor::TEXT); |
| 1527 |
auto gray = getColor(imColor::TEXT_DISABLED); |
| 1528 |
|
| 1529 |
std::string_view xLabel = "raw input"; |
| 1530 |
std::string_view yLabel = "output"; |
| 1531 |
auto xTextSize = ImGui::CalcTextSize(xLabel); |
| 1532 |
auto yTextSize = ImGui::CalcTextSize(yLabel); |
| 1533 |
auto unit = xTextSize.y * 0.5f; |
| 1534 |
auto origin = pos + gl::vec2{3 * unit, size.y - 3 * unit}; |
| 1535 |
drawList->AddText(pos + gl::vec2{(size.x - xTextSize.x) * 0.5f, size.y - xTextSize.y}, white, xLabel.data(), xLabel.data() + xLabel.size()); |
| 1536 |
verticalText(drawList, pos + gl::vec2{0, (size.y + yTextSize.x) * 0.5f}, white, yLabel); |
| 1537 |
|
| 1538 |
auto arrowRight = gl::vec2{pos.x + size.x, origin.y}; |
| 1539 |
auto arrowTop = gl::vec2{origin.x, pos.y}; |
| 1540 |
|
| 1541 |
auto graphMin = origin.x + 4 * unit; |
| 1542 |
auto graphMax = arrowRight.x - 4 * unit; |
| 1543 |
auto fullWidth = graphMax - graphMin; |
| 1544 |
|
| 1545 |
auto graphLeft = graphMin + inner * fullWidth; |
| 1546 |
auto graphMidX = graphMin + middle * fullWidth; |
| 1547 |
auto graphRight = graphMin + outer * fullWidth; |
| 1548 |
|
| 1549 |
auto graphTop = arrowTop.y + 2 * unit; |
| 1550 |
auto graphBottom = origin.y; |
| 1551 |
auto graphMidY = (graphTop + graphBottom) * 0.5f; |
| 1552 |
auto graphWidth = graphRight - graphLeft; |
| 1553 |
auto graphHeight = graphBottom - graphTop; |
| 1554 |
|
| 1555 |
drawList->AddLine({origin.x, graphTop}, {arrowRight.x, graphTop}, gray); |
| 1556 |
drawList->AddLine({origin.x, graphMidY}, {arrowRight.x, graphMidY}, gray); |
| 1557 |
drawList->AddLine({graphMin, origin.y}, {graphMin, origin.y + unit}, gray); |
| 1558 |
drawList->AddLine({graphMax, origin.y}, {graphMax, origin.y + unit}, gray); |
| 1559 |
|
| 1560 |
auto newLeft = drawControlEdge("##inner", {graphLeft, graphTop}, graphHeight, graphHeight, graphMin, graphMidX - 1); |
| 1561 |
auto newMid = drawControlEdge("##middle", {graphMidX, graphTop}, graphHeight, graphHeight * 0.5f, graphLeft + 1, graphRight - 1); |
| 1562 |
auto newRight = drawControlEdge("##outer", {graphRight, graphTop}, graphHeight, 0, graphMidX + 1, graphMax); |
| 1563 |
bool changed = false; |
| 1564 |
if (newLeft != graphLeft) { |
| 1565 |
changed = true; |
| 1566 |
auto old = inner; |
| 1567 |
inner = std::clamp(((newLeft - graphMin) / fullWidth), 0.0f, 1.0f); |
| 1568 |
middle = outer - ((outer - middle) * (outer - inner) / (outer - old)); |
| 1569 |
} |
| 1570 |
if (newMid != graphMidX) { |
| 1571 |
changed = true; |
| 1572 |
middle = std::clamp(((newMid - graphMin) / fullWidth), 0.0f, 1.0f); |
| 1573 |
} |
| 1574 |
if (newRight != graphRight) { |
| 1575 |
changed = true; |
| 1576 |
auto old = outer; |
| 1577 |
outer = std::clamp(((newRight - graphMin) / fullWidth), 0.0f, 1.0f); |
| 1578 |
middle = inner + ((middle - inner) * (outer - inner) / (old - inner)); |
| 1579 |
} |
| 1580 |
if (changed) { |
| 1581 |
assert(settings); |
| 1582 |
auto innerInt = inner * 100.0f; |
| 1583 |
auto middleInt = middle * 100.0f; |
| 1584 |
auto outerInt = outer * 100.0f; |
| 1585 |
innerInt = std::clamp(innerInt, 0.0f, middleInt - 0.1f); |
| 1586 |
middleInt = std::clamp(middleInt, innerInt + 0.1f, outerInt - 0.1f); |
| 1587 |
outerInt = std::clamp(outerInt, middleInt + 0.1f, 100.0f); |
| 1588 |
settings->deadZoneSetting ->setFloat(innerInt); |
| 1589 |
settings->midPointSetting ->setFloat(middleInt); |
| 1590 |
settings->saturationSetting->setFloat(outerInt); |
| 1591 |
} |
| 1592 |
auto f = std::log(0.5f) / std::log((middle - inner) / (outer - inner)); |
| 1593 |
|
| 1594 |
drawList->AddLine(arrowRight, origin - unit * gl::vec2{2, 0}, white, 2.0f); |
| 1595 |
drawList->AddLine(arrowRight, arrowRight + unit * gl::vec2{-1, -1}, white, 2.0f); |
| 1596 |
drawList->AddLine(arrowRight, arrowRight + unit * gl::vec2{-1, 1}, white, 2.0f); |
| 1597 |
|
| 1598 |
drawList->AddLine(arrowTop, origin + unit * gl::vec2{0, 2}, white, 2.0f); |
| 1599 |
drawList->AddLine(arrowTop, arrowTop + unit * gl::vec2{-1, 1}, white, 2.0f); |
| 1600 |
drawList->AddLine(arrowTop, arrowTop + unit * gl::vec2{ 1, 1}, white, 2.0f); |
| 1601 |
|
| 1602 |
std::vector<ImVec2> points; |
| 1603 |
points.reserve(40); |
| 1604 |
points.emplace_back(origin); |
| 1605 |
generateCurvePoints( |
| 1606 |
[&](float x) { return std::pow(x, f); }, |
| 1607 |
[&](gl::vec2 p) { return p * gl::vec2{graphWidth, -graphHeight} + gl::vec2{graphLeft, origin.y}; }, |
| 1608 |
0.0f, 1.0f, points); |
| 1609 |
points.emplace_back(arrowRight.x, graphTop); |
| 1610 |
drawList->AddPolyline(points.data(), points.size(), white, 4.0f); |
| 1611 |
|
| 1612 |
auto numAxes = disabled ? 0 : joystickManager.getNumAxes(joysticks[selectedJoystick]).value_or(0); |
| 1613 |
for (auto axis : xrange(int(numAxes))) { |
| 1614 |
auto [r, g, b] = generateDistinctColor(axis); |
| 1615 |
ImU32 color = ImColor{r, g, b}; |
| 1616 |
|
| 1617 |
auto axisValue = joystickManager.getAxis(joysticks[selectedJoystick], axis).value_or(0); |
| 1618 |
auto input = float(std::abs(axisValue)) / 32768.0f; |
| 1619 |
auto output = pow(std::clamp((input - inner) / (outer - inner), 0.0f, 1.0f), f); |
| 1620 |
|
| 1621 |
|
| 1622 |
auto cross = gl::vec2{graphMin + input * fullWidth, origin.y - output * graphHeight}; |
| 1623 |
if (output > 0.0f) { |
| 1624 |
drawList->AddLine({origin.x, cross.y}, gl::vec2{origin.x, cross.y} + unit * gl::vec2{1, -1}, color, 2.0f); |
| 1625 |
drawList->AddLine({origin.x, cross.y}, gl::vec2{origin.x, cross.y} + unit * gl::vec2{1, 1}, color, 2.0f); |
| 1626 |
} |
| 1627 |
drawList->AddLine({cross.x, origin.y}, gl::vec2{cross.x, origin.y} + unit * gl::vec2{-1, -1}, color, 2.0f); |
| 1628 |
drawList->AddLine({cross.x, origin.y}, gl::vec2{cross.x, origin.y} + unit * gl::vec2{ 1, -1}, color, 2.0f); |
| 1629 |
drawList->AddLine(cross + unit * gl::vec2{-1, -1}, cross + unit * gl::vec2{1, 1}, color, 2.0f); |
| 1630 |
drawList->AddLine(cross + unit * gl::vec2{-1, 1}, cross + unit * gl::vec2{1, -1}, color, 2.0f); |
| 1631 |
|
| 1632 |
|
| 1633 |
auto lPos = gl::vec2{graphMin, graphTop + float(axis) * unit * 2.5f + unit}; |
| 1634 |
drawList->AddRectFilled(lPos, lPos + gl::vec2{2.0f * unit}, color); |
| 1635 |
auto text = tmpStrCat("axis ", axis); |
| 1636 |
drawList->AddText(lPos + gl::vec2{3 * unit, 0.0f}, white, text.data(), text.data() + text.size()); |
| 1637 |
} |
| 1638 |
}); |
| 1639 |
}); |
| 1640 |
} |
| 1641 |
|
| 1642 |
void ImGuiSettings::paintFont() |
| 1643 |
{ |
| 1644 |
im::Window("Select font", &showFont, [&]{ |
| 1645 |
auto selectFilename = [&](FilenameSetting& fileSetting, IntegerSetting& faceSetting, zstring_view name, float width) { |
| 1646 |
auto currentFile = fileSetting.getString(); |
| 1647 |
auto currentFace = faceSetting.getInt(); |
| 1648 |
|
| 1649 |
ImGui::SetNextItemWidth(width); |
| 1650 |
im::Combo(tmpStrCat("##", fileSetting.getBaseName()).c_str(), name.c_str(), [&]{ |
| 1651 |
for (const auto& font : getAvailableFonts()) { |
| 1652 |
bool isSelected = currentFile == font.filename && |
| 1653 |
currentFace == font.faceIndex; |
| 1654 |
if (ImGui::Selectable(font.displayName.c_str(), isSelected)) { |
| 1655 |
fileSetting.setString(font.filename); |
| 1656 |
faceSetting.setInt(font.faceIndex); |
| 1657 |
} |
| 1658 |
} |
| 1659 |
}); |
| 1660 |
}; |
| 1661 |
auto selectSize = [](IntegerSetting& setting) { |
| 1662 |
auto display = [](int s) { return strCat(s); }; |
| 1663 |
auto current = setting.getInt(); |
| 1664 |
ImGui::SetNextItemWidth(4.0f * ImGui::GetFontSize()); |
| 1665 |
im::Combo(tmpStrCat("##", setting.getBaseName()).c_str(), display(current).c_str(), [&]{ |
| 1666 |
for (int size : {9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 22, 24, 26, 28, 30, 32}) { |
| 1667 |
if (ImGui::Selectable(display(size).c_str(), current == size)) { |
| 1668 |
setting.setInt(size); |
| 1669 |
} |
| 1670 |
} |
| 1671 |
}); |
| 1672 |
}; |
| 1673 |
|
| 1674 |
auto pos = ImGui::CalcTextSize("Monospace").x + 2.0f * ImGui::GetStyle().ItemSpacing.x; |
| 1675 |
auto width = 12.0f * ImGui::GetFontSize(); |
| 1676 |
|
| 1677 |
ImGui::AlignTextToFramePadding(); |
| 1678 |
ImGui::TextUnformatted("Normal"); |
| 1679 |
ImGui::SameLine(pos); |
| 1680 |
selectFilename(manager.fontPropFilename, manager.fontPropIndex, manager.fontPropName, width); |
| 1681 |
ImGui::SameLine(); |
| 1682 |
selectSize(manager.fontPropSize); |
| 1683 |
HelpMarker("You can install more fonts by copying .ttf file(s) to your \"<openmsx>/share/skins\" directory."); |
| 1684 |
|
| 1685 |
ImGui::AlignTextToFramePadding(); |
| 1686 |
ImGui::TextUnformatted("Monospace"); |
| 1687 |
ImGui::SameLine(pos); |
| 1688 |
im::Font(manager.fontMono, [&]{ |
| 1689 |
selectFilename(manager.fontMonoFilename, manager.fontMonoIndex, manager.fontMonoName, width); |
| 1690 |
}); |
| 1691 |
ImGui::SameLine(); |
| 1692 |
selectSize(manager.fontMonoSize); |
| 1693 |
HelpMarker("Some GUI elements (e.g. the console) require a monospaced font."); |
| 1694 |
}); |
| 1695 |
} |
| 1696 |
|
| 1697 |
[[nodiscard]] static std::string formatShortcutWithAnnotations(const Shortcuts::Shortcut& shortcut) |
| 1698 |
{ |
| 1699 |
auto result = getKeyChordName(shortcut.keyChord); |
| 1700 |
|
| 1701 |
if (shortcut.type == Shortcuts::Type::GLOBAL) result += ", global"; |
| 1702 |
return result; |
| 1703 |
} |
| 1704 |
|
| 1705 |
[[nodiscard]] static gl::vec2 buttonSize(std::string_view text, float defaultSize_) |
| 1706 |
{ |
| 1707 |
const auto& style = ImGui::GetStyle(); |
| 1708 |
auto textSize = ImGui::CalcTextSize(text).x + 2.0f * style.FramePadding.x; |
| 1709 |
auto defaultSize = ImGui::GetFontSize() * defaultSize_; |
| 1710 |
return {std::max(textSize, defaultSize), 0.0f}; |
| 1711 |
} |
| 1712 |
|
| 1713 |
void ImGuiSettings::paintEditShortcut() |
| 1714 |
{ |
| 1715 |
using enum Shortcuts::Type; |
| 1716 |
|
| 1717 |
bool editShortcutWindow = editShortcutId != Shortcuts::ID::INVALID; |
| 1718 |
if (!editShortcutWindow) return; |
| 1719 |
|
| 1720 |
im::Window("Edit shortcut", &editShortcutWindow, ImGuiWindowFlags_AlwaysAutoResize, [&]{ |
| 1721 |
auto& shortcuts = manager.getShortcuts(); |
| 1722 |
auto shortcut = shortcuts.getShortcut(editShortcutId); |
| 1723 |
|
| 1724 |
im::Table("table", 2, [&]{ |
| 1725 |
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); |
| 1726 |
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); |
| 1727 |
|
| 1728 |
if (ImGui::TableNextColumn()) { |
| 1729 |
ImGui::AlignTextToFramePadding(); |
| 1730 |
ImGui::TextUnformatted("key"); |
| 1731 |
} |
| 1732 |
static constexpr auto waitKeyTitle = "Waiting for key"; |
| 1733 |
if (ImGui::TableNextColumn()) { |
| 1734 |
auto text = getKeyChordName(shortcut.keyChord); |
| 1735 |
if (ImGui::Button(text.c_str(), buttonSize(text, 4.0f))) { |
| 1736 |
popupTimeout = 10.0f; |
| 1737 |
centerNextWindowOverCurrent(); |
| 1738 |
ImGui::OpenPopup(waitKeyTitle); |
| 1739 |
} |
| 1740 |
} |
| 1741 |
bool isOpen = true; |
| 1742 |
im::PopupModal(waitKeyTitle, &isOpen, ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize, [&]{ |
| 1743 |
ImGui::Text("Enter key combination for shortcut '%s'", |
| 1744 |
Shortcuts::getShortcutDescription(editShortcutId).c_str()); |
| 1745 |
ImGui::Text("Timeout in %d seconds.", int(popupTimeout)); |
| 1746 |
|
| 1747 |
popupTimeout -= ImGui::GetIO().DeltaTime; |
| 1748 |
if (!isOpen || popupTimeout <= 0.0f) { |
| 1749 |
ImGui::CloseCurrentPopup(); |
| 1750 |
} |
| 1751 |
if (auto keyChord = getCurrentlyPressedKeyChord(); keyChord != ImGuiKey_None) { |
| 1752 |
shortcut.keyChord = keyChord; |
| 1753 |
shortcuts.setShortcut(editShortcutId, shortcut); |
| 1754 |
editShortcutWindow = false; |
| 1755 |
ImGui::CloseCurrentPopup(); |
| 1756 |
} |
| 1757 |
}); |
| 1758 |
|
| 1759 |
if (shortcut.type == one_of(LOCAL, GLOBAL)) { |
| 1760 |
if (ImGui::TableNextColumn()) { |
| 1761 |
ImGui::AlignTextToFramePadding(); |
| 1762 |
ImGui::TextUnformatted("global"); |
| 1763 |
} |
| 1764 |
if (ImGui::TableNextColumn()) { |
| 1765 |
bool global = shortcut.type == GLOBAL; |
| 1766 |
if (ImGui::Checkbox("##global", &global)) { |
| 1767 |
shortcut.type = global ? GLOBAL : LOCAL; |
| 1768 |
shortcuts.setShortcut(editShortcutId, shortcut); |
| 1769 |
} |
| 1770 |
simpleToolTip( |
| 1771 |
"Global shortcuts react when any GUI window has focus.\n" |
| 1772 |
"Local shortcuts only react when the specific GUI window has focus.\n"sv); |
| 1773 |
} |
| 1774 |
} |
| 1775 |
}); |
| 1776 |
ImGui::Separator(); |
| 1777 |
const auto& defaultShortcut = Shortcuts::getDefaultShortcut(editShortcutId); |
| 1778 |
im::Disabled(shortcut == defaultShortcut, [&]{ |
| 1779 |
if (ImGui::Button("Restore default")) { |
| 1780 |
shortcuts.setShortcut(editShortcutId, defaultShortcut); |
| 1781 |
editShortcutWindow = false; |
| 1782 |
} |
| 1783 |
simpleToolTip([&]{ return formatShortcutWithAnnotations(defaultShortcut); }); |
| 1784 |
}); |
| 1785 |
|
| 1786 |
ImGui::SameLine(); |
| 1787 |
im::Disabled(shortcut == Shortcuts::Shortcut{}, [&]{ |
| 1788 |
if (ImGui::Button("Set None")) { |
| 1789 |
shortcuts.setShortcut(editShortcutId, Shortcuts::Shortcut{}); |
| 1790 |
editShortcutWindow = false; |
| 1791 |
} |
| 1792 |
simpleToolTip("Set no binding for this shortcut"sv); |
| 1793 |
}); |
| 1794 |
}); |
| 1795 |
if (!editShortcutWindow) editShortcutId = Shortcuts::ID::INVALID; |
| 1796 |
} |
| 1797 |
|
| 1798 |
void ImGuiSettings::paintShortcut() |
| 1799 |
{ |
| 1800 |
im::Window("Edit shortcuts", &showShortcut, [&]{ |
| 1801 |
int flags = ImGuiTableFlags_Resizable |
| 1802 |
| ImGuiTableFlags_RowBg |
| 1803 |
| ImGuiTableFlags_NoBordersInBodyUntilResize |
| 1804 |
| ImGuiTableFlags_SizingStretchProp; |
| 1805 |
im::Table("table", 2, flags, {-FLT_MIN, 0.0f}, [&]{ |
| 1806 |
ImGui::TableSetupColumn("description"); |
| 1807 |
ImGui::TableSetupColumn("key"); |
| 1808 |
|
| 1809 |
const auto& shortcuts = manager.getShortcuts(); |
| 1810 |
im::ID_for_range(std::to_underlying(Shortcuts::ID::NUM), [&](int i) { |
| 1811 |
auto id = static_cast<Shortcuts::ID>(i); |
| 1812 |
auto shortcut = shortcuts.getShortcut(id); |
| 1813 |
|
| 1814 |
if (ImGui::TableNextColumn()) { |
| 1815 |
ImGui::AlignTextToFramePadding(); |
| 1816 |
ImGui::TextUnformatted(Shortcuts::getShortcutDescription(id)); |
| 1817 |
} |
| 1818 |
if (ImGui::TableNextColumn()) { |
| 1819 |
auto text = formatShortcutWithAnnotations(shortcut); |
| 1820 |
if (ImGui::Button(text.c_str(), buttonSize(text, 9.0f))) { |
| 1821 |
editShortcutId = id; |
| 1822 |
centerNextWindowOverCurrent(); |
| 1823 |
} |
| 1824 |
} |
| 1825 |
}); |
| 1826 |
}); |
| 1827 |
}); |
| 1828 |
paintEditShortcut(); |
| 1829 |
} |
| 1830 |
|
| 1831 |
void ImGuiSettings::paint(MSXMotherBoard* motherBoard) |
| 1832 |
{ |
| 1833 |
if (selectedStyle < 0) { |
| 1834 |
|
| 1835 |
selectedStyle = 0; |
| 1836 |
setStyle(); |
| 1837 |
} |
| 1838 |
auto& joystickManager = manager.getReactor().getInputEventGenerator().getJoystickManager(); |
| 1839 |
if (motherBoard && showConfigureJoystick) { |
| 1840 |
paintJoystick(*motherBoard, joystickManager); |
| 1841 |
} else { |
| 1842 |
deinitListener(); |
| 1843 |
} |
| 1844 |
if (showCalibrateJoystick) paintCalibrate(joystickManager); |
| 1845 |
if (showFont) paintFont(); |
| 1846 |
if (showShortcut) paintShortcut(); |
| 1847 |
} |
| 1848 |
|
| 1849 |
std::span<const ImGuiSettings::FontInfo> ImGuiSettings::getAvailableFonts() |
| 1850 |
{ |
| 1851 |
if (availableFonts.empty()) { |
| 1852 |
|
| 1853 |
std::vector<std::string> files; |
| 1854 |
const auto& context = systemFileContext(); |
| 1855 |
for (const auto& path : context.getPaths()) { |
| 1856 |
foreach_file(FileOperations::join(path, "skins"), [&](const std::string& , std::string_view name) { |
| 1857 |
auto nameGz = name.ends_with(".gz") ? name.substr(0, name.size() - 3) : name; |
| 1858 |
if (nameGz.ends_with(".ttf") || nameGz.ends_with(".ttc") || nameGz.ends_with(".otf")) { |
| 1859 |
files.emplace_back(name); |
| 1860 |
} |
| 1861 |
}); |
| 1862 |
} |
| 1863 |
|
| 1864 |
std::ranges::sort(files); |
| 1865 |
auto u = std::ranges::unique(files); |
| 1866 |
files.erase(u.begin(), u.end()); |
| 1867 |
|
| 1868 |
|
| 1869 |
for (const auto& file : files) { |
| 1870 |
try { |
| 1871 |
auto resolved = context.resolve(FileOperations::join("skins", file)); |
| 1872 |
auto fontData = File(resolved).mmap<uint8_t>(); |
| 1873 |
for (auto i : xrange(getNumFaces(manager.freeTypeLibrary, fontData))) { |
| 1874 |
FontInfo info; |
| 1875 |
info.filename = file; |
| 1876 |
info.displayName = manager.getFontDisplayName(fontData, i, file); |
| 1877 |
info.faceIndex = narrow<int>(i); |
| 1878 |
availableFonts.emplace_back(std::move(info)); |
| 1879 |
} |
| 1880 |
} catch (MSXException&) { |
| 1881 |
|
| 1882 |
} |
| 1883 |
} |
| 1884 |
} |
| 1885 |
return availableFonts; |
| 1886 |
} |
| 1887 |
|
| 1888 |
bool ImGuiSettings::signalEvent(const Event& event) |
| 1889 |
{ |
| 1890 |
auto getJoyDeadThreshold = [&](JoystickId joyId) { |
| 1891 |
const auto& joyMan = manager.getReactor().getInputEventGenerator().getJoystickManager(); |
| 1892 |
return joyMan.getJoyDeadThreshold(joyId); |
| 1893 |
}; |
| 1894 |
auto getJoyValue = [&](JoystickId joyId, const JoystickAxisMotionEvent& e) { |
| 1895 |
const auto& joyMan = manager.getReactor().getInputEventGenerator().getJoystickManager(); |
| 1896 |
return joyMan.getJoyValue(joyId, e); |
| 1897 |
}; |
| 1898 |
|
| 1899 |
for (auto& [binding, value] : analogBindings) { |
| 1900 |
if (auto v = match(binding, event, getJoyValue)) { |
| 1901 |
value = *v; |
| 1902 |
} |
| 1903 |
} |
| 1904 |
|
| 1905 |
using SP = std::span<const zstring_view>; |
| 1906 |
auto keyNames = joystick < 2 ? SP{msxjoystick::keyNames} |
| 1907 |
: joystick < 4 ? SP{joymega ::keyNames} |
| 1908 |
: SP{joyhandle ::keyNames}; |
| 1909 |
if (const auto numButtons = keyNames.size(); addPopupForKey >= numButtons) { |
| 1910 |
deinitListenerIfIdle(); |
| 1911 |
return false; |
| 1912 |
} |
| 1913 |
|
| 1914 |
bool escape = false; |
| 1915 |
if (const auto* keyDown = get_event_if<KeyDownEvent>(event)) { |
| 1916 |
escape = keyDown->getKeyCode() == SDLK_ESCAPE; |
| 1917 |
} |
| 1918 |
if (!escape) { |
| 1919 |
TclObject key(keyNames[addPopupForKey]); |
| 1920 |
bool wheel = key == "WHEEL"; |
| 1921 |
|
| 1922 |
std::string bs; |
| 1923 |
if (wheel) { |
| 1924 |
auto b = captureAnalogInput(event, getJoyDeadThreshold); |
| 1925 |
if (!b) return true; |
| 1926 |
bs = toString(*b); |
| 1927 |
} else { |
| 1928 |
auto b = captureBooleanInput(event, getJoyDeadThreshold); |
| 1929 |
if (!b) return true; |
| 1930 |
bs = toString(*b); |
| 1931 |
} |
| 1932 |
|
| 1933 |
auto* motherBoard = manager.getReactor().getMotherBoard(); |
| 1934 |
if (!motherBoard) return true; |
| 1935 |
const auto& controller = motherBoard->getMSXCommandController(); |
| 1936 |
auto* setting = dynamic_cast<StringSetting*>(controller.findSetting(settingName(joystick))); |
| 1937 |
if (!setting) return true; |
| 1938 |
auto& interp = setting->getInterpreter(); |
| 1939 |
|
| 1940 |
TclObject bindings = setting->getValue(); |
| 1941 |
TclObject bindingList = bindings.getDictValue(interp, key); |
| 1942 |
|
| 1943 |
if (!contains(bindingList, bs)) { |
| 1944 |
bindingList.addListElement(bs); |
| 1945 |
bindings.setDictValue(interp, key, bindingList); |
| 1946 |
setting->setValue(bindings); |
| 1947 |
if (wheel) analogBindings.clear(); |
| 1948 |
} |
| 1949 |
} |
| 1950 |
|
| 1951 |
addPopupForKey = unsigned(-1); |
| 1952 |
deinitListenerIfIdle(); |
| 1953 |
return true; |
| 1954 |
} |
| 1955 |
|
| 1956 |
void ImGuiSettings::initListener() |
| 1957 |
{ |
| 1958 |
if (listening) return; |
| 1959 |
listening = true; |
| 1960 |
|
| 1961 |
auto& distributor = manager.getReactor().getEventDistributor(); |
| 1962 |
|
| 1963 |
using enum EventType; |
| 1964 |
for (auto type : {KEY_DOWN, MOUSE_BUTTON_DOWN, |
| 1965 |
JOY_BUTTON_DOWN, JOY_HAT, JOY_AXIS_MOTION}) { |
| 1966 |
distributor.registerEventListener(type, *this); |
| 1967 |
} |
| 1968 |
} |
| 1969 |
|
| 1970 |
void ImGuiSettings::deinitListenerIfIdle() |
| 1971 |
{ |
| 1972 |
if (analogBindings.empty() && (addPopupForKey == unsigned(-1))) { |
| 1973 |
deinitListener(); |
| 1974 |
} |
| 1975 |
} |
| 1976 |
|
| 1977 |
void ImGuiSettings::deinitListener() |
| 1978 |
{ |
| 1979 |
if (!listening) return; |
| 1980 |
listening = false; |
| 1981 |
|
| 1982 |
addPopupForKey = unsigned(-1); |
| 1983 |
analogBindings.clear(); |
| 1984 |
|
| 1985 |
auto& distributor = manager.getReactor().getEventDistributor(); |
| 1986 |
using enum EventType; |
| 1987 |
for (auto type : {JOY_AXIS_MOTION, JOY_HAT, JOY_BUTTON_DOWN, |
| 1988 |
MOUSE_BUTTON_DOWN, KEY_DOWN}) { |
| 1989 |
distributor.unregisterEventListener(type, *this); |
| 1990 |
} |
| 1991 |
} |
| 1992 |
|
| 1993 |
} |