mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 14:20:42 -07:00
overlay: helper modal dialog for picking option values (#561)
## Link to GitHub Issue or related Pull Request, if one exists ## Description of change Introduce a new modal dialog for picking out some of the options to improve UX. Implemented a few for now: 1. UI for picking ASIO drivers from a list (for -iidxasio, -sdvxasio, -asioconvert, etc) 2. Button to generate card numbers for -card0 and card1 3. Processor Affinity selector (very similar to what's in Windows Task Manager, list of checkboxes) 4. file selector (file open dialog for various file path overrides like log file) 5. folder selector (folder override) I wrote a selector for COM ports as well but how we use the value was inconsistent (\\\\.\\COMx vs. COMx) and didn't see a huge point in it so I didn't check it in. ## Testing
This commit is contained in:
@@ -13,6 +13,15 @@ enum class OptionType {
|
||||
Hex,
|
||||
};
|
||||
|
||||
enum class OptionPickerType {
|
||||
None,
|
||||
AsioDriver,
|
||||
EACard,
|
||||
CpuAffinity,
|
||||
FilePath,
|
||||
DirectoryPath,
|
||||
};
|
||||
|
||||
struct OptionDefinition {
|
||||
std::string title;
|
||||
// unique identifier used for flag matching but also stored in config files
|
||||
@@ -32,6 +41,10 @@ struct OptionDefinition {
|
||||
bool sensitive = false;
|
||||
std::vector<std::pair<std::string, std::string>> elements = {};
|
||||
bool disabled = false;
|
||||
OptionPickerType picker = OptionPickerType::None;
|
||||
|
||||
// for OptionPickerType::FilePath
|
||||
std::string file_extension = "";
|
||||
};
|
||||
|
||||
class Option {
|
||||
|
||||
@@ -71,6 +71,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Text,
|
||||
.setting_name = "*.dll",
|
||||
.category = "Path Overrides",
|
||||
// intentionally not setting a file picker here to discourage people setting this without a good reason
|
||||
},
|
||||
{
|
||||
.title = "Open Configurator",
|
||||
@@ -121,6 +122,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.setting_name = "E004010000000000",
|
||||
.category = "Network",
|
||||
.sensitive = true,
|
||||
.picker = OptionPickerType::EACard,
|
||||
},
|
||||
{
|
||||
.title = "Player 2 Card",
|
||||
@@ -130,6 +132,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.setting_name = "E004010000000000",
|
||||
.category = "Network",
|
||||
.sensitive = true,
|
||||
.picker = OptionPickerType::EACard,
|
||||
},
|
||||
{
|
||||
// Player1PinMacro
|
||||
@@ -618,6 +621,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Text,
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Game Options",
|
||||
.picker = OptionPickerType::AsioDriver,
|
||||
},
|
||||
{
|
||||
.title = "IIDX BIO2 Firmware Update",
|
||||
@@ -778,6 +782,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Text,
|
||||
.game_name = "Sound Voltex",
|
||||
.category = "Game Options (Advanced)",
|
||||
.picker = OptionPickerType::DirectoryPath,
|
||||
},
|
||||
{
|
||||
.title = "SDVX Printer Output Clear",
|
||||
@@ -889,6 +894,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Text,
|
||||
.game_name = "Sound Voltex",
|
||||
.category = "Game Options",
|
||||
.picker = OptionPickerType::AsioDriver,
|
||||
},
|
||||
{
|
||||
// spice2x_SDVXSubPos
|
||||
@@ -1284,6 +1290,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc = "Sets a custom path to the modules folder.",
|
||||
.type = OptionType::Text,
|
||||
.category = "Path Overrides",
|
||||
// intentionally not setting a folder picker here to discourage people setting this without a good reason
|
||||
},
|
||||
{
|
||||
.title = "Screenshot Folder Override",
|
||||
@@ -1291,6 +1298,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc = "Sets a custom path to the screenshots folder.",
|
||||
.type = OptionType::Text,
|
||||
.category = "Path Overrides",
|
||||
.picker = OptionPickerType::DirectoryPath,
|
||||
},
|
||||
{
|
||||
.title = "Configuration Path Override",
|
||||
@@ -1310,6 +1318,8 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
"If left empty, %appdata%\\spice2x\\spicetools_screen_resize.json will be used.",
|
||||
.type = OptionType::Text,
|
||||
.category = "Path Overrides",
|
||||
.picker = OptionPickerType::FilePath,
|
||||
.file_extension = "JSON",
|
||||
},
|
||||
{
|
||||
// PatchManagerConfigPath
|
||||
@@ -1319,6 +1329,8 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
"If left empty, %appdata%\\spice2x\\spicetools_patch_manager.json will be used.",
|
||||
.type = OptionType::Text,
|
||||
.category = "Path Overrides",
|
||||
.picker = OptionPickerType::FilePath,
|
||||
.file_extension = "JSON",
|
||||
},
|
||||
{
|
||||
.title = "Intel SDE",
|
||||
@@ -1326,6 +1338,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc = "Path to Intel SDE kit path for automatic attaching.",
|
||||
.type = OptionType::Text,
|
||||
.category = "Development",
|
||||
.picker = OptionPickerType::DirectoryPath
|
||||
},
|
||||
{
|
||||
.title = "ea3-config.xml Override",
|
||||
@@ -1333,6 +1346,8 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc = "Sets a custom path to ea3-config.xml.",
|
||||
.type = OptionType::Text,
|
||||
.category = "Path Overrides",
|
||||
.picker = OptionPickerType::FilePath,
|
||||
.file_extension = "XML",
|
||||
},
|
||||
{
|
||||
.title = "app-config.xml Override",
|
||||
@@ -1340,6 +1355,8 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc = "Sets a custom path to app-config.xml.",
|
||||
.type = OptionType::Text,
|
||||
.category = "Path Overrides",
|
||||
.picker = OptionPickerType::FilePath,
|
||||
.file_extension = "XML",
|
||||
},
|
||||
{
|
||||
.title = "avs-config.xml Override",
|
||||
@@ -1347,6 +1364,8 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc = "Sets a custom path to avs-config.xml.",
|
||||
.type = OptionType::Text,
|
||||
.category = "Path Overrides",
|
||||
.picker = OptionPickerType::FilePath,
|
||||
.file_extension = "XML",
|
||||
},
|
||||
{
|
||||
.title = "bootstrap.xml Override",
|
||||
@@ -1354,6 +1373,8 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc = "Sets a custom path to bootstrap.xml.",
|
||||
.type = OptionType::Text,
|
||||
.category = "Path Overrides",
|
||||
.picker = OptionPickerType::FilePath,
|
||||
.file_extension = "XML",
|
||||
},
|
||||
{
|
||||
.title = "log.txt Override",
|
||||
@@ -1361,6 +1382,8 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.desc = "Sets a custom path to log.txt.",
|
||||
.type = OptionType::Text,
|
||||
.category = "Path Overrides",
|
||||
.picker = OptionPickerType::FilePath,
|
||||
.file_extension = "TXT",
|
||||
},
|
||||
{
|
||||
.title = "API TCP Port",
|
||||
@@ -1670,6 +1693,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
"Must provide a hexadecimal mask (e.g., 0x1ff00).",
|
||||
.type = OptionType::Hex,
|
||||
.category = "Performance",
|
||||
.picker = OptionPickerType::CpuAffinity,
|
||||
},
|
||||
{
|
||||
// spice2x_ProcessorEfficiencyClass
|
||||
@@ -1774,6 +1798,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
"Does nothing for games that do not output to exclusive WASAPI.",
|
||||
.type = OptionType::Text,
|
||||
.category = "Audio",
|
||||
.picker = OptionPickerType::AsioDriver,
|
||||
},
|
||||
{
|
||||
.title = "WASAPI Dummy Context",
|
||||
@@ -2607,6 +2632,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.type = OptionType::Text,
|
||||
.game_name = "LovePlus",
|
||||
.category = "Game Options (Advanced)",
|
||||
.picker = OptionPickerType::DirectoryPath,
|
||||
},
|
||||
{
|
||||
.title = "LovePlus Printer Output Clear",
|
||||
|
||||
@@ -142,7 +142,6 @@ namespace ImGui {
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleColor();
|
||||
if (!tooltip.empty() && ImGui::IsItemHovered(TOOLTIP_FLAGS)) {
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpTooltip(tooltip.c_str());
|
||||
}
|
||||
ImGui::PopID();
|
||||
@@ -159,7 +158,6 @@ namespace ImGui {
|
||||
bool clicked = ImGui::Button("\u00D7"); // multiplication sign (×)
|
||||
ImGui::PopStyleColor(4);
|
||||
if (!tooltip.empty() && ImGui::IsItemHovered(TOOLTIP_FLAGS)) {
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpTooltip(tooltip.c_str());
|
||||
}
|
||||
ImGui::PopID();
|
||||
|
||||
@@ -283,7 +283,7 @@ void ImGui_ImplSpice_NewFrame() {
|
||||
memset(io.MouseDown, false, sizeof(io.MouseDown));
|
||||
|
||||
// early quit if window not in focus
|
||||
if (!superexit::has_focus()) {
|
||||
if (!superexit::has_focus() || rawinput::OS_WINDOW_ACTIVE) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,13 +4,16 @@
|
||||
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
#include <shlobj.h>
|
||||
#include <commdlg.h>
|
||||
|
||||
#include "build/defs.h"
|
||||
#include "build/resource.h"
|
||||
#include "cfg/config.h"
|
||||
#include "cfg/configurator.h"
|
||||
#include "external/asio/asiolist.h"
|
||||
#include "external/imgui/imgui_internal.h"
|
||||
#include "external/imgui/misc/cpp/imgui_stdlib.h"
|
||||
#include "games/io.h"
|
||||
#include "avs/core.h"
|
||||
#include "avs/ea3.h"
|
||||
@@ -26,6 +29,7 @@
|
||||
#include "util/fileutils.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/resutils.h"
|
||||
#include "util/scope_guard.h"
|
||||
#include "util/time.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
@@ -33,6 +37,17 @@
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
static int CALLBACK BrowseCallbackProc(
|
||||
HWND hwnd,
|
||||
UINT uMsg,
|
||||
LPARAM lParam,
|
||||
LPARAM lpData) {
|
||||
if (uMsg == BFFM_INITIALIZED) {
|
||||
SendMessageW(hwnd, BFFM_SETSELECTIONW, TRUE, lpData);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
// same width as dummy marker
|
||||
@@ -40,6 +55,8 @@ namespace overlay::windows {
|
||||
const auto PROJECT_URL = "https://spice2x.github.io";
|
||||
|
||||
constexpr ImVec4 TEXT_COLOR_GREEN(0.f, 1.f, 0.f, 1.f);
|
||||
|
||||
std::unique_ptr<AsioDriverList> asio_driver_list;
|
||||
|
||||
Config::Config(overlay::SpiceOverlay *overlay) : Window(overlay) {
|
||||
this->title = "Configuration";
|
||||
@@ -2707,17 +2724,17 @@ namespace overlay::windows {
|
||||
|
||||
// open dialog to get path
|
||||
auto ofn_path = std::make_unique<wchar_t[]>(512);
|
||||
ofn_path[0] = L'\0';
|
||||
OPENFILENAMEW ofn {};
|
||||
memset(&ofn, 0, sizeof(ofn));
|
||||
ofn.lStructSize = sizeof(ofn);
|
||||
ofn.hwndOwner = nullptr;
|
||||
ofn.lpstrFilter = L"";
|
||||
ofn.lpstrFile = ofn_path.get();
|
||||
ofn.nMaxFile = 512;
|
||||
ofn.Flags = OFN_EXPLORER;
|
||||
ofn.Flags = OFN_EXPLORER | OFN_NOCHANGEDIR;
|
||||
ofn.lpstrDefExt = L"txt";
|
||||
ofn.lpstrInitialDir = L".";
|
||||
|
||||
// check for success
|
||||
auto guard = rawinput::set_os_window_focus_guard();
|
||||
if (GetSaveFileNameW(&ofn)) {
|
||||
|
||||
// update card path
|
||||
@@ -2905,6 +2922,246 @@ namespace overlay::windows {
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
std::string Config::build_option_value_picker_title(const OptionDefinition& definition) {
|
||||
// need to make these all unique since they are also used as ID
|
||||
// if not unique, append ## per ImGui rules to create unique ones
|
||||
switch (definition.picker) {
|
||||
case OptionPickerType::AsioDriver:
|
||||
return "ASIO Driver Picker";
|
||||
case OptionPickerType::EACard:
|
||||
return "EA Card Picker";
|
||||
case OptionPickerType::CpuAffinity:
|
||||
return "CPU Affinity Picker";
|
||||
case OptionPickerType::FilePath:
|
||||
return "File Picker";
|
||||
case OptionPickerType::DirectoryPath:
|
||||
return "Folder Picker";
|
||||
default:
|
||||
return "Unknown Picker";
|
||||
};
|
||||
}
|
||||
|
||||
void Config::build_option_value_picker(Option& option) {
|
||||
auto &definition = option.get_definition();
|
||||
if (definition.picker == OptionPickerType::AsioDriver) {
|
||||
if (asio_driver_list == nullptr) {
|
||||
asio_driver_list = std::make_unique<AsioDriverList>();
|
||||
}
|
||||
|
||||
if (asio_driver_list->driver_list.empty()) {
|
||||
ImGui::TextUnformatted("No ASIO drivers found.");
|
||||
} else {
|
||||
ImGui::TextUnformatted("Pick from ASIO drivers:");
|
||||
ImGui::SetNextItemWidth(300.f);
|
||||
if (ImGui::BeginListBox("##asiodrivers")) {
|
||||
for (const auto &driver : asio_driver_list->driver_list) {
|
||||
const bool is_selected = option.value == std::string(driver.name);
|
||||
if (ImGui::Selectable(fmt::format("[{}] {}", driver.id, driver.name).c_str(), is_selected)) {
|
||||
option.value = driver.name;
|
||||
}
|
||||
}
|
||||
ImGui::EndListBox();
|
||||
}
|
||||
}
|
||||
} else if (definition.picker == OptionPickerType::EACard) {
|
||||
ImGui::TextUnformatted("Generate a new card number:");
|
||||
if (ImGui::Button("Generate")) {
|
||||
char new_card[17];
|
||||
generate_ea_card(new_card);
|
||||
option.value = new_card;
|
||||
}
|
||||
} else if (definition.picker == OptionPickerType::CpuAffinity) {
|
||||
ImGui::TextUnformatted("Requires restart! Showing all procs in Group 0.");
|
||||
ImGui::TextUnformatted("");
|
||||
ImGui::TextUnformatted("Pick CPU cores to use:");
|
||||
const uint64_t cpu_count = GetActiveProcessorCount(0);
|
||||
uint64_t affinity = 0;
|
||||
if (!option.value.empty()) {
|
||||
try {
|
||||
affinity = std::stoull(option.value, nullptr, 16);
|
||||
} catch (const std::exception &ex) {
|
||||
option.value = "";
|
||||
affinity = 0;
|
||||
}
|
||||
}
|
||||
ImGui::BeginChild(
|
||||
"##cpuaffinity",
|
||||
ImVec2(
|
||||
0,
|
||||
ImGui::GetFrameHeightWithSpacing() * std::clamp(cpu_count / 4, 2ull, 4ull))
|
||||
);
|
||||
|
||||
bool selection_changed = false;
|
||||
uint64_t set_bits = 0;
|
||||
for (uint64_t i = 0; i < cpu_count; i++) {
|
||||
if (i % 4 != 0) {
|
||||
ImGui::SameLine();
|
||||
}
|
||||
|
||||
bool selected = (affinity & (1ULL << i)) != 0;
|
||||
if (ImGui::Checkbox(fmt::format("CPU {}", i).c_str(), &selected)) {
|
||||
selection_changed = true;
|
||||
if (selected) {
|
||||
affinity |= (1ULL << i);
|
||||
} else {
|
||||
affinity &= ~(1ULL << i);
|
||||
}
|
||||
}
|
||||
if (selected) {
|
||||
set_bits++;
|
||||
}
|
||||
}
|
||||
if (selection_changed) {
|
||||
if (set_bits == 0) {
|
||||
option.value = "";
|
||||
} else {
|
||||
option.value = fmt::format("0x{:X}", affinity);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
if (option.value.empty()) {
|
||||
ImGui::TextUnformatted("Using all CPUs (option default).");
|
||||
} else {
|
||||
ImGui::TextUnformatted("Using selected CPUs.");
|
||||
}
|
||||
} else if (definition.picker == OptionPickerType::FilePath) {
|
||||
if (!cfg::CONFIGURATOR_STANDALONE) {
|
||||
ImGui::BeginDisabled();
|
||||
ImGui::TextUnformatted("File browser only works in spicecfg.");
|
||||
}
|
||||
if (ImGui::Button("Select File...")) {
|
||||
// run in separate thread otherwise we get a crash
|
||||
if (!file_picker_thread) {
|
||||
file_picker_done = false;
|
||||
file_picker_path = "";
|
||||
file_picker_thread = new std::thread([this, &definition] {
|
||||
|
||||
std::wstring extensions;
|
||||
if (!definition.file_extension.empty()) {
|
||||
const std::wstring ext = s2ws(definition.file_extension);
|
||||
// filter to file extension preferred by the option (e.g., DLL)
|
||||
extensions = ext + L" Files (*." + ext + L")";
|
||||
extensions.push_back(L'\0');
|
||||
extensions += L"*." + ext;
|
||||
extensions.push_back(L'\0');
|
||||
// also add "All files" filter
|
||||
extensions += L"All Files (*.*)";
|
||||
extensions.push_back(L'\0');
|
||||
extensions += L"*.*";
|
||||
extensions.push_back(L'\0');
|
||||
// eol
|
||||
extensions.push_back(L'\0');
|
||||
} else {
|
||||
extensions = L"All Files (*.*)";
|
||||
extensions.push_back(L'\0');
|
||||
extensions += L"*.*";
|
||||
extensions.push_back(L'\0');
|
||||
extensions.push_back(L'\0');
|
||||
}
|
||||
|
||||
// open dialog to get path
|
||||
auto ofn_path = std::make_unique<wchar_t[]>(512);
|
||||
ofn_path[0] = L'\0';
|
||||
OPENFILENAMEW ofn{};
|
||||
ofn.lStructSize = sizeof(ofn);
|
||||
ofn.lpstrFilter = extensions.c_str();
|
||||
ofn.lpstrFile = ofn_path.get();
|
||||
ofn.nMaxFile = 512;
|
||||
ofn.nFilterIndex = 1;
|
||||
ofn.Flags = OFN_EXPLORER | OFN_NOCHANGEDIR;
|
||||
ofn.lpstrInitialDir = L".";
|
||||
|
||||
// check for success
|
||||
auto guard = rawinput::set_os_window_focus_guard();
|
||||
if (GetSaveFileNameW(&ofn)) {
|
||||
file_picker_path = std::filesystem::path(ofn_path.get());
|
||||
}
|
||||
|
||||
// clean up
|
||||
file_picker_done = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (file_picker_done) {
|
||||
file_picker_done = false;
|
||||
file_picker_thread->join();
|
||||
delete file_picker_thread;
|
||||
file_picker_thread = nullptr;
|
||||
if (!file_picker_path.empty()) {
|
||||
option.value = file_picker_path.string();
|
||||
}
|
||||
}
|
||||
|
||||
if (!cfg::CONFIGURATOR_STANDALONE) {
|
||||
ImGui::EndDisabled();
|
||||
ImGui::TextUnformatted("File browser only works in spicecfg.");
|
||||
}
|
||||
} else if (definition.picker == OptionPickerType::DirectoryPath) {
|
||||
if (!cfg::CONFIGURATOR_STANDALONE) {
|
||||
ImGui::BeginDisabled();
|
||||
ImGui::TextUnformatted("File browser only works in spicecfg.");
|
||||
}
|
||||
if (ImGui::Button("Browse...")) {
|
||||
// run in separate thread otherwise we get a crash
|
||||
if (!file_picker_thread) {
|
||||
file_picker_done = false;
|
||||
file_picker_path = "";
|
||||
file_picker_thread = new std::thread([this] {
|
||||
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||
|
||||
const auto spice_bin_path =
|
||||
libutils::module_file_name(nullptr).parent_path().wstring();
|
||||
|
||||
// SHBrowseForFolderW sucks, but the alternatives are:
|
||||
// 1. Use IFileDialog, which requires pulling in more Windows dependencies
|
||||
// 2. use ImGui::FileBrowser
|
||||
// both are acceptable but sticking to legacy UI for now for simplicity
|
||||
BROWSEINFOW info{};
|
||||
info.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
|
||||
info.lpfn = BrowseCallbackProc;
|
||||
info.lParam = reinterpret_cast<LPARAM>(spice_bin_path.c_str());
|
||||
auto guard = rawinput::set_os_window_focus_guard();
|
||||
auto pidl = SHBrowseForFolderW(&info);
|
||||
if (pidl) {
|
||||
wchar_t path[MAX_PATH];
|
||||
std::filesystem::path result;
|
||||
if (SHGetPathFromIDListW(pidl, path)) {
|
||||
file_picker_path = path;
|
||||
}
|
||||
CoTaskMemFree(pidl);
|
||||
} else {
|
||||
file_picker_path = "";
|
||||
}
|
||||
|
||||
// clean up
|
||||
file_picker_done = true;
|
||||
CoUninitialize();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (file_picker_done) {
|
||||
file_picker_done = false;
|
||||
file_picker_thread->join();
|
||||
delete file_picker_thread;
|
||||
file_picker_thread = nullptr;
|
||||
if (!file_picker_path.empty()) {
|
||||
option.value = file_picker_path.string();
|
||||
}
|
||||
}
|
||||
|
||||
if (!cfg::CONFIGURATOR_STANDALONE) {
|
||||
ImGui::EndDisabled();
|
||||
ImGui::TextUnformatted("File browser only works in spicecfg.");
|
||||
}
|
||||
|
||||
} else {
|
||||
ImGui::TextUnformatted("No picker available for this option. How did you get here?");
|
||||
}
|
||||
}
|
||||
|
||||
void Config::build_options(
|
||||
std::vector<Option> *options, const std::string &category, const std::string *filter) {
|
||||
int options_count;
|
||||
@@ -3183,12 +3440,24 @@ namespace overlay::windows {
|
||||
::Config::getInstance().updateBinding(games_list[games_selected], option);
|
||||
}
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
const std::string option_popup_id = build_option_value_picker_title(definition);
|
||||
if (definition.picker != OptionPickerType::None && !option.disabled && !definition.disabled) {
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Pick")) {
|
||||
ImGui::OpenPopup(option_popup_id.c_str());
|
||||
}
|
||||
if (ImGui::IsItemHovered(ImGui::TOOLTIP_FLAGS)) {
|
||||
ImGui::HelpTooltip(definition.desc.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar(); // ImGuiStyleVar_ItemSpacing
|
||||
|
||||
// clean up disabled item flags
|
||||
if (option.disabled || definition.disabled) {
|
||||
ImGui::PopItemFlag();
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopItemFlag(); // ImGuiItemFlags_Disabled
|
||||
ImGui::PopStyleVar(); // ImGuiStyleVar_Alpha
|
||||
}
|
||||
|
||||
// disabled help
|
||||
@@ -3199,6 +3468,44 @@ namespace overlay::windows {
|
||||
"Run spicecfg.exe to configure the options and then run spice(64).exe directly.");
|
||||
}
|
||||
|
||||
// value picker
|
||||
if (definition.picker != OptionPickerType::None &&
|
||||
!option.disabled && !definition.disabled) {
|
||||
if (ImGui::BeginPopupModal(option_popup_id.c_str(),
|
||||
nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
// for min width enforcement
|
||||
ImGui::Dummy(ImVec2(320.f, 0.f));
|
||||
ImGui::TextColored(ImVec4(1, 0.7f, 0, 1), "%s", definition.title.c_str());
|
||||
|
||||
ImGui::TextUnformatted("");
|
||||
|
||||
ImGui::TextUnformatted("Current value:");
|
||||
ImGui::BeginDisabled();
|
||||
// keeping it read only; if you want to make this editable, we need to refactor the
|
||||
// input validation logic above (for int/hex) so people don't end up with invalid
|
||||
// values that can crash spicecfg
|
||||
ImGui::InputText("", &option.value, ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
if (ImGui::ClearButton("Reset to default")) {
|
||||
option.value = "";
|
||||
}
|
||||
|
||||
ImGui::TextUnformatted("");
|
||||
|
||||
build_option_value_picker(option);
|
||||
|
||||
ImGui::TextUnformatted("");
|
||||
|
||||
if (ImGui::Button("Save & Close")) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
this->options_dirty = true;
|
||||
::Config::getInstance().updateBinding(games_list[games_selected], option);
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
// next item
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
@@ -76,6 +76,10 @@ namespace overlay::windows {
|
||||
std::string search_filter = "";
|
||||
std::string search_filter_in_lower_case = "";
|
||||
|
||||
std::filesystem::path file_picker_path;
|
||||
std::thread *file_picker_thread = nullptr;
|
||||
bool file_picker_done = false;
|
||||
|
||||
void build_buttons(const std::string &name, std::vector<Button> *buttons, int min = 0, int max = -1);
|
||||
void build_button(
|
||||
const std::string &name,
|
||||
@@ -104,6 +108,8 @@ namespace overlay::windows {
|
||||
void edit_light_popup(Light &primary_light, Light *light, const int alt_index);
|
||||
|
||||
void build_cards();
|
||||
std::string build_option_value_picker_title(const OptionDefinition& option);
|
||||
void build_option_value_picker(Option& option);
|
||||
void build_options(
|
||||
std::vector<Option> *options, const std::string &category, const std::string *filter=nullptr);
|
||||
void build_about();
|
||||
|
||||
@@ -38,6 +38,10 @@ namespace rawinput {
|
||||
uint32_t MIDI_NOTE_SUSTAIN = 20;
|
||||
|
||||
static MidiNoteAlgorithm MIDI_NOTE_ALGORITHM = MidiNoteAlgorithm::V2;
|
||||
|
||||
// the price we pay for making spice overlay consume from raw input
|
||||
// making focus detection a nightmare
|
||||
bool OS_WINDOW_ACTIVE = false;
|
||||
}
|
||||
|
||||
rawinput::MidiNoteAlgorithm rawinput::get_midi_algorithm() {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "device.h"
|
||||
#include "hotplug.h"
|
||||
#include "util/scope_guard.h"
|
||||
|
||||
namespace rawinput {
|
||||
|
||||
@@ -31,6 +32,15 @@ namespace rawinput {
|
||||
extern bool NAIVE_REQUIRE_FOCUS;
|
||||
extern bool RAWINPUT_REQUIRE_FOCUS;
|
||||
|
||||
// while active, prevents overlay from accepting any input
|
||||
// can be used while OS modal dialog is shown on top of overlay/spicecfg
|
||||
// always prefer RAII set_os_window_focus_guard instead of the global bool
|
||||
extern bool OS_WINDOW_ACTIVE;
|
||||
inline scope_guard set_os_window_focus_guard() {
|
||||
rawinput::OS_WINDOW_ACTIVE = true;
|
||||
return scope_guard {[]() { rawinput::OS_WINDOW_ACTIVE = false; }};
|
||||
}
|
||||
|
||||
struct DeviceCallback {
|
||||
void *data;
|
||||
std::function<void(void*, Device*)> f;
|
||||
|
||||
Reference in New Issue
Block a user