33 void renderBool(
const std::string& key, nlohmann::json& value,
const std::string& sectionName) {
34 bool val = value.get<
bool>();
35 if (ImGui::Checkbox(key.c_str(), &val)) {
37 LOG_DEBUG(
"Updated " + sectionName +
"." + key +
" to " + std::to_string(val));
41 void renderFloat(
const std::string& key, nlohmann::json& value,
const std::string& sectionName,
42 float minVal = 0.0f,
float maxVal = 1.0f,
const char* format =
"%.2f") {
43 float val = value.get<
float>();
44 if (key ==
"Title weight") {
45 ImGui::Text(
"Metadata matchmaking settings:");
46 minVal = 0.2f; maxVal = 0.8f;
47 }
else if (key ==
"Year weight") {
48 minVal = 0.0f; maxVal = 0.4f;
49 }
else if (key ==
"Manufacturer weight") {
50 minVal = 0.0f; maxVal = 0.3f;
51 }
else if (key ==
"ROM weight") {
52 minVal = 0.0f; maxVal = 0.5f;
53 }
else if (key ==
"Title similarity") {
54 minVal = 0.3f; maxVal = 0.8f;
55 }
else if (key ==
"Overall confidence") {
56 minVal = 0.4f; maxVal = 0.9f;
57 }
else if (key ==
"DPI Scale") {
58 minVal = 0.5f; maxVal = 3.0f;
59 }
else if (key.find(
"Alpha") != std::string::npos || key ==
"scrollbarLength" ||
60 key ==
"metadataPanelWidth" || key ==
"metadataPanelHeight") {
61 minVal = 0.0f; maxVal = 1.0f;
62 }
else if (key ==
"arrowHintWidth" || key ==
"arrowHintHeight") {
63 minVal = 0.0f; maxVal = 200.0f;
64 }
else if (key ==
"arrowThickness" || key ==
"arrowGlow") {
65 minVal = 0.0f; maxVal = 10.0f;
66 }
else if (key ==
"scrollbarWidth" || key ==
"thumbWidth") {
67 minVal = 0.0f; maxVal = 50.0f;
68 }
else if (key ==
"masterVol" || key ==
"mediaAudioVol" || key ==
"tableMusicVol" ||
69 key ==
"interfaceAudioVol" || key ==
"interfaceAmbienceVol") {
70 minVal = 0.0f; maxVal = 100.0f;
71 }
else if (key ==
"configUIWidth" || key ==
"configUIHeight") {
72 minVal = 0.1f; maxVal = 1.0f;
74 if (ImGui::SliderFloat(key.c_str(), &val, minVal, maxVal, format)) {
76 LOG_DEBUG(
"Updated " + sectionName +
"." + key +
" to " + std::to_string(val));
80 void renderInt(
const std::string& key, nlohmann::json& value,
const std::string& sectionName,
81 int minVal = 0,
int maxVal = 10000) {
82 int val = value.get<
int>();
83 if (key.find(
"WindowWidth") != std::string::npos || key.find(
"WindowHeight") != std::string::npos ||
84 key.find(
"MediaWidth") != std::string::npos || key.find(
"MediaHeight") != std::string::npos) {
85 minVal = 0; maxVal = 3840;
86 }
else if (key ==
"fontSize") {
87 minVal = 10; maxVal = 60;
88 }
else if (key ==
"screenshotWait") {
89 minVal = 0; maxVal = 60;
91 if (ImGui::InputInt(key.c_str(), &val)) {
92 val = std::clamp(val, minVal, maxVal);
94 LOG_DEBUG(
"Updated " + sectionName +
"." + key +
" to " + std::to_string(val));
98 void renderString(
const std::string& key, nlohmann::json& value,
const std::string& sectionName) {
99 std::string val = value.get<std::string>();
101 strncpy(buffer, val.c_str(),
sizeof(buffer) - 1);
102 buffer[
sizeof(buffer) - 1] =
'\0';
103 if (ImGui::InputText(key.c_str(), buffer,
sizeof(buffer))) {
104 value = std::string(buffer);
105 LOG_DEBUG(
"Updated " + sectionName +
"." + key +
" to " + buffer);
109 void renderColor(
const std::string& key, nlohmann::json& value,
const std::string& sectionName) {
111 value[0].get<
float>() / 255.0f,
112 value[1].get<float>() / 255.0f,
113 value[2].get<
float>() / 255.0f,
114 value[3].get<float>() / 255.0f
116 if (ImGui::ColorEdit4(key.c_str(), color)) {
117 value = nlohmann::json{
118 static_cast<int>(color[0] * 255.0f),
119 static_cast<int>(color[1] * 255.0f),
120 static_cast<int>(color[2] * 255.0f),
121 static_cast<int>(color[3] * 255.0f)
123 LOG_DEBUG(
"Updated " + sectionName +
"." + key +
" to [" +
124 std::to_string(value[0].get<int>()) +
"," +
125 std::to_string(value[1].get<int>()) +
"," +
126 std::to_string(value[2].get<int>()) +
"," +
127 std::to_string(value[3].get<int>()) +
"]");
131 void renderRotation(
const std::string& key, nlohmann::json& value,
const std::string& sectionName) {
132 int val = value.get<
int>();
133 static std::unordered_map<std::string, int> lastLoggedValues;
134 int currentValue = snapToStep(val);
135 if (ImGui::SliderInt(key.c_str(), ¤tValue, 0, 360,
"%d°")) {
136 int snappedValue = snapToStep(currentValue);
137 if (snappedValue != lastLoggedValues[key]) {
138 value = snappedValue;
139 lastLoggedValues[key] = snappedValue;
140 LOG_DEBUG(
"Updated " + sectionName +
"." + key +
" to " + std::to_string(snappedValue) +
"°");
145 void renderKeybind(
const std::string& key, nlohmann::json& value,
const std::string& sectionName,
146 bool& isCapturing, std::string& capturingKeyName) {
147 if (!value.is_string()) {
148 LOG_DEBUG(
"Invalid type for keybind " + key +
", expected string, got " + value.type_name());
151 std::string currentBind = value.get<std::string>();
152 std::string buttonLabel =
"[" + key +
": " + (currentBind.empty() ?
"Unbound" : currentBind) +
"]";
153 if (ImGui::Button(buttonLabel.c_str(), ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
156 capturingKeyName = key;
157 LOG_DEBUG(
"Started capturing key for " + sectionName +
"." + key);
160 if (isCapturing && capturingKeyName == key) {
162 ImGui::Text(
"Press a key or joystick input to bind to %s...", capturingKeyName.c_str());
166 void renderPathOrExecutable(
const std::string& key, nlohmann::json& value,
const std::string& sectionName,
167 ImGuiFileDialog* fileDialog,
bool& isDialogOpen, std::string& dialogKey) {
168 std::string val = value.get<std::string>();
170 strncpy(buffer, val.c_str(),
sizeof(buffer) - 1);
171 buffer[
sizeof(buffer) - 1] =
'\0';
172 ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x - 60);
173 if (ImGui::InputText(
"##value", buffer,
sizeof(buffer))) {
174 value = std::string(buffer);
175 LOG_DEBUG(
"Updated " + sectionName +
"." + key +
" to " + buffer);
177 ImGui::PopItemWidth();
179 if (ImGui::Button(
"Browse", ImVec2(50, 0))) {
180 LOG_DEBUG(
"Browse button clicked for " + key);
181 IGFD::FileDialogConfig config;
182 config.path = (!val.empty() && std::filesystem::exists(val)) ? std::filesystem::path(val).parent_path().string() : std::string(getenv(
"HOME"));
183 config.flags = ImGuiFileDialogFlags_Modal;
184 fileDialog->SetFileStyle(IGFD_FileStyleByTypeDir,
nullptr, ImVec4(0.5f, 1.0f, 0.9f, 0.9f));
186 if (key ==
"VPXTablesPath") {
187 fileDialog->OpenDialog(
"FolderDlg_VPXTablesPath",
"Select VPX Tables Folder",
nullptr, config);
188 }
else if (key ==
"VPinballXPath") {
189 fileDialog->SetFileStyle(IGFD_FileStyleByFullName,
"((VPinballX.*))", ImVec4(0.0f, 1.0f, 0.0f, 0.9f));
190 fileDialog->OpenDialog(
"FileDlg_VPinballXPath",
"Select VPinballX Executable",
"((VPinballX.*))", config);
191 }
else if (key ==
"vpxIniPath") {
192 fileDialog->SetFileStyle(IGFD_FileStyleByExtention,
".ini", ImVec4(1.0f, 1.0f, 0.0f, 0.9f));
193 fileDialog->OpenDialog(
"FileDlg_vpxIniPath",
"Select VPinballX Config File",
".ini", config);
197 LOG_DEBUG(
"Dialog opened with key: " + dialogKey +
", isDialogOpen: " + std::to_string(isDialogOpen));
201 int snapToStep(
int value) {
202 const int steps[] = {0, 90, 180, 270, 360};
204 int minDiff = abs(value - steps[0]);
205 for (
int i = 1; i < 5; ++i) {
206 int diff = abs(value - steps[i]);
207 if (diff < minDiff) {
209 nearestStep = steps[i];