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 ==
"titleWeight") {
45 minVal = 0.2f; maxVal = 0.8f;
46 }
else if (key ==
"yearWeight") {
47 minVal = 0.0f; maxVal = 0.4f;
48 }
else if (key ==
"manufacturerWeight") {
49 minVal = 0.0f; maxVal = 0.3f;
50 }
else if (key ==
"romWeight") {
51 minVal = 0.0f; maxVal = 0.5f;
52 }
else if (key ==
"titleThreshold") {
53 minVal = 0.3f; maxVal = 0.8f;
54 }
else if (key ==
"confidenceThreshold") {
55 minVal = 0.4f; maxVal = 0.9f;
56 }
else if (key ==
"DPI Scale") {
57 minVal = 0.5f; maxVal = 3.0f;
58 }
else if (key.find(
"Alpha") != std::string::npos || key ==
"scrollbarLength" ||
59 key ==
"metadataPanelWidth" || key ==
"metadataPanelHeight") {
60 minVal = 0.0f; maxVal = 1.0f;
61 }
else if (key ==
"arrowHintWidth" || key ==
"arrowHintHeight") {
62 minVal = 0.0f; maxVal = 200.0f;
63 }
else if (key ==
"arrowThickness" || key ==
"arrowGlow") {
64 minVal = 0.0f; maxVal = 10.0f;
65 }
else if (key ==
"scrollbarWidth" || key ==
"thumbWidth") {
66 minVal = 0.0f; maxVal = 50.0f;
67 }
else if (key ==
"masterVol" || key ==
"mediaAudioVol" || key ==
"tableMusicVol" ||
68 key ==
"interfaceAudioVol" || key ==
"interfaceAmbienceVol") {
69 minVal = 0.0f; maxVal = 100.0f;
70 }
else if (key ==
"configUIWidth" || key ==
"configUIHeight") {
71 minVal = 0.1f; maxVal = 1.0f;
73 if (ImGui::SliderFloat(key.c_str(), &val, minVal, maxVal, format)) {
75 LOG_DEBUG(
"Updated " + sectionName +
"." + key +
" to " + std::to_string(val));
79 void renderInt(
const std::string& key, nlohmann::json& value,
const std::string& sectionName,
80 int minVal = 0,
int maxVal = 10000) {
81 int val = value.get<
int>();
82 if (key.find(
"WindowWidth") != std::string::npos || key.find(
"WindowHeight") != std::string::npos ||
83 key.find(
"MediaWidth") != std::string::npos || key.find(
"MediaHeight") != std::string::npos) {
84 minVal = 0; maxVal = 3840;
85 }
else if (key ==
"fontSize") {
86 minVal = 10; maxVal = 60;
87 }
else if (key ==
"screenshotWait") {
88 minVal = 0; maxVal = 60;
90 if (ImGui::InputInt(key.c_str(), &val)) {
91 val = std::clamp(val, minVal, maxVal);
93 LOG_DEBUG(
"Updated " + sectionName +
"." + key +
" to " + std::to_string(val));
97 void renderString(
const std::string& key, nlohmann::json& value,
const std::string& sectionName) {
98 std::string val = value.get<std::string>();
100 strncpy(buffer, val.c_str(),
sizeof(buffer) - 1);
101 buffer[
sizeof(buffer) - 1] =
'\0';
102 if (ImGui::InputText(key.c_str(), buffer,
sizeof(buffer))) {
103 value = std::string(buffer);
104 LOG_DEBUG(
"Updated " + sectionName +
"." + key +
" to " + buffer);
108 void renderColor(
const std::string& key, nlohmann::json& value,
const std::string& sectionName) {
110 value[0].get<
float>() / 255.0f,
111 value[1].get<float>() / 255.0f,
112 value[2].get<
float>() / 255.0f,
113 value[3].get<float>() / 255.0f
115 if (ImGui::ColorEdit4(key.c_str(), color)) {
116 value = nlohmann::json{
117 static_cast<int>(color[0] * 255.0f),
118 static_cast<int>(color[1] * 255.0f),
119 static_cast<int>(color[2] * 255.0f),
120 static_cast<int>(color[3] * 255.0f)
122 LOG_DEBUG(
"Updated " + sectionName +
"." + key +
" to [" +
123 std::to_string(value[0].get<int>()) +
"," +
124 std::to_string(value[1].get<int>()) +
"," +
125 std::to_string(value[2].get<int>()) +
"," +
126 std::to_string(value[3].get<int>()) +
"]");
130 void renderRotation(
const std::string& key, nlohmann::json& value,
const std::string& sectionName) {
131 int val = value.get<
int>();
132 static std::unordered_map<std::string, int> lastLoggedValues;
133 int currentValue = snapToStep(val);
134 if (ImGui::SliderInt(key.c_str(), ¤tValue, 0, 360,
"%d°")) {
135 int snappedValue = snapToStep(currentValue);
136 if (snappedValue != lastLoggedValues[key]) {
137 value = snappedValue;
138 lastLoggedValues[key] = snappedValue;
139 LOG_DEBUG(
"Updated " + sectionName +
"." + key +
" to " + std::to_string(snappedValue) +
"°");
144 void renderKeybind(
const std::string& key, nlohmann::json& value,
const std::string& sectionName,
145 bool& isCapturing, std::string& capturingKeyName) {
146 if (!value.is_string()) {
147 LOG_DEBUG(
"Invalid type for keybind " + key +
", expected string, got " + value.type_name());
150 std::string currentBind = value.get<std::string>();
151 std::string buttonLabel =
"[" + key +
": " + (currentBind.empty() ?
"Unbound" : currentBind) +
"]";
152 if (ImGui::Button(buttonLabel.c_str(), ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
155 capturingKeyName = key;
156 LOG_DEBUG(
"Started capturing key for " + sectionName +
"." + key);
159 if (isCapturing && capturingKeyName == key) {
160 ImGui::Text(
"Press a key or joystick input to bind... (Esc to cancel)");
164 void renderPathOrExecutable(
const std::string& key, nlohmann::json& value,
const std::string& sectionName,
165 ImGuiFileDialog* fileDialog,
bool& isDialogOpen, std::string& dialogKey) {
166 std::string val = value.get<std::string>();
168 strncpy(buffer, val.c_str(),
sizeof(buffer) - 1);
169 buffer[
sizeof(buffer) - 1] =
'\0';
170 ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x - 60);
171 if (ImGui::InputText(
"##value", buffer,
sizeof(buffer))) {
172 value = std::string(buffer);
173 LOG_DEBUG(
"Updated " + sectionName +
"." + key +
" to " + buffer);
175 ImGui::PopItemWidth();
177 if (ImGui::Button(
"Browse", ImVec2(50, 0))) {
178 LOG_DEBUG(
"Browse button clicked for " + key);
179 IGFD::FileDialogConfig config;
180 config.path = (!val.empty() && std::filesystem::exists(val)) ? std::filesystem::path(val).parent_path().string() : std::string(getenv(
"HOME"));
181 config.flags = ImGuiFileDialogFlags_Modal;
182 fileDialog->SetFileStyle(IGFD_FileStyleByTypeDir,
nullptr, ImVec4(0.5f, 1.0f, 0.9f, 0.9f));
184 if (key ==
"VPXTablesPath") {
185 fileDialog->OpenDialog(
"FolderDlg_VPXTablesPath",
"Select VPX Tables Folder",
nullptr, config);
186 }
else if (key ==
"VPinballXPath") {
187 fileDialog->SetFileStyle(IGFD_FileStyleByFullName,
"VPinballX*", ImVec4(0.0f, 1.0f, 0.0f, 0.9f));
188 fileDialog->OpenDialog(
"FileDlg_VPinballXPath",
"Select VPinballX Executable",
"VPinballX*", config);
189 }
else if (key ==
"vpxIniPath") {
190 fileDialog->SetFileStyle(IGFD_FileStyleByExtention,
".ini", ImVec4(1.0f, 1.0f, 0.0f, 0.9f));
191 fileDialog->OpenDialog(
"FileDlg_vpxIniPath",
"Select VPinballX Config File",
".ini", config);
195 LOG_DEBUG(
"Dialog opened with key: " + dialogKey +
", isDialogOpen: " + std::to_string(isDialogOpen));
199 int snapToStep(
int value) {
200 const int steps[] = {0, 90, 180, 270, 360};
202 int minDiff = abs(value - steps[0]);
203 for (
int i = 1; i < 5; ++i) {
204 int diff = abs(value - steps[i]);
205 if (diff < minDiff) {
207 nearestStep = steps[i];